本文参考 Vue3.0 文档 TypeScript with Composition API 模块。

本文假定用户使用场景是 <script setup> 组合式 API ,代码都跑写在 <script setup lang="ts"> 。所以文内大部分代码块里的代码会省略掉 <script setup lang="ts"> 这部分且不考虑 选项式 API 的情况。

props

props 的类型声明分为以下两种

  • 运行时声明(自动推导)
  • 基于类型的声明(泛型)
  • 两种方式编译以后得到的 “运行时的 props ”是一致的(即选项式API 里的 props ),以上两种都可以使用,但不能同时使用两种。

    运行时声明(自动推导)

    在定义组件内 props 时, defineProps 方法支持从它的参数自动推导类型

    const props = defineProps({
      year: { type: Number, required: true },
      month: Number,
      day: Number
    

    基于类型的声明(泛型)

    注意: 泛型参数可以包含从其他文件引入的类型,但它本身不能是一个直接导入的类型

    泛型形式无法定义默认值, 需要开启实验性功能里的 响应式解构 props 才能设定默认值

    也可以给 defineProps 传递定义好的对象类型或接口

    const props = defineProps<{
      year: number,
      month?: number,
      day?: number
    
    interface Props {
      year: number,
      month?: number,
      day?: number
    const props = defineProps<Props>()
    // 可以通过 “响应式 props 解构” 定义默认值
    // 该功能还属于实验性功能,开启需要做对应配置,具体查看标题引用的:“响应式 props 解构” 文档 
    const { year, month = 1, day = 1 } = defineProps<Props>()
    

    emit 类型定义方式与 props 一致,也是分为 “运行时”、“基于类型”两种方式

    运行时声明(自动推导)

    const emit = defineEmits(['update']);
    

    基于类型的声明(泛型)

    interface Emit {
      /** test emit: update func */
      (e: "update", id: number): void;
    const emit = defineEmits<Emit>();
    emit("update", 1)
    

    ref 类型标注共有三种方式

    import { Ref, ref } from 'vue';
    /** 自动推导: 有默认值 */
    let year = ref(2022); // Ref<number>
    /** 自动推导: 无默认值 */
    let yearEmpty = ref(); // Ref<any>
    
    import { ref, Ref } from 'vue';
    /** 类型注解: 必须有默认值 */
    let month: Ref<number | string> = ref('10'); // Ref<string | number>
    
    import { ref } from 'vue';
    /** 泛型: 有默认值 */
    let day = ref<number>(10); // Ref<number>
    /** 泛型: 无默认值 */
    let dayEmpty = ref<number>(); // Ref<number | undefined>
    

    reactive

    reactive 类型标注和 ref一样,共有三种方式

  • 泛型(不推荐使用)
  • import { reactive } from 'vue';
    /** 自动推导: 有默认值 */
    const tom = reactive({ name: 'Tom' }); // { name: string; }
    /** 自动推导: 无默认值 */
    const tomEmpty = reactive({}); // {}
    
    import { reactive, ref } from 'vue';
    interface Dog { name: string }
    /** 类型注解: 普通对象 */
    const spike: Dog = reactive({ name: 'Spike' }) //  Dog
    /** 类型注解: 含 ref 对象 */
    const spikeDeepRef: Dog = reactive({ name: ref('Spike') }) // Dog
    

    泛型(不推荐使用)

    官方文档: 不推荐使用 reactive() 的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。

    import { reactive, ref } from 'vue';
    interface Mouse { name: string }
    /** 泛型: 普通对象 */
    const mouse = reactive<Mouse>({ name: "jerry" }) // Mouse
    /** 泛型: 含 ref 对象 */
    const mouseDeepRef = reactive<Mouse>({ name: ref('jerry') }) // error:不能将类型“Ref<string>”分配给类型“string
    

    computed

    computed 共有三种方式

  • 类型注解( 不推荐使用, 官方文档也未标出 )
  • import { computed, ComputedRef, ref } from 'vue';
    const num = ref(10);
    /** 自动推导 */
    const double = computed(() => num.value * 2) // ComputedRef<number>
    
    import { computed, ref } from 'vue';
    const num = ref(10);
    /** 泛型 */
    const three = computed<number>(() => num.value * 3) // ComputedRef<number>
    

    类型注解 (不推荐使用)

    官方文档: 不推荐使用 reactive() 的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。

    import { computed, ComputedRef, ref } from 'vue';
    const num = ref(10);
    /** 类型注解*/
    const four: ComputedRef<number> = computed(() => num.value * 4) // ComputedRef<number>
    const fourError: number = computed(() => num.value * 4) // error: 不能将类型“ComputedRef<number>”分配给类型“number”。
    

    provide

    provide 比较特殊,他是无法做到自动推导的,想要让 provide 提供的值被注入时能被准确的标注类型,只能使用 Vue提供的 InjectionKey接口, 它是一个继承自 Symbol 的泛型类型。

    未使用 InjectionKey

    // 提供者代码
    import { provide } from "vue";
    let tom = { name: "tom" };
    provide("cat", tom); // { name: string; }
    // 消费者代码
    import { inject } from "vue";
    const tom = inject("cat"); // unknown
    // 如果明确知道注入进来的类型的话,也可以使用泛型给类型做注解
    const tomCorrect = inject<{ name: 'string' }>("cat")  // { name: string; } || undefined
    // 注意:此处传入了错误的泛型,但是是不会报错的,产生了一个坑
    const tomError = inject<string>("cat") // string | undefine
    

    使用 InjectionKey

    // 存储 provide key 的文件代码
    import { InjectionKey } from "vue";
    export const cat = Symbol() as InjectionKey<{ name: string }>;
    // 提供者代码
    import { cat } from "./keys.ts"; 
    import { provide } from "vue";
    let tom = { name: "tom" };
    provide(cat, tom); 	// InjectionKey<{ name: string; }>
    // 消费者代码
    import { inject } from "vue";
    import { cat } from "./keys.ts";
    const tom = inject(cat); // { name: string; } | undefined
    

    看完代码会发现,以上代码块里,能识别出类型的都会带着 undefined 类型,要去除掉它的话,只需要在注入方传递默认值或类型断言

    // 提供者代码
    import { provide } from "vue";
    let tom = { name: "tom" };
    provide("cat", tom); // { name: string; }
    // 消费者代码
    import { inject } from "vue";
    // 类型断言
    const tom = inject("cat") as { name: string; }; // { name: string; }
    // 默认值
    const tomWithDefaultVal = inject<{ name: string }>("cat", { name: "tom" }); // { name: string; }
    

    event

    原生的事件对象类型会被标注为 any,需要使用 Event 来显式的标注它的类型。且需要显式地强制转换 event 上的 property

    <script setup lang="ts">
    function handleChange(event: Event) {
      console.log((event.target as HTMLInputElement).value)
    </script>
    <template>
      <input type="text" @change="handleChange" />
    </template>
    

    模板 ref

    模板 ref 需要通过一个显式指定的泛型参数和一个初始值 null 来创建

    <script setup lang="ts">
    import { ref, onMounted } from 'vue'
    const el = ref<HTMLInputElement | null>(null)
    onMounted(() => {
      el.value?.focus()
    </script>
    <template>
      <input ref="el" />
    </template>
    

    组件模板 ref

    首先需要通过 typeof 得到其类型,再使用 TypeScript 内置的 InstanceType 工具类型

    import MyModal from './MyModal.vue'
    const modal = ref<InstanceType<typeof MyModal> | null>(null)
    const openModal = () => {
      modal.value?.open()
    </script>
    复制代码
    分类:
    前端
  •