Vue3 + TypeScript最佳实践指南
引言
Vue3和TypeScript的结合为前端开发带来了类型安全和更好的开发体验。本文将分享在实际项目中总结的最佳实践。
项目初始化
使用Vite创建项目
bash
# 创建Vue3 + TypeScript项目
npm create vite@latest my-vue-app -- --template vue-ts
# 安装依赖
cd my-vue-app
npm install
类型定义最佳实践
组件Props类型定义
typescript
// 定义接口
interface User {
id: number
name: string
email: string
avatar?: string
}
// Props类型定义
interface Props {
user: User
loading?: boolean
onUpdate?: (user: User) => void
}
// 在组件中使用
defineProps<Props>()
响应式数据类型定义
typescript
import { ref, reactive } from 'vue'
// 使用ref
const count = ref<number>(0)
const user = ref<User | null>(null)
// 使用reactive
interface State {
users: User[]
loading: boolean
error: Error | null
}
const state = reactive<State>({
users: [],
loading: false,
error: null
})
Composition API最佳实践
可复用的组合式函数
typescript
// useUser.ts
import { ref, computed } from 'vue'
import type { Ref } from 'vue'
export function useUser(initialId: number) {
const userId = ref(initialId)
const user: Ref<User | null> = ref(null)
const loading = ref(false)
const error = ref<Error | null>(null)
const fullName = computed(() => {
return user.value ? `${user.value.firstName} ${user.value.lastName}` : ''
})
async function fetchUser() {
loading.value = true
try {
const response = await fetch(`/api/users/${userId.value}`)
user.value = await response.json()
} catch (e) {
error.value = e as Error
} finally {
loading.value = false
}
}
return {
user,
loading,
error,
fullName,
fetchUser
}
}
类型安全的事件处理
typescript
// 定义事件类型
interface UpdateUserEvent {
userId: number
data: Partial<User>
}
// 在组件中使用
const emit = defineEmits<{
(e: 'update', event: UpdateUserEvent): void
(e: 'delete', userId: number): void
}>()
// 事件处理函数
function handleUpdate(data: Partial<User>) {
emit('update', {
userId: props.user.id,
data
})
}
路由类型定义
typescript
// router/types.ts
import 'vue-router'
declare module 'vue-router' {
interface RouteMeta {
requiresAuth: boolean
roles?: string[]
}
}
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/dashboard',
component: () => import('../views/Dashboard.vue'),
meta: {
requiresAuth: true,
roles: ['admin']
}
}
]
Vuex/Pinia状态管理
Pinia示例
typescript
import { defineStore } from 'pinia'
interface State {
user: User | null
token: string | null
}
export const useAuthStore = defineStore('auth', {
state: (): State => ({
user: null,
token: null
}),
getters: {
isAuthenticated(): boolean {
return !!this.token
}
},
actions: {
async login(credentials: { email: string; password: string }) {
// 实现登录逻辑
}
}
})
单元测试
typescript
import { mount } from '@vue/test-utils'
import { describe, it, expect } from 'vitest'
import UserProfile from './UserProfile.vue'
describe('UserProfile', () => {
it('renders user information correctly', () => {
const user: User = {
id: 1,
name: 'John Doe',
email: '[email protected]'
}
const wrapper = mount(UserProfile, {
props: { user }
})
expect(wrapper.text()).toContain(user.name)
expect(wrapper.text()).toContain(user.email)
})
})
性能优化
- 组件懒加载
typescript
const UserProfile = defineAsyncComponent(() =>
import('./components/UserProfile.vue')
)
- 计算属性缓存
typescript
const expensiveComputation = computed(() => {
// 复杂计算逻辑
})
- 大列表虚拟滚动
typescript
import { VirtualList } from '@vueuse/components'
<VirtualList
:items="items"
:item-height="50"
>
<template #default="{ item }">
<div class="item">{{ item.name }}</div>
</template>
</VirtualList>
最佳实践总结
- 始终为组件的props和emits定义类型
- 使用组合式函数封装可复用的逻辑
- 利用TypeScript的类型推导
- 合理使用泛型提高代码复用性
- 保持一致的代码风格和命名规范
常见问题解决
- 类型声明文件管理
- 第三方库类型定义
- 类型断言的正确使用
- 泛型约束的应用
参考资料
- Vue3官方文档
- TypeScript官方文档
- Vite官方文档
- Vue Router文档
- Pinia文档