Vue3 TypeScript使用技巧

defineComponent函数

1. 定义slot参数(vue@3.3支持)

TS Playground

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使用泛型。

TS Playground

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
  • 推断datacomputedpropsmixinsemits

TS Playground

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 强制转换组件类型

Vue TS Playground

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选项

Vue TS Playground

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选项

Vue TS Playground

tsx 复制代码
// Comp.vue
<script setup lang="ts">
defineProps<{
  foo: string
}>()
</script>

<template>
  <div class="container">{{ foo }}</div>
</template>

// 使用Comp组件
<Comp foo="str" />

3. 泛型组件

Vue TS Playground

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类型

TS Playground

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

TS Playground

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

TS Playground

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. 获取组件实例

TS Playground

tsx 复制代码
import { Button } from 'vant'

type ButtonInstance = InstanceType<typeof Button>

5. 其他常用工具函数

  • PropType
  • MaybeRef
  • MaybeRefOrGetter
  • ExtractPropTypes
  • ExtractPublicPropTypes

查看文档

配置

Vue模块类型

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里面反馈。

相关推荐
无人生还几秒前
从 Vue3 到 React · 快速上手系列第 9 篇:自定义 Hook(对标 Composables)
前端·vue.js·react.js
张龙68711 分钟前
RAG 知识库问答系统实战:分块、混合检索、RRF 融合与重排全链路拆解
前端
用户13060956072312 分钟前
第二章学完后,我对 webpack5 前端工程化的一点理解
前端
a11177613 分钟前
小鸡下蛋 小游戏 html
前端·开源·html
码哥DFS21 分钟前
算法练习day1-备战2027届秋招
前端·javascript·数据结构·算法
Bigger23 分钟前
因为一句「华流才是最屌的」,我做了一个中国风设计的 Skill
前端·人工智能·设计
别怪我很水32 分钟前
Vue element admin 浏览器本地存储 localStorage、useStorage
前端·javascript·vue.js
手揽回忆怎么睡33 分钟前
Ubuntu 22.04 64位安装MinIO
linux·前端·ubuntu
铁皮饭盒36 分钟前
网页端OCR, 加载6mb大小模型, 又快又准, 百度这次真香
前端·后端
Web极客码1 小时前
如何在WordPress中添加动态内容
服务器·前端·wordpress