前言
本文参考 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
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
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>