TypeScript 应用
创建 vue-ts 项目
创建一个基于 ts 的 vue 项目,来学习 ts 语法
bash
# npm 6.x
npm create vite@latest my-vue-ts-app --template vue-ts
# npm 7+, extra double-dash is needed:
npm create vite@latest my-vue-ts-app -- --template vue-ts
# yarn
yarn create vite my-vue-ts-app --template vue-ts
# pnpm
pnpm create vite my-vue-ts-app --template vue-ts
在基于 vite 的项目中可以直接验证 ts 代码结果,因为已经配置好了 ts 环境。
① TypeScript与Vue
TIP
typescript 配合 Vue3 composition-api 使用
https://staging-cn.vuejs.org/guide/typescript/composition-api.html
前提:script 加上 lang="ts"
才能写ts代码
vue
<script setup lang="ts"></script>
defineProps的TS写法
- defineProps 的基本使用:
ts
const props = defineProps({
money: {
type: Number,
required: true
},
car: {
type: String,
required: false,
default: '宝马车'
}
})
console.log(props.money) // number
console.log(props.car) // string | undefined
- defineProps 通过泛型参数来定义 props 的类型通常更直接:
ts
// 👎
const props = defineProps<{
money: number
car?: string
}>()
// 👍 推荐
interface Props {
money: number
car?: string
}
const props = defineProps<Props>()
- 如果需要给 props 设置默认值,👎需要使用
withDefaults
函数:
ts
const props = withDefaults(defineProps<Props>(),{
car: '宝马车'
})
- 上面写法太笨拙,可以使用 响应式语法糖 解构 + defineProps 就行:
ts
const { money, car = "宝马车" } = defineProps<Props>();
注意:目前需要 显式地选择开启 ,因为它还是一个实验性特性。
ts
// vite.config.ts
export default defineConfig({
plugins: [
vue({
reactivityTransform: true,
}),
],
});
扩展-了解TS中函数的调用签名
ts
// 普通函数的写法
type MyFn = (name: "changeNum" | "changeStr", value: number | string ) => void
const fn: MyFn = (name, value) => {};
fn("changeNum", "1");
fn("changeNum", 1);
fn("changeStr", "1");
扩展:TS语法 调用签名
JS中万物皆对象, 函数也属于对象类型
ts
interface MyFn {
(name: "changeNum", value: number): void;
(name: "changeStr", value: string): void;
}
const fn: MyFn = (name, value) => {};
fn("changeNum", "1"); // ❌会报错
fn("changeMsg", 1); // ❌会报错
fn("changeNum", "1"); // ✅
fn("changeMsg", 1); // ✅
defineEmits的TS写法
- defineEmits 的JS用法:
ts
const emit = defineEmits(['changeMoney', 'changeCar'])
- defineEmits 通过泛型参数来定义,可以实现更细粒度的校验:
ts
// 👎·
const emit = defineEmits<{
(e: 'changeMoney', money: number): void
(e: 'changeCar', car: string): void
}>()
// 👍·
interface Emits {
(e: 'changeMoney', money: number): void
(e: 'changeCar', car: string): void
}
const emit = defineEmits<Emits>()
ref的TS写法
ref()
会隐式的依据数据推导类型
- 如果是简单类型,推荐使用类型推导:
ts
// const money = ref<number>(10)
const money = ref(10)
- 如果是复杂类型,推荐指定泛型:
ts
type Todo = {
id: number
name: string
done: boolean
}
const list = ref<Todo[]>([])
setTimeout(() => {
list.value = [
{ id: 1, name: '吃饭', done: false },
{ id: 2, name: '睡觉', done: true }
]
}, 1000)
复杂数据一般是后台返回数据,默认值是空,无法进行类型推导。
小结:
- 简单数据类型,省略泛型
- 复杂数据类型,完整写法
reactive的TS写法
reactive()
也会隐式的依据数据推导类型
- 默认值属性是固定的,推荐使用类型推导:
ts
// 推导得到的类型:{ title: string }
const book = reactive({ title: 'Vue3 在线医疗' })
- 根据默认值推导不出我们需要的类型,推荐使用接口或者类型别名给变量指定类型:
ts
// 我们想要的类型:{ title: string, year?: number }
type Book = {
title: string
year?: number
}
const book: Book = reactive({ title: 'Vue3 在线医疗' })
book.year = 2022
- 官方:不推荐使用
reactive()
的泛型参数,因为底层和ref()
实现不一样。
computed和TS
computed()
会从其计算函数的返回值上推导出类型:
ts
import { ref, computed } from 'vue'
const count = ref(100);
const doubleCount = computed(() => count.value * 2);
- 可以通过泛型参数显式指定类型:
ts
const doubleMoney = computed<string>(() => (count.value * 2).toFixed(2));
事件处理与TS
- 不加类型,event默认是any,类型不安全:
vue
<script setup lang="ts">
// 提示:参数“event”隐式具有“any”类型。
const handleChange = (event) => {
console.log(event.target.value)
}
</script>
<template>
<input type="text" @change="handleChange" />
</template>
- 处理类型:
ts
// `event` 隐式地标注为 `any` 类型,如何指定:event 类型?
// 1. @change="handleChange($event)"" 查看$event类型
// 2. 鼠标悬停事件 @change 查看类型
const handleChange = (event: Event) => {
// `event.target` 是 `EventTarget | null` 类型,如何指定具体类型?
// document.querySelector('input') 查看返回值类型
console.log((event.target as HTMLInputElement).value)
}
Template Ref与TS
模板 ref
需要通过一个显式指定的泛型参数,建议默认值 null
vue
<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>
- 注意为了严格的类型安全,有必要在访问
el.value
时使用可选链或类型守卫。 - 这是因为直到组件被挂载前,这个
ref
的值都是初始的null
,并且在由于v-if
的行为将引用的元素卸载时也可以被设置为null
。
非空断言
处理类型可能是 null 或 undefined 的值,下面的属性或函数的访问赋值:
- 可选链
vue
<script setup lang="ts">
import { onMounted, ref } from 'vue';
const input = ref< HTMLInputElement | null >(null)
onMounted(()=>{
// 可选链:只能访问,不能赋值
// input.value?.value = "123" ❌
console.log(input.value?.value);
})
</script>
<template>
<div>App组件</div>
<input type="text" ref="input" value="abc">
</template>
- 逻辑判断
ts
// 逻辑判断, 类型守卫
if (input.value) {
console.log(input.value.value)
input.value.value = '123'
}
- 非空断言
ts
// 一定要确定不为空!!!
console.log(input.value!.value)
input.value!.value = '123'