- Notifications
You must be signed in to change notification settings - Fork433
Description
Summary
To be able to define component props with class properties. You can useprop
helper to specify detailed prop options:
import{Vue,prop}from'vue-class-component'// Define props in a classclassProps{count=prop({// Same as Vue core's prop optiontype:Number,required:true,validator:(value)=>value>=0})}// Pass the Props class to `Vue.with` so that the props are defined in the componentexportdefaultclassMyCompextendsVue.with(Props){}
In TypeScript, you can omitprop
helper when you only need to define its type (runtime validation does not happen in that case):
import{Vue,prop}from'vue-class-component'classProps{// optional propfoo?:string// required propbar!:string// optional prop with defaultbaz=prop<string>({default:'default value'})}exportdefaultclassMyCompextendsVue.with(Props){}
You need to specify"useDefineForClassFields": true
for TypeScript compiler option to let Vue Class Component aware of the properties without initializer (in the above examplefoo
andbar
):
{"compilerOptions": {"useDefineForClassFields":true }}
Motivation
One motivation is to properly typeProps
type parameter of a component for props type checking in TSX andVetur. TSX can validate props type on compile type thanks to TypeScript:
import{defineComponent}from'vue'// The type Props = { count: number } in component typeconstCounter=defineComponent({props:{count:{type:Number,required:true}}})<Countercount={'Hello'}/>//Errorbecause`count`isoftype`number`
Vetur also offers similar prop type validation on<template>
block. To utilize these features, we need to properly typeProps
type parameter of a component.
The other motivation is less verbosity. Vue's basicprops
option requires us to define props withvalues then infers the prop type from the value. For example, we have to annotate complex type withPropType
utility:
interfacePerson{firstName:stringlastName:string}constApp=defineComponent({props:{// Specify value `Object` then annotate it with `PropType<Person>`person:ObjectasPropType<Person>}})
This is relatively verbose compared to the existing@Prop
decorator approach fromvue-property-decorator.
interfacePerson{firstName:stringlastName:string}@ComponentclassAppextendsVue{// Just specify `Person` type (and `@Prop` decorator) @Propperson:Person}
Ideally, the new approach should as short as the decorator approach.
Details
We will introduce two API:Vue.with
static method andprop
helper function.
Vue.with(...)
method receives a class constructor that describes the component props. It collects all class properties and generatesprops
option for the component under the hood. It also respects the property types for the props types:
import{Vue}from'vue-class-component'classProps{optional?:stringrequired!:number}classAppextendsVue.with(Props){// Vue.with generates the following props option under the hood// props: { optional: null, required: null }mounted(){// It retains the property types for propsthis.optional// string | undefinedthis.required// number}}
Note that we have to specifyuseDefineForClassFields: true
component option in TypeScript to make the above code works.
We can also specify detailed prop options by usingprop
helper (e.g.default
,validator
). Theprop
helper receives exact same as Vue core'sprops
option object:
classProps{// with validatorcount:number=prop({validator:(count:number)=>count>=0})// with default// You can specify the type via `prop` type parameteramount=prop<number>({default:1})}
Note that we have to specify the type of prop viaprop
helper type parameter when we usedefault
value. This is to differentiaterequired
prop and with-default
prop on the type level. That is,required
should be always of typestring
butwithDefault
should be of typestring
in the component while being of typestring | undefined
when it is used on a parent component since it does not have to receive a value. If the type is able to be inferred from the default value, you don't have to specify it.
classProps{// type is `string`required!:string// type is `WithDefault<string>`withDefault=prop({default:'default'})}classAppextendsVue.with(Props){mounted(){this.required// stringthis.withDefault// string}}// In the usage of TSX/template// required: string// withDefault: string | undefined<Apprequired="Hello"/>
Alternative approaches
Decorator approach
There has been an approach with@Prop
decorator but there are issues which already described in#447
TL;DR
- Cannot type the
Props
type parameter, then there is no way to check props type. - There are concerns regarding uncertainties of the spec.
Mixin approach
This is an approach proposed in#447. But it turned out too verbose compared to the decorator approach. There is also a feedback that defining it as a mixin is not intuitive.