深入对比vue3中defineProps两种类型声明方式

4,667 阅读4分钟

众所周知,vue3使用defineProps可以有两种写法,一种是传统的,和vue2保持一致,以对象的形式申明,一种是利用ts用类型标注来声明。根据官方,分别把他们称为运行时声明和基于类型声明,这里对这两种方式的不同之处进行一些对比。

写法

import { PropType } from 'vue'
interface Dog {
  name: string
  age?: number
}

withDefaults(defineProps<{
  msg?: Dog[]
}>(), {
  msg: () => [{ name: "" }],
})

defineProps({
  msg: {
    type: Array as PropType<Dog[]>,
    required: false,
    default: () => [{ name: "" }],
    validator: (val: Dog[]) => {
      return val.length > 0;
    },
  }
})

这里给出了两种定义方式的写法的对比:

  • 在类型定义上,如果是引用类型的props,运行时声明需要使用PropType类型断言,因为构造函数作为类型已经不能满足需要了。
  • 在是否必传上,基于类型声明的方式来的更为简单,只需要加一个问号,也是标准的ts语法,没有学习成本。
  • 在默认值上,二者保持一致,基本没有差别。
  • 在validator验证器上,并没有找到基于类型声明的验证器写法,但是可以通过复杂的ts类型做到,比如,下边的类型代表2到5的整数:
type Enumerate<N extends number, Acc extends number[] = []> = Acc["length"] extends N
    ? Acc[number]
    : Enumerate<N, [...Acc, Acc["length"]]>;
type RangeNumber<F extends number, T extends number> = Exclude<Enumerate<T>, Enumerate<F>>;
defineProps<{
  msg?: RangeNumber<2, 5>
}>()
  • 如果使用了基于类型的 prop 声明 ,Vue 会尽最大努力在运行时按照 prop 的类型标注进行编译。举例来说,defineProps<{ msg: string }> 会被编译为 { msg: { type: String, required: true }},也就是说,基于类型声明会拥有运行时声明的所有功能。

类型检查时机

顾名思义,运行时声明需要在运行的时候才能给出类型的判断,无法使用静态分析,vscode不能给出类型错误的判断。而使用类型声明的时候,静态分析会自动生成等效的运行时声明,并且会利用ts给出类型是否正确的判断。不过好在给子组件给props传值类型错误的时候,二者都能给出静态提示。

// 也就是说,我这里的默认值类型错误,vue却不会给出任何提示,只会在项目运行时。控制台给出警告
defineProps({
  msg: {
    type: Array as PropType<Dog[]>,
    default: () => 12,
  }
})

复杂的props

如果一个组件的props参数很多而且类型非常的复杂,这个时候使用基于类型声明可以导入其他文件中的类型(这个特性只在3.3以上版本支持,在 3.2 及以下版本中,defineProps() 的泛型类型参数仅限于类型文字或对本地接口的引用),示例如下:

import { PropsType } from './type';
withDefaults(defineProps<PropsType>(), {})

运行时声明似乎无法做到这样导入一个类型代表props类型,只能分别对每一个props进行标注,如果组件的props参数太多的话就非常不友好了。

import { PropsType } from './type';
defineProps( {
  msg: {
    type: Array as PropType<PropsType[]>
  }
  // 假设这里还有10多个参数。。。
})
// 尽管也可以像这样导出一个对象,不过这个本质是把对象写到另一个文件,掩耳盗铃罢了,而且导出的不是ts类型,没有通用性,极不推荐使用
import { PropsType } from './type';
defineProps(PropsType)

打包

类型声明由于是根据基于ts,所以在生产模式下打包的时候编译器会生成数组格式的声明来减少打包体积 (这里的 props 会被编译成 ['msg']),这样做的目的是为了去除类型声明等额外信息,因为在生产环境中这些类型检查和错误提示不再需要。转换后的数组格式声明仅仅用于指示组件接受哪些 props,而不包含类型信息。
而运行时声明是通过在组件实例创建之前,直接在代码中将 props 传递给组件的方式。这种方式不涉及类型声明,而是在代码中动态地传递属性。(此内容来源于chatgpt,目前还没有查询到相关资料表明运行时声明的打包体积小于基于类型声明).

结论

在写法上,基于类型声明更加贴合ts,定义的类型更加复杂,只需要懂ts的类型就可以了,也没有额外的学习成本,除了在验证器上略逊色于运行时声明,其他方面均不弱于运行时声明。所以,如果你的项目使用了ts,就十分推荐使用基于类型的props声明方式。