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

相关推荐
学习使我快乐0124 分钟前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript
bobostudio199525 分钟前
TypeScript 设计模式之【策略模式】
前端·javascript·设计模式·typescript·策略模式
黄尚圈圈1 小时前
Vue 中引入 ECharts 的详细步骤与示例
前端·vue.js·echarts
浮华似水2 小时前
简洁之道 - React Hook Form
前端
正小安4 小时前
如何在微信小程序中实现分包加载和预下载
前端·微信小程序·小程序
_.Switch6 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
一路向前的月光6 小时前
Vue2中的监听和计算属性的区别
前端·javascript·vue.js
长路 ㅤ   6 小时前
vite学习教程06、vite.config.js配置
前端·vite配置·端口设置·本地开发
长路 ㅤ   6 小时前
vue-live2d看板娘集成方案设计使用教程
前端·javascript·vue.js·live2d
Fan_web6 小时前
jQuery——事件委托
开发语言·前端·javascript·css·jquery