defineComponent函数
1. 定义slot参数(vue@3.3支持)
tsx
import { defineComponent, type SlotsType } from 'vue';
const Comp = defineComponent({
slots: Object as SlotsType<{
header: { foo: string; bar: number }
}>,
setup(props, { slots }) {
slots.header({ foo: 'str', bar: 1 })
}
})
2. JSX校验
对组件的属性和事件进行类型校验。使用PropType定义组件属性,使用emits选项定义组件事件。
选项组件的TS Playground
tsx
const Comp = defineComponent({
props: {
foo: String as PropType<'large' | 'small'>
},
emits: {
bar: (val: number) => true
}
});
<Comp foo={'large'} onBar={(val) => {
val; // number
}}/>;
函数组件的TS Playground
tsx
const Comp = defineComponent((props: { foo: string; onBar: (val: number) => void }) => {
return () => {
<div>{props.foo}</div>
}
});
<Comp foo={'str'} onBar={(val) => {
val; // number
}}/>;
3. 泛型组件
函数组件支持在defineComponent使用泛型。
tsx
const Comp = defineComponent(
<T extends string | number>(props: { msg: T; list: T[] }) => {
const count = ref(0)
return () => (
<div>
{props.msg} {count.value}
</div>
)
}
);
<Comp msg="fse" list={['foo']} />;
<Comp msg={123} list={[123]} />;
// @ts-expect-error generics don't match
<Comp msg="fse" list={[123]} />
4. 推断this
类型
inject
选项的类型被推断为unknown- 推断
data
、computed
、props
、mixins
、emits
tsx
import { defineComponent, type PropType } from 'vue';
const Mixin = defineComponent({
props: {
ddd: Boolean,
}
})
const Comp = defineComponent({
props: {
bar: String as PropType<'large' | 'small'>
},
mixins: [Mixin],
emits: {
change: (val: string) => true
},
data() {
return {
aaa: 1
}
},
computed: {
bbb() {
return this.aaa + 1
}
},
inject: {
foo: 'foo',
},
created() {
this.bar; // "large" | "small" | undefined
this.foo; // unknown
this.aaa; // number
this.bbb; // number
this.ddd; // boolean
this.$emit('change', 'str') // 校验参数为string
}
})
5. 高阶组件
5.1 继承原生HTML元素
见vuejs/core#7444,还在RFC阶段
tsx
const MyImg = defineComponent({
props: {
foo: String
},
attrs: Object as AttrsType<ImgHTMLAttributes>,
created() {
this.$attrs.class // any
this.$attrs.style // StyleValue | undefined
this.$attrs.onError // ((payload: Event) => void) | undefined
this.$attrs.src // string | undefined
},
render() {
return <img {...this.$attrs} />
}
});
<MyImg class={'str'} style={'str'} src={'https://xxx.com/aaa.jpg'} onError={(e) => {
e; // Event
}}/>;
5.2 继承Vue组件
见vuejs/core#7444,还在RFC阶段
tsx
const Child = defineComponent({
props: {
foo: String
},
emits: {
baz: (val: number) => true
},
render() {
return <div>{this.foo}</div>
}
});
const Comp = defineComponent({
props: {
bar: Number
},
attrs: Object as AttrsType<typeof Child>,
created() {
this.$attrs.class // unknown
this.$attrs.style // unknown
this.$attrs.onBaz; // ((val: number) => any) | undefined
this.$attrs.foo; // string | undefined
},
render() {
return <Child {...this.$attrs} />
}
});
<Comp class={'str'} style={'str'} bar={1} foo={'str'} onBaz={(val) => {
val; // number
}} />;
5.3 强制转换组件类型
tsx
<script lang="ts">
import { defineComponent } from 'vue';
import type { ComponentProps } from 'vue-component-type-helpers';
import Child from './Child.vue'
const Comp = defineComponent({
components: {
Child
},
props: {
foo: {
type: String
}
},
})
export default Comp as typeof Comp & { new(): { $props: ComponentProps<typeof Child> } }
</script>
<template>
<h1>{{ foo }}</h1>
<Child v-bind="$attrs"></Child>
</template>
Setup script
1. defineSlots(vue@3.3支持)
作用同slots
选项
tsx
<script setup lang="ts">
defineSlots<{
header(props: { foo: string; bar: number }): any
}>()
</script>
<template>
<div class="container">
<slot name="header" foo="1" :bar="1"></slot>
<span>hello world</span>
</div>
</template>
2. defineProps
作用同props
选项
tsx
// Comp.vue
<script setup lang="ts">
defineProps<{
foo: string
}>()
</script>
<template>
<div class="container">{{ foo }}</div>
</template>
// 使用Comp组件
<Comp foo="str" />
3. 泛型组件
tsx
// Comp.vue
<script setup lang="ts" generic="T">
defineProps<{
foo: T
}>();
defineSlots<{
header(props: { bar: T[] }): any
}>()
</script>
<template>
<div>
<span>{{ foo }}</span>
<slot name="header" :bar="[1]"/>
</div>
</template>
// App.vue
<script setup>
import Comp from './Comp.vue'
</script>
<template>
<Comp :foo="1">
<template #header="info">
{{ info.bar }} // number[]
</template>
</Comp>
</template>
4. defineAttrs
使用 defineAttrs
宏实现高阶组件。(vuejs/core#7444,还在RFC阶段)
tsx
<script setup lang="ts">
import { Button } from 'element-plus';
const attrs = defineAttrs<typeof Button>();
</script>
TS类型体操
1. 抽出组件的props和events类型
tsx
import { Button } from 'vant'
// vue-component-type-helpers包的实现
type ComponentProps<T> =
T extends new () => { $props: infer P; } ? NonNullable<P> :
T extends (props: infer P, ...args: any) => any ? P :
{};
type ButtonPropsAndEvents = ComponentProps<typeof Button>
2. 抽出组件events
tsx
import { defineComponent } from 'vue'
const Comp = defineComponent({
emits: {
foo: (val: boolean) => true,
bar: (val: string) => true
}
})
// vue-component-type-helpers包的实现
type ComponentEmit<T> =
T extends new () => { $emit: infer E; } ? NonNullable<E> :
T extends (props: any, ctx: { emit: infer E; }, ...args: any) => any ? NonNullable<E> :
{};
type CompEvents = ComponentEmit<typeof Comp>
3. 抽出组件的slots
tsx
import { defineComponent, type SlotsType } from 'vue'
const Comp = defineComponent({
slots: Object as SlotsType<{
header: { foo: string; bar: number }
}>
})
// vue-component-type-helpers包的实现
type ComponentSlots<T> =
T extends new () => { $slots: infer S; } ? NonNullable<S> :
T extends (props: any, ctx: { slots: infer S; }, ...args: any) => any ? NonNullable<S> :
{};
type CompSlots = ComponentSlots<typeof Comp>
4. 获取组件实例
tsx
import { Button } from 'vant'
type ButtonInstance = InstanceType<typeof Button>
5. 其他常用工具函数
- PropType
- MaybeRef
- MaybeRefOrGetter
- ExtractPropTypes
- ExtractPublicPropTypes
配置
Vue模块类型
- ComponentCustomOptions 扩展自定义选项。
- ComponentCustomProperties 扩展全局属性
- GlobalComponents 定义全局组件类型,以便volar进行jsx属性校验。
- GlobalDirectives 定义全局指令, eg:
v-tooltip
- ComponentCustomProps。定义组件的自定义属性
- CSSProperties。定义style属性绑定上允许的值的类型。
TS config
- vueCompilerOptions.strictTemplates
支持更严格的 Template type checking,在使用未知的Component Tag 和 Props 时报告错误。
- compilerOptions.jsxImportSource
全局jsx定义,支持定义从react引入还是vue引入。见PR。
最后
考虑到vue2后面官方已经不维护了,而vue2的defineComponent
还有一些遗留的问题,例如无法推断inject
,我发布了一个npm包,vue-ts-utils,这个npm包同步vue3的defineComponent
功能,有问题可以在仓库的issue里面反馈。