Subscribe on changes!

Inconsistent props type resolving in defineComponent between runtime phase & type checking phase

avatar
Jan 21st 2021

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

avatar
Jan 21st 2021

I think props is data verification, but not type detection ...

avatar
Jan 21st 2021

I think props is data verification, but not type detection ...

The verification is inconsistent in different prop format with actually the same behavior.

avatar
Jan 21st 2021

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
avatar
Jan 26th 2021

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
avatar
Jan 26th 2021

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.

avatar
Feb 9th 2021

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.

avatar
Apr 16th 2021

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