文章目录
- 前言
- 一、运行时声明、基于类型的声明
- 二、一个小小的好奇心
- 三、非 script setup场景下
- 总结
前言
在 Vue 3 中,组件的声明方式主要有两种:运行时声明和基于类型的声明。这两种方式在 Vue 3 的 Composition API 中体现得尤为明显。
一、运行时声明、基于类型的声明
这两天一直有一个疑问,为什么这种方式声明属性不能支持Type和interface?
//这种方式会报错,同理,Type声明的类型也不能
defineProps({
navigationItem :NavigationItem,//“NavigationItem”仅表示类型,但在此处却作为值使用。ts(2693)
author: Person,
book:shtype//“shtype”仅表示类型,但在此处却作为值使用。ts(2693
})
先来了解一下两个概念:运行时声明、基于类型的声明
当使用
<script setup lang="ts">
const props = defineProps({
foo: { type: String, required: true },
bar: Number
})
props.foo // string
props.bar // number | undefined
</script>
这被称之为“运行时声明”,因为传递给 defineProps() 的参数会作为运行时的 props 选项使用。
然而,通过泛型参数来定义 props 的类型通常更直接:
<script setup lang="ts">
const props = defineProps<{
foo: string
bar?: number
}>()
</script>
这被称之为“基于类型的声明”。编译器会尽可能地尝试根据类型参数推导出等价的运行时选项。在这种场景下,我们第二个例子中编译出的运行时选项和第一个是完全一致的。
基于类型的声明或者运行时声明可以择一使用,但是不能同时使用。
我们也可以将 props 的类型移入一个单独的接口中:
<script setup lang="ts">
interface Props {
foo: string
bar?: number
}
const props = defineProps<Props>()
</script>
还可以从外部导入接口声明:
<script setup lang="ts">
import type { Props } from './foo'
const props = defineProps<Props>()
</script>
在 3.2 及以下版本中,defineProps() 的泛型类型参数仅限于类型文字或对本地接口的引用。
这个限制在 3.3 中得到了解决。最新版本的 Vue 支持在类型参数位置引用导入和有限的复杂类型。但是,由于类型到运行时转换仍然基于 AST,一些需要实际类型分析的复杂类型,例如条件类型,还未支持。您可以使用条件类型来指定单个 prop 的类型,但不能用于整个 props 对象的类型。
上一篇文章中注意到Prop校验的话,有一个默认值选项,官方文档说,
当使用基于类型的声明时,我们失去了为 props 声明默认值的能力。这可以通过 withDefaults 编译器宏解决:
export interface Props {
msg?: string
labels?: string[]
}
const props = withDefaults(defineProps<Props>(), {
msg: 'hello',
labels: () => ['one', 'two']
})
这将被编译为等效的运行时 props default 选项。此外,withDefaults 帮助程序为默认值提供类型检查,并确保返回的 props 类型删除了已声明默认值的属性的可选标志。
二、一个小小的好奇心
能不能在接口中声明默认值?搜了一下:
特别好奇,尝试了一下:
interface NavigationItem{
children: Array<NavigationItem>,
id:string,
name:string = 'sh',
propDef: {
type: String,
required: false,
default:""
},
path:string,
child:{
id:string,
name:string,
path:string,
child:{}
}
}
接口中也不应该这样的写法,但是编译器没有报错:
propDef: {
type: String,
required: false,
default:""
}
实例化时候:
这是类型啊!!!所以不要把接口和对象的各种约束等方式混淆了。老老实实按照官方文档来吧,好奇害死猫,浪费了不少时间去验证一些奇奇怪怪的想法。还有就是搜到的不一定是你需要的。
三、非 script setup场景下
如果没有使用 script setup,那么为了开启 props 的类型推导,必须使用 defineComponent()。传入 setup() 的 props 对象类型是从 props 选项中推导而来:
import { defineComponent } from 'vue'
export default defineComponent({
props: {
message: String
},
setup(props) {
props.message // <-- 类型:string
}
})
重点来了,基于类型的声明能干的事儿,运行时声明也能干
通过基于类型的声明,一个 prop 可以像使用其他任何类型一样使用一个复杂类型:
<script setup lang="ts">
interface Book {
title: string
author: string
year: number
}
const props = defineProps<{
book: Book
}>()
</script>
对于运行时声明,我们可以使用 PropType 工具类型:
import type { PropType } from 'vue'
const props = defineProps({
book: Object as PropType<Book>
})
其工作方式与直接指定 props 选项基本相同:
import { defineComponent } from 'vue'
import type { PropType } from 'vue'
export default defineComponent({
props: {
book: Object as PropType<Book>
}
})
总结
运行时声明: 这是一种传统的 Vue 组件声明方式,它主要依赖于 Vue 的运行时特性。在 Vue 2.x 中,这种方式主要通过 options API 实现,例如 data、methods、computed、watch 等。在 Vue 3 中,虽然 Composition API 被引入,但运行时声明仍然是一种可选的方式。例如,可以直接在 setup 函数中定义响应式变量、方法等,无需额外的类型注解,Vue 3 的响应式系统会在运行时自动处理这些声明。
基于类型的声明: 这种方式是 Vue 3 新引入的,特别是与 TypeScript 一起使用时,可以提供更严格的类型检查和更好的开发体验。基于类型的声明通常涉及到使用 TypeScript 的类型注解来定义组件的 props、emits、slots 以及其他响应式状态。Vue 3 的 Composition API,如 ref、reactive、computed、watch 等函数,都可以与 TypeScript 的类型系统很好地配合工作。
人间的事往往如此,当时提起痛不欲生,几年之后,也不过是一场回忆而已。