-
Notifications
You must be signed in to change notification settings - Fork 431
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New way to define props
and emits
options
#447
Comments
props
and emits
helper to define corresponding component optionsprops
and emits
options
This comment has been minimized.
This comment has been minimized.
@ktsn Am I still able to use |
@zischler You can still use |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@ktsn I'm currently trying something like this and it doesn't seem to work. (My linter also gives errors) const Props = props({
someProp: {
type: Number,
default: 100,
required: false,
},
})
@Options<Carousel>({
watch: {
this.swipe.hasCursorDown() {
this.hasCursorDown = !this.hasCursorDown;
}
}
})
export default class Carousel extends mixins(Props) {
swipe = new Swipe();
hasCursorDown = false;
.
.
. |
@zischler Please open a new issue with a reproduction for a bug report. Thank you. |
@ktsn I didn't know that |
I would like to know what other devs are thinking about the pain points I explain here ? |
I agree that : (Vue 3 : class-component) const Props = props({
firstProp: {
type: Number,
required: true
},
secondProp: {
type: Number,
required: true
}
})
export default Counter extends mixins(Props, Emits) {
} (Vue 2 class-component + property-decorator) export default Counter extends Vue {
@Prop() firstProp!: number;
@Prop() secondProp!: number;
} indeed the new Vue version is so much more verbose, less readable, less understandable, and of course less intuitive and less logical for a TypeScript Developer. |
These snippets are unfair to compare. The equivalent props settings are: const Props = props({
firstProp: Number,
secondProp: Number
})
export default Counter extends Props {} vs. export default Counter extends Vue {
@Prop()
firstProp?: number
@Prop()
secondProp?: number
} As for verbosity, I don't think there is much difference between the two approaches. You eventually need the same props options if you want to define your props in detail. Rather, I think the decorator approach is slightly more verbose as we have to write I'm curious what aspects of this proposal makes you feel less readable, less understandable, less intuitive and less logical? By using |
What will be the equivalent of the following code in vue-class-component v8? export interface Person {
firstName: string,
lastName: string
}
@Prop(Object) readonly person: Person Because when using this code:
Typescript typings of props are not working. |
@Mikilll94 You can use export interface Person {
firstName: string,
lastName: string
}
const Props = props({
person: {
type: Object as PropType<Person>
required: true
}
})
export default class HelloWorld extends Props {} |
So the verbosity is the same for primitive types but as you show, we need this PropType hack for "complex" types , types which are used everywhere actually. export interface Person {
firstName: string,
lastName: string
}
const Props = props({
person: Person
})
export default class HelloWorld extends Props {} Why can't this be written ? Can this limitation be alleviated in a followup Vue release ? Is there work going in this direction? I hope it's clear to the Vue.ts devs that this is an API downgrade |
Because export class Person {
firstName!: string
lastName!: string
}
const Props = props({
person: Person
})
export default class HelloWorld extends Props {}
Well, this |
Thanks for the explanation, that being said I still think that the downsides outwheights those future improvements. |
Ok, I understand what you mean but I think make your component extend Props is not intuitive. BTW: |
One idea to make it closer to the decorator approach would be: class AppProps {
// optional prop
foo?: string
// required prop
bar: string = prop({ required: true })
// required prop (without runtime check)
bar2!: string
// optional prop with default
baz = prop({ default: 'default value' })
}
class App extends Vue.props(AppProps) {} Just roughly implemented it and seems working. But I have to make sure if it actually works with practical code/edge cases etc. This approach doesn't require you to define runtime options ( It still needs to be defined out of the component class but it is the root restriction that we cannot modify the type parameter from in-class definition and I've been thinking of this problem for a long time but no luck. If there is an approach to solve this problem with the definition inside a class, I'm happy to consider that. |
Maybe it could be the combination of both..
The boilerplate of name is acceptable. |
Let me summarize the points (if I'm missing something, please add it!): Definition verbosityIf we re-use the Vue core's canonical const Props = props({
// to get person: Person type...
person: {
type: Object as PropType<Person>,
required: true
}
})
class App extends Props {} The decorator (and the alternative approach above) does not require class App extends Vue {
@Prop
person: Person
} Definition in-class vs. out-classVue's TSX refers On the other hand, the decorator allows us to define props inside a class. It is not possible to modify |
Thinking of the intuitiveness of in-class definition vs. out-class one, I noticed React's props definition is put outside a class. interface Props {
person: Person
}
class App extends React.Component<Props> {} For me, this way of definition is clear and intuitive enough and I have not heard any negative voice about that as far as I know. The alternative proposal looks similar to this way: class Props {
person!: Person
}
class App extends Vue.props(Props) {} Defining props individually, then passing it to the super constructor. The difference is React's is type (interface) while the proposed one is value (class). Is it still unintuitive? It would be appreciated to hear everyone's thoughts. |
Thanks @ktsn for your new proposal. I saw too that React has Props declaration outside the class component. The class seems intuitive too. Firstly:Will it be possible for default props value to be declared and assigned like that: class Props {
person = new Person();
} If not, why? (But it not a big deal cause with the old decorator way we already had to write Secondly:With this new proposal API, will the props be properly type-checked at the component call site? <MyComponent :person="new Person()"/> So here, will the Lastly:Does this new proposal API alleviate the need of the BTW: |
I also think this is the best of the current proposals. |
Thank you for your feedback. I'm answering your questions below: FirstlyI'm introducing To differentiate
|
Personally I prefer an interface to define props (makes more sense to me), I love the above example of defining props and I hope we end up with the same or similar implementation like the above. Related to #416
Personally I don't like the idea of props to be proxied on const Props = props({
something: String,
});
export class SomeComponent extends Props {
something = 'CLASS PROPERTY aka Data';
public render() {
return (
<div>
{this.$props.something} - {this.something}
</div>
);
}
} <SomeComponent something="PROP VALUE" /> The result of this component is: <div>
PROP VALUE - PROP VALUE
</div> but where is the value of the I really hope we change this behavior @ktsn !!! |
The challenge of Vue's prop is that it is needed to be defined as a value to be able to use it. For example, in the canonical Vue component: // HelloWorld.js
export default {
props: {
foo: String
},
mounted() {
// When it is used as <HelloWorld foo="Hello" bar="World" />
console.log(this.foo) // Hello
console.log(this.bar) // undefined
}
} In the above example, although the parent component passes values This is the reason that I think we have to define There was a proposal to make it optional but it was dropped later because of issues regarding attribute fallthrough behavior. I'm not sure if there is another way to make it closer to interface under this restriction. But I'm happy to hear an idea if any.
Unfortunately, this is something we cannot change in Vue Class Component as it's Vue's behavior. |
Thanks @ktsn again for your answer describing all the points I wrote! I'm happy to see this proposal and to understand why it has to be a "little bit more verbose" for runtime Vue validation! When this new proposal API will be on production so we could try it? Thanks again for putting consideration on our feedback and for your investment in integrating typescript into Vue as well as possible. |
@nicolidin I'm going to release the next beta including the improved approach this weekend so that you can try it in your code base. If you mean the official release of v8.0.0, I cannot say a specific date for that but I wish it will be by the end of Oct. |
@ktsn Give as please one example how you can use Props and Mixins together. One thing that I noticed, is that with this implementation I lost the
interface Person {
name: string;
age: number;
}
export class Props {
person = prop<Person>({ required: true }); // for the moment you need this for the browser, ignored :(
}
export class Comp extends Vue.props(Props) {
public render() {
return (
<button>SOMETHING....</button>
);
}
}
interface Person {
name: string;
age: number;
}
export class Props {
person = prop<Person>({ required: true });
}
class Component<P> extends Vue {
$props!: P; // Inform the IDE for the available props
}
export class Comp extends Component<Props> {
public render() {
return (
<button>SOMETHING....</button>
);
}
} Also thank you very much for the quick response on my previous post. |
I got the same info from both types (the printed type is, of course, different because vue-class-component adds extra types allowed in JSX). I suppose your IDE handles some special cases of JSX and prints the dedicated error message? I guess there is nothing we can do if that is the case. 🤔 |
Basically the see the comments please... class Component<P> extends Vue {
// all the informations coming from here, in this case all the info/types from class `Props`
// this is the reason why the `Insert required attribute` is available, and the printed type is shorter
$props!: P; <========= $props
}
export class Comp extends Component<Props> {
public render() {
return (
<div>SOMETHING....</div>
);
}
} but is all good, we going to find later solution for this :) |
From the class perspective, is there a reason I'd define my constructor args in a parent class and then extend that parent for the implementation? That's the hardest part for me to embrace this kind of solution. class Foo_vars {
constructor(protected bar: string) { ... }
}
class Foo extends Foo_vars {
bar() { this.bar }
} |
I already described why we have to use inheritance in this thread. #447 (comment) |
@ktsn, thanks! I was having trouble navigating the different scenarios this solution is trying to resolve. Quick question - you seem to be emphasizing that decorators will not be a solution going forward - is that true or are you only talking about @component and @prop? |
I've written details of the alternative approach at #465. Thank you for everyone's feedback! |
When i try to import Are you sure
|
There is an alternative idea here. Feedback welcome!
Summary
props
andemits
helper function to define corresponding component options in class syntax with type safety.Motivation
We need additional helpers to define
props
andemits
because there are no corresponding concepts in class syntax. You can define them via@Options
decorator, but the problem of the decorator approach is that it does not properly type the component type.Because
props
andemits
options modify the existing component types$props
and$emit
, and has runtime declaration (validator
,default
, etc.) in addition to types, we have to define them as a super class (mixins).Details
To provide
props
andemits
function. They receive as the same value as componentprops
andemits
options.They return a class component mixin so that you can use them with
mixins
helper function:As they are just Vue constructors, you can just extend it if there are no other mixins to extend:
Why not decorators?
There has been an approach to define
props
with ES decorators.But the decorator approach has several issues unresolved yet as stated in abandoned Class API RFC for Vue core. Let's bring them here and take a closer look:
Generic argument still requires the runtime props option declaration - this results in a awkward, redundant double-declaration.
Since decorators do not modify the original class type, we cannot type
$props
type with them:To properly type props, we have to pass a type parameter to the super class which is a redundant type declaration.
Using decorators creates a reliance on a stage-2 spec with a lot of uncertainties, especially when TypeScript's current implementation is completely out of sync with the TC39 proposal.
Although the current decorators proposal is stage 2, TypeScript's decorator implementation (
experimentalDecorators
) is still based on stage 1 spec. The current Babel decorator (@babel/plugin-proposal-decorators
) is based on stage 2 but there is still uncertainty on the spec as the current spec (static decorator) is already different from the original stage 2 proposal (there is a PR to add this syntax in Babel), and also there is another proposal called read/write trapping decorators due to an issue in the static decorators.Vue Class Component already uses decorators syntax with
@Component
(in v8@Options
) but it would be good not to rely on it too much in a new API because of its uncertainty and to reduce the impact of potential breaking changes in the future when we adapt Vue Class Component with the latest spec.In addition, there is no way to expose the types of props declared with decorators on this.$props, which breaks TSX support.
This is similar to the first problem. The Vue component type has
Props
type parameter that TSX uses for checking available props type. For example, let's say your component has a type{ value: number }
as the props type, then the component type isComponent<{ value: number }>
(this is different from the actual Vue type but you can get the idea from it). TSX knows what kind of value should be passed for the component:It is impossible to define the props type like the above with decorators because decorators cannot intercept the original class types.
The above class component can have the property
value
of typenumber
but it is not defined as props but just a normal property on the type level. Therefore, the component type is likeComponent<{}>
which TSX cannot know about the props.The text was updated successfully, but these errors were encountered: