好的,各位观众老爷们,今天咱就来聊聊 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 的世界里自由驰骋,写出高质量的代码! 下课!