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

相关推荐
小阮的学习笔记8 分钟前
Vue3中使用LogicFlow实现简单流程图
javascript·vue.js·流程图
YBN娜9 分钟前
Vue实现登录功能
前端·javascript·vue.js
阳光开朗大男孩 = ̄ω ̄=9 分钟前
CSS——选择器、PxCook软件、盒子模型
前端·javascript·css
杨荧11 分钟前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
minDuck13 分钟前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
小政爱学习!34 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。39 分钟前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼1 小时前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k09331 小时前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang13581 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端