Inconsistent props type resolving in defineComponent between runtime phase & type checking phase
Version
3.0.5
Reproduction link
https://codesandbox.io/s/suspicious-brown-5mgcx?file=/src/index.ts
Steps to reproduce
import { defineComponent, PropType } from "vue";
function stringOnly(v: string) {}
defineComponent({
name: "PropsTest",
props: {
a: String,
a1: String as PropType<string>,
b: {
type: String,
default: undefined
},
b1: {
type: String as PropType<string>,
default: undefined
},
c: {
type: String,
default: "string"
}
},
setup(props) {
// @ts-expect-error
stringOnly(props.a);
stringOnly(props.a1); // <- error
// @ts-expect-error <- no error in fact, expected to be `string | undefined` but `string`
stringOnly(props.b);
// @ts-expect-error <- no error in fact, not consistent with a1
stringOnly(props.b1);
stringOnly(props.c);
}
});
What is expected?
see above
Default value (undefined) should be considered into prop's type.
At least value: String
and value: { type: String, default: undefined }
should has the same prop type.
The type inferred should be consistent in runtime validation & static check.
What is actually happening?
see above
I think
props
is data verification, but not type detection ...
The verification is inconsistent in different prop format with actually the same behavior.
Here are my expected types. Does anyone has different opinions?
Format | Expected Prop Type | Current Behavior |
---|---|---|
name: String |
string | undefined |
✅ |
name: String as PropType<X> |
X | undefined |
✅ |
name: { type: String, default: undefined } |
string | undefined |
string |
name: { type: String as PropType<X>, default: undefined } |
X | undefined |
X |
name: { type: String, default: null } |
string | null |
string |
name: { type: String, default: 'string' } |
string |
✅ |
name: { type: String, required: true } |
string |
✅ |
I think this is the right way to identify the type:
Format | Expected Prop Type | Current Behavior | |
---|---|---|---|
1 | name: String | string | undefined | ✅ |
2 | name: String as PropType |
X | undefined | ✅ |
3 | name: { type: String, default: undefined } | string | undefined | string |
4 | name: { type: String as PropType |
X | undefined | X |
5 | name: { type: String, default: null } | string | undefined | string |
6 | name: { type: String, default: 'string' } | string | undefined | string |
7 | name: { type: String, required: true } | string | ✅ |
8 | name: { type: String, default: 11 } | string | undefined | string |
9 | name: { type: String, default: () => ''} | string | ✅ |
10 | name: { type: String, default: () => {}} | string | undefined | string |
11 | name: { type: String as PropType |
string | undefined | string |
I think this is the right way to identify the type:
Format Expected Prop Type Current Behavior 1 name: String string | undefined ✅ 2 name: String as PropType X | undefined ✅ 3 name: { type: String, default: undefined } string | undefined string 4 name: { type: String as PropType, default: undefined } X | undefined X 5 name: { type: String, default: null } string | undefined string 6 name: { type: String, default: 'string' } string | undefined string 7 name: { type: String, required: true } string ✅ 8 name: { type: String, default: 11 } string | undefined string 9 name: { type: String, default: () => ''} string ✅ 10 name: { type: String, default: () => {}} string | undefined string 11 name: { type: String as PropType, default: 22} string | undefined string
I think when default type doesn't match prop type, there would better be an error. In most case people write them in mistake.
I voted to support the default
value must obey the restriction of the type
field, thus the type of props is always determined by the type field, and the default
field is only used to give a default value for that type, which can avoid accidentally giving wrong default values.
same problem.
foo: {
type: String,
validator: (value): value is 'bar' => value === 'bar'
}
// expected:
props.foo // => 'bar'
// now:
props.foo // => String | undefined
imagine world:
propType.string = (value): value is string => typeof value === 'string'
propTypeMustBeZero = (value): value is 0 => Object.is(value, 0)
props = {
foo: propTypeMustBeZero
}
// expected:
props.foo // => 0
// now:
// => error
props = {
foo?: propTypeMustBeZero
}
// expected:
props.foo // => 0 | undefined
// now:
// => error