好的,各位观众老爷们,今天咱就来聊聊 Vue 3 的 Composition API 和 TypeScript 这对“神雕侠侣”是如何珠联璧合、所向披靡的。保证各位听完之后,功力大增,写代码也能如丝般顺滑,Bug 见了都绕着走!
开场白:TypeScript,你的代码守护神
首先,咱们得明确一下,TypeScript 是个啥玩意儿?简单来说,它就是 JavaScript 的“超能力”版本,给 JavaScript 加上了静态类型检查。这意味着,你在写代码的时候,TypeScript 就能帮你揪出很多潜在的类型错误,防患于未然。想象一下,你辛辛苦苦写了几个小时的代码,结果运行时才发现一个简单的类型错误,那感觉,简直比吃了苍蝇还难受!有了 TypeScript,就能帮你提前避免这种悲剧。
Composition API:Vue 3 的新姿势
Vue 3 的 Composition API 则是另一种强大的武器,它是一种新的组织 Vue 组件逻辑的方式。传统的 Options API (data、methods、computed、watch 等) 在组件变得复杂时,很容易让代码变得难以阅读和维护。Composition API 则允许你将相关的逻辑提取到独立的函数中,然后在组件中组合使用,让代码更清晰、更易于复用。
Composition API + TypeScript:天作之合
现在,把 Composition API 和 TypeScript 放在一起,就如同给关羽配上了赤兔马,给吕布配上了方天画戟,简直是如虎添翼!TypeScript 强大的类型推断能力,可以让你在使用 Composition API 时,享受到更安全、更智能的开发体验。
1. 类型推断:让 TypeScript 猜猜猜
TypeScript 最厉害的本事之一就是类型推断。这意味着,很多时候你不需要显式地声明变量的类型,TypeScript 也能根据变量的值,自动推断出它的类型。这在 Composition API 中尤其有用,因为很多时候,你的状态都是通过 ref 或 reactive 创建的。
import { ref } from 'vue';
// TypeScript 会自动推断 count 的类型为 number
const count = ref(0);
// TypeScript 会自动推断 message 的类型为 string
const message = ref('Hello, Vue 3!');
// TypeScript 会自动推断 person 的类型为 { name: string; age: number; }
const person = ref({
name: '张三',
age: 30,
});
看到了吗?你只需要用 ref 创建变量,TypeScript 就能自动帮你推断出它们的类型,省去了很多麻烦。
2. 显式类型声明:当 TypeScript 猜不准的时候
当然,TypeScript 也有猜不准的时候。比如,你希望 ref 存储的值的类型比较复杂,或者你希望给 ref 一个初始值,但又不想让 TypeScript 自动推断类型,这时就需要显式地声明类型。
import { ref, Ref } from 'vue';
// 显式声明 count 的类型为 number | null
const count: Ref<number | null> = ref(null);
// 显式声明 message 的类型为 string,并给一个初始值
const message: Ref<string> = ref('');
// 显式声明 person 的类型为 { name: string; age: number; } | null
interface Person {
name: string;
age: number;
}
const person: Ref<Person | null> = ref(null);
//如果你要使用泛型默认值,写法如下
const myRef = ref<string>('initialValue');
这里我们使用了 Ref<T> 类型,它是 Vue 提供的用于表示 ref 类型的泛型接口。T 就是 ref 存储的值的类型。
3. reactive:让你的对象拥有“超能力”
reactive 可以将一个普通的 JavaScript 对象转换为响应式对象,这意味着当对象的值发生变化时,Vue 会自动更新相关的视图。TypeScript 也能很好地支持 reactive,为你提供类型检查。
import { reactive } from 'vue';
interface User {
name: string;
age: number;
}
const user = reactive<User>({
name: '李四',
age: 25,
});
// TypeScript 会检查你是否访问了 user 对象上不存在的属性
// user.address = '北京市'; // 报错:Property 'address' does not exist on type 'User'.
// TypeScript 会检查你是否给 user 对象的属性赋了错误类型的值
// user.age = '30'; // 报错:Type 'string' is not assignable to type 'number'.
通过显式声明 reactive 的类型,TypeScript 就能帮你检查你是否访问了对象上不存在的属性,或者是否给属性赋了错误类型的值。
4. computed:计算属性,TypeScript 也算得清楚
computed 可以根据其他响应式状态计算出一个新的值,并且只有当依赖的状态发生变化时,才会重新计算。TypeScript 也能很好地支持 computed,为你提供类型检查。
import { ref, computed } from 'vue';
const firstName = ref('王');
const lastName = ref('五');
const fullName = computed(() => {
return firstName.value + ' ' + lastName.value;
});
// TypeScript 会自动推断 fullName 的类型为 string
console.log(fullName.value);
// 你也可以显式声明 computed 的类型
import { ComputedRef } from 'vue';
const age = ref(20);
const isAdult: ComputedRef<boolean> = computed(() => {
return age.value >= 18;
});
TypeScript 会自动推断 computed 的返回值类型,或者你也可以显式声明 computed 的类型,让 TypeScript 帮你检查你是否返回了错误类型的值。
5. watch:监听状态变化,TypeScript 也时刻关注
watch 可以监听一个或多个响应式状态的变化,并在状态发生变化时执行回调函数。TypeScript 也能很好地支持 watch,为你提供类型检查。
import { ref, watch } from 'vue';
const count = ref(0);
watch(
count,
(newValue, oldValue) => {
// TypeScript 会自动推断 newValue 和 oldValue 的类型为 number
console.log(`count 的值从 ${oldValue} 变成了 ${newValue}`);
},
{ immediate: true } // 立即执行回调函数
);
// 监听多个状态
import { reactive, watch } from 'vue';
const state = reactive({
name: '赵六',
age: 35,
});
watch(
() => [state.name, state.age],
([newName, newAge], [oldName, oldAge]) => {
// TypeScript 会自动推断 newName 和 oldName 的类型为 string
// TypeScript 会自动推断 newAge 和 oldAge 的类型为 number
console.log(`name 从 ${oldName} 变成了 ${newName}`);
console.log(`age 从 ${oldAge} 变成了 ${newAge}`);
}
);
TypeScript 会自动推断 watch 回调函数中 newValue 和 oldValue 的类型,或者你也可以使用泛型来显式声明类型。
6. provide 和 inject:跨组件传递数据,TypeScript 也能保驾护航
provide 和 inject 可以让你在祖先组件中提供数据,然后在后代组件中注入数据,而无需通过 props 逐层传递。TypeScript 也能很好地支持 provide 和 inject,为你提供类型检查。
// 在祖先组件中
import { provide, ref } from 'vue';
interface Theme {
color: string;
fontSize: string;
}
const theme = ref<Theme>({
color: 'red',
fontSize: '16px',
});
provide('theme', theme);
// 在后代组件中
import { inject, Ref, onMounted } from 'vue';
const theme = inject<Ref<Theme>>('theme');
onMounted(() => {
if (theme) {
console.log(theme.value.color);
}
});
在使用 provide 和 inject 时,你需要使用泛型来显式声明提供和注入的类型,这样 TypeScript 就能帮你检查你是否提供了正确类型的数据,以及你是否注入了正确类型的数据。
7. 自定义 Hook:让你的代码更 DRY
自定义 Hook 是 Composition API 的一个重要特性,它可以让你将组件的逻辑提取到独立的函数中,然后在多个组件中复用。TypeScript 也能很好地支持自定义 Hook,为你提供类型检查。
import { ref, onMounted, onUnmounted } from 'vue';
// 自定义 Hook:useMousePosition
interface MousePosition {
x: number;
y: number;
}
function useMousePosition(): Ref<MousePosition> {
const mousePosition = ref<MousePosition>({
x: 0,
y: 0,
});
const updateMousePosition = (event: MouseEvent) => {
mousePosition.value = {
x: event.clientX,
y: event.clientY,
};
};
onMounted(() => {
window.addEventListener('mousemove', updateMousePosition);
});
onUnmounted(() => {
window.removeEventListener('mousemove', updateMousePosition);
});
return mousePosition;
}
// 在组件中使用自定义 Hook
import { defineComponent } from 'vue';
export default defineComponent({
setup() {
const mousePosition = useMousePosition();
return {
mousePosition,
};
},
});
在使用自定义 Hook 时,你需要定义 Hook 的返回值类型,这样 TypeScript 就能帮你检查你是否返回了正确类型的值。
8. 组件 Props 的类型定义
在 Vue 组件中,Props 用于接收父组件传递的数据。使用 TypeScript,我们可以为 Props 定义类型,确保父组件传递的数据类型正确,增强组件的稳定性和可维护性。
import { defineComponent } from 'vue';
interface Props {
name: string;
age?: number; // 可选属性
isStudent: boolean;
}
export default defineComponent({
props: {
name: {
type: String,
required: true,
},
age: {
type: Number,
default: 18,
},
isStudent: {
type: Boolean,
default: false,
},
},
setup(props: Props) {
// 在 setup 函数中,props 已经有了类型定义
console.log(props.name);
console.log(props.age);
console.log(props.isStudent);
return {};
},
});
在这个例子中,我们定义了一个 Props 接口,用于描述组件的 Props。然后在 defineComponent 中,我们使用 props 选项来声明组件的 Props,并为每个 Prop 指定类型、是否必填、默认值等。在 setup 函数中,我们可以直接使用 props 对象,它已经有了类型定义,TypeScript 会帮我们检查是否访问了不存在的属性,或者是否给属性赋了错误类型的值。
9. 组件 Emits 的类型定义
Emits 用于组件向父组件传递事件。使用 TypeScript,我们可以为 Emits 定义类型,确保组件传递的事件名称和参数类型正确,增强组件的交互性和可维护性。
import { defineComponent } from 'vue';
interface Emits {
(e: 'update:name', name: string): void;
(e: 'update:age', age: number): void;
}
export default defineComponent({
emits: ['update:name', 'update:age'],
setup(props, { emit }) {
const updateName = (name: string) => {
emit('update:name', name);
};
const updateAge = (age: number) => {
emit('update:age', age);
};
return {
updateName,
updateAge,
};
},
});
在这个例子中,我们定义了一个 Emits 接口,用于描述组件的 Emits。然后在 defineComponent 中,我们使用 emits 选项来声明组件的 Emits,并为每个 Emit 指定事件名称和参数类型。在 setup 函数中,我们可以使用 emit 函数来触发事件,TypeScript 会帮我们检查是否触发了不存在的事件,或者是否给事件传递了错误类型的参数。
10. 与第三方库的集成
TypeScript 可以很好地与各种第三方库集成,为你提供类型检查。很多流行的第三方库都提供了 TypeScript 的类型定义文件(.d.ts),你可以直接安装这些类型定义文件,让 TypeScript 能够理解这些库的 API。
例如,如果你使用 Axios 发起 HTTP 请求,你可以安装 @types/axios 包,让 TypeScript 能够理解 Axios 的 API,并为你提供类型检查。
npm install @types/axios --save-dev
安装完成后,你就可以在代码中使用 Axios,TypeScript 会自动为你提供类型检查。
import axios from 'axios';
interface Todo {
id: number;
title: string;
completed: boolean;
}
axios.get<Todo[]>('https://jsonplaceholder.typicode.com/todos')
.then(response => {
const todos: Todo[] = response.data;
console.log(todos);
});
在这个例子中,我们使用 axios.get<Todo[]> 来指定请求返回的数据类型为 Todo[],TypeScript 会帮我们检查 response.data 是否符合 Todo[] 类型。
总结:Composition API + TypeScript,让你的代码更上一层楼
总而言之,Vue 3 的 Composition API 和 TypeScript 的结合,可以让你写出更安全、更智能、更易于维护的代码。TypeScript 强大的类型推断能力,可以帮你提前发现潜在的类型错误,而 Composition API 则可以让你更好地组织组件的逻辑,让代码更清晰、更易于复用。
| 特性 | 描述 | 示例 |
|---|---|---|
| 类型推断 | TypeScript 自动推断变量类型 | const count = ref(0); // count 的类型为 number |
| 显式类型声明 | 显式声明变量类型 | const message: Ref<string> = ref(''); |
reactive |
将普通对象转换为响应式对象,并进行类型检查 | const user = reactive<User>({ name: '李四', age: 25 }); |
computed |
计算属性,并进行类型检查 | const fullName = computed(() => firstName.value + ' ' + lastName.value); // fullName 的类型为 string |
watch |
监听状态变化,并进行类型检查 | watch(count, (newValue, oldValue) => { ... }); // newValue 和 oldValue 的类型为 number |
provide/inject |
跨组件传递数据,并进行类型检查 | provide('theme', theme); const theme = inject<Ref<Theme>>('theme'); |
| 自定义 Hook | 将组件逻辑提取到独立的函数中,并进行类型检查 | function useMousePosition(): Ref<MousePosition> { ... } |
| Props 类型定义 | 为组件 Props 定义类型,确保父组件传递的数据类型正确 | props: { name: { type: String, required: true } } |
| Emits 类型定义 | 为组件 Emits 定义类型,确保组件传递的事件名称和参数类型正确 | emits: ['update:name', 'update:age'] |
| 第三方库集成 | 与第三方库集成,提供类型检查 | import axios from 'axios'; axios.get<Todo[]>('...'); |
希望今天的讲解对大家有所帮助。记住,熟练掌握 Composition API 和 TypeScript,你就能在 Vue 3 的世界里自由驰骋,写出高质量的代码! 下课!