Vue3 TypeScript 支持详解
概述
Vue3 是用 TypeScript 重写的,提供了完整的类型支持。这让你可以在开发时获得更好的代码提示和错误检查。
类型系统架构
基本类型支持
1. 组件 Props 类型定义
// 使用 defineProps 和泛型
interface UserProps {
id: number;
name: string;
email?: string;
age: number;
isActive: boolean;
}
export default defineComponent({
props: defineProps<UserProps>(),
setup(props) {
// props 有完整的类型推导
console.log(props.id); // number
console.log(props.name); // string
console.log(props.email); // string | undefined
return {};
},
});
2. 响应式数据类型
import { ref, reactive, computed } from "vue";
export default defineComponent({
setup() {
// ref 类型推导
const count = ref(0); // Ref<number>
const message = ref("Hello"); // Ref<string>
const isVisible = ref(true); // Ref<boolean>
// 明确指定类型
const user = ref<User | null>(null);
const items = ref<string[]>([]);
// reactive 类型推导
const state = reactive({
name: "张三",
age: 25,
hobbies: ["读书", "游泳"],
});
// 使用接口定义状态类型
interface AppState {
user: User | null;
loading: boolean;
error: string | null;
}
const appState = reactive<AppState>({
user: null,
loading: false,
error: null,
});
// computed 类型推导
const doubleCount = computed(() => count.value * 2); // ComputedRef<number>
const userDisplayName = computed(() => {
return user.value ? user.value.name : "未知用户";
}); // ComputedRef<string>
return {
count,
message,
isVisible,
user,
items,
state,
appState,
doubleCount,
userDisplayName,
};
},
});
3. 事件类型定义
export default defineComponent({
emits: defineEmits<{
update: [value: string];
delete: [id: number];
submit: [data: FormData];
}>(),
setup(props, { emit }) {
const handleUpdate = (value: string) => {
emit("update", value);
};
const handleDelete = (id: number) => {
emit("delete", id);
};
const handleSubmit = (data: FormData) => {
emit("submit", data);
};
return {
handleUpdate,
handleDelete,
handleSubmit,
};
},
});
组合式 API 类型支持
1. setup 函数类型
import { SetupContext } from "vue";
interface Props {
title: string;
count: number;
}
interface Emits {
(e: "update:title", value: string): void;
(e: "update:count", value: number): void;
}
export default defineComponent({
props: defineProps<Props>(),
emits: defineEmits<Emits>(),
setup(props: Props, context: SetupContext<Emits>) {
// props 类型安全
const title = computed(() => props.title.toUpperCase());
// emit 类型安全
const updateTitle = (newTitle: string) => {
context.emit("update:title", newTitle);
};
const updateCount = (newCount: number) => {
context.emit("update:count", newCount);
};
return {
title,
updateTitle,
updateCount,
};
},
});
2. 生命周期钩子类型
import { onMounted, onUnmounted, onBeforeRouteLeave } from "vue";
import { NavigationGuardNext, RouteLocationNormalized } from "vue-router";
export default defineComponent({
setup() {
// 生命周期钩子类型安全
onMounted(() => {
console.log("组件已挂载");
});
onUnmounted(() => {
console.log("组件即将卸载");
});
// 路由守卫类型安全
onBeforeRouteLeave(
(
to: RouteLocationNormalized,
from: RouteLocationNormalized,
next: NavigationGuardNext
) => {
if (hasUnsavedChanges()) {
if (confirm("有未保存的更改,确定要离开吗?")) {
next();
} else {
next(false);
}
} else {
next();
}
}
);
return {};
},
});
3. 组合式函数类型
// useCounter.ts
import { ref, computed, Ref, ComputedRef } from "vue";
interface CounterState {
count: Ref<number>;
doubleCount: ComputedRef<number>;
increment: () => void;
decrement: () => void;
reset: () => void;
}
export function useCounter(initialValue: number = 0): CounterState {
const count = ref(initialValue);
const doubleCount = computed(() => count.value * 2);
const increment = () => {
count.value++;
};
const decrement = () => {
count.value--;
};
const reset = () => {
count.value = initialValue;
};
return {
count,
doubleCount,
increment,
decrement,
reset,
};
}
模板类型支持
1. 模板引用类型
<template>
<div>
<input ref="inputRef" v-model="message" />
<button @click="focusInput">聚焦输入框</button>
</div>
</template>
<script lang="ts">
import { ref, onMounted } from 'vue'
export default defineComponent({
setup() {
const message = ref('')
// 模板引用类型
const inputRef = ref<HTMLInputElement | null>(null)
const focusInput = () => {
if (inputRef.value) {
inputRef.value.focus()
}
}
onMounted(() => {
// 类型安全的 DOM 操作
if (inputRef.value) {
inputRef.value.select()
}
})
return {
message,
inputRef,
focusInput
}
}
})
</script>
2. 事件处理器类型
<template>
<div>
<input
@input="handleInput"
@keydown="handleKeydown"
@focus="handleFocus"
/>
<button @click="handleClick">点击</button>
</div>
</template>
<script lang="ts">
export default defineComponent({
setup() {
const handleInput = (event: Event) => {
const target = event.target as HTMLInputElement
console.log('输入值:', target.value)
}
const handleKeydown = (event: KeyboardEvent) => {
if (event.key === 'Enter') {
console.log('按下了回车键')
}
}
const handleFocus = (event: FocusEvent) => {
console.log('输入框获得焦点')
}
const handleClick = (event: MouseEvent) => {
console.log('按钮被点击:', event.clientX, event.clientY)
}
return {
handleInput,
handleKeydown,
handleFocus,
handleClick
}
}
})
</script>
路由类型支持
1. 路由参数类型
// router/index.ts
import { RouteRecordRaw } from "vue-router";
interface UserRouteParams {
id: string;
}
interface PostRouteParams {
id: string;
slug?: string;
}
const routes: RouteRecordRaw[] = [
{
path: "/user/:id",
name: "User",
component: () => import("../views/User.vue"),
props: true,
},
{
path: "/post/:id/:slug?",
name: "Post",
component: () => import("../views/Post.vue"),
props: true,
},
];
export default routes;
// views/User.vue
interface UserProps {
id: string;
}
export default defineComponent({
props: defineProps<UserProps>(),
setup(props) {
const route = useRoute();
// 路由参数类型安全
const userId = computed(() => props.id);
const query = computed(() => route.query);
return {
userId,
query,
};
},
});
2. 路由元信息类型
// 扩展路由元信息类型
declare module "vue-router" {
interface RouteMeta {
requiresAuth: boolean;
requiresAdmin?: boolean;
title?: string;
keepAlive?: boolean;
}
}
const routes: RouteRecordRaw[] = [
{
path: "/admin",
name: "Admin",
component: () => import("../views/Admin.vue"),
meta: {
requiresAuth: true,
requiresAdmin: true,
title: "管理后台",
keepAlive: true,
},
},
];
状态管理类型支持
1. Pinia Store 类型
// stores/user.ts
import { defineStore } from "pinia";
interface User {
id: number;
name: string;
email: string;
role: "admin" | "user";
}
interface UserState {
users: User[];
currentUser: User | null;
loading: boolean;
error: string | null;
}
export const useUserStore = defineStore("user", {
state: (): UserState => ({
users: [],
currentUser: null,
loading: false,
error: null,
}),
getters: {
totalUsers: (state): number => state.users.length,
adminUsers: (state): User[] => {
return state.users.filter((user) => user.role === "admin");
},
getUserById:
(state) =>
(id: number): User | undefined => {
return state.users.find((user) => user.id === id);
},
},
actions: {
async fetchUsers(): Promise<void> {
this.loading = true;
this.error = null;
try {
const response = await fetch("/api/users");
const data: User[] = await response.json();
this.users = data;
} catch (error) {
this.error = error instanceof Error ? error.message : "未知错误";
} finally {
this.loading = false;
}
},
setCurrentUser(user: User): void {
this.currentUser = user;
},
clearCurrentUser(): void {
this.currentUser = null;
},
},
});
2. 在组件中使用 Store
<template>
<div>
<div v-if="userStore.loading">加载中...</div>
<div v-else-if="userStore.error">错误: {{ userStore.error }}</div>
<div v-else>
<h2>用户列表 ({{ userStore.totalUsers }})</h2>
<ul>
<li v-for="user in userStore.users" :key="user.id">
{{ user.name }} - {{ user.role }}
</li>
</ul>
</div>
</div>
</template>
<script lang="ts">
import { useUserStore } from '@/stores/user'
export default defineComponent({
setup() {
const userStore = useUserStore()
// 在组件挂载时获取用户数据
onMounted(() => {
userStore.fetchUsers()
})
return {
userStore
}
}
})
</script>
工具类型
1. Vue 内置工具类型
import { PropType, ComponentPublicInstance, VNode } from "vue";
// PropType - 为 props 提供类型
interface ComplexObject {
name: string;
value: number;
}
export default defineComponent({
props: {
complex: {
type: Object as PropType<ComplexObject>,
required: true,
},
},
});
// ComponentPublicInstance - 组件实例类型
const componentRef = ref<ComponentPublicInstance | null>(null);
// VNode - 虚拟节点类型
const renderVNode = (): VNode => {
return h("div", "Hello World");
};
2. 自定义工具类型
// 提取组件 props 类型
type ExtractProps<T> = T extends { props: infer P } ? P : never;
// 提取组件 emits 类型
type ExtractEmits<T> = T extends { emits: infer E } ? E : never;
// 提取 ref 类型
type UnwrapRef<T> = T extends Ref<infer U> ? U : T;
// 提取数组元素类型
type ArrayElement<T> = T extends Array<infer U> ? U : never;
// 使用示例
interface UserComponentProps {
user: User;
onUpdate: (user: User) => void;
}
type UserProps = ExtractProps<UserComponentProps>; // { user: User, onUpdate: (user: User) => void }
type UserRef = UnwrapRef<Ref<User>>; // User
type UserArrayElement = ArrayElement<User[]>; // User
类型声明文件
1. 全局类型声明
// types/global.d.ts
declare global {
interface Window {
__APP_VERSION__: string;
__BUILD_TIME__: string;
}
interface Document {
title: string;
}
// 全局工具函数类型
function formatDate(date: Date): string;
function formatCurrency(amount: number): string;
}
export {};
2. 模块类型声明
// types/modules.d.ts
declare module "*.vue" {
import { DefineComponent } from "vue";
const component: DefineComponent<{}, {}, any>;
export default component;
}
declare module "*.svg" {
const content: string;
export default content;
}
declare module "*.png" {
const content: string;
export default content;
}
最佳实践
1. 类型定义原则
- 优先使用接口定义对象类型
- 使用联合类型表示可选值
- 合理使用泛型提高代码复用性
- 避免使用
any类型
2. 组件类型设计
// 使用接口定义组件 props
interface ButtonProps {
type?: "primary" | "secondary" | "danger";
size?: "small" | "medium" | "large";
disabled?: boolean;
loading?: boolean;
onClick?: (event: MouseEvent) => void;
}
// 使用接口定义组件 emits
interface ButtonEmits {
(e: "click", event: MouseEvent): void;
(e: "focus", event: FocusEvent): void;
}
export default defineComponent({
props: defineProps<ButtonProps>(),
emits: defineEmits<ButtonEmits>(),
setup(props, { emit }) {
const handleClick = (event: MouseEvent) => {
if (!props.disabled && !props.loading) {
emit("click", event);
}
};
return {
handleClick,
};
},
});
3. 错误处理类型
// 定义错误类型
interface ApiError {
code: number;
message: string;
details?: string;
}
// 定义 API 响应类型
interface ApiResponse<T> {
success: boolean;
data?: T;
error?: ApiError;
}
// 使用类型安全的错误处理
async function fetchUser(id: number): Promise<User> {
try {
const response = await fetch(`/api/users/${id}`);
const result: ApiResponse<User> = await response.json();
if (result.success && result.data) {
return result.data;
} else {
throw new Error(result.error?.message || "获取用户失败");
}
} catch (error) {
if (error instanceof Error) {
throw error;
} else {
throw new Error("未知错误");
}
}
}
总结
Vue3 的 TypeScript 支持为开发者提供了完整的类型安全保障。通过合理使用类型定义、接口、泛型等 TypeScript 特性,可以构建出类型安全、易于维护的 Vue 应用。掌握这些类型系统的使用方法,将显著提升开发体验和代码质量。
