Skip to content

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)
  })
})

性能优化

  1. 组件懒加载
typescript
const UserProfile = defineAsyncComponent(() =>
  import('./components/UserProfile.vue')
)
  1. 计算属性缓存
typescript
const expensiveComputation = computed(() => {
  // 复杂计算逻辑
})
  1. 大列表虚拟滚动
typescript
import { VirtualList } from '@vueuse/components'

<VirtualList
  :items="items"
  :item-height="50"
>
  <template #default="{ item }">
    <div class="item">{{ item.name }}</div>
  </template>
</VirtualList>

最佳实践总结

  1. 始终为组件的props和emits定义类型
  2. 使用组合式函数封装可复用的逻辑
  3. 利用TypeScript的类型推导
  4. 合理使用泛型提高代码复用性
  5. 保持一致的代码风格和命名规范

常见问题解决

  1. 类型声明文件管理
  2. 第三方库类型定义
  3. 类型断言的正确使用
  4. 泛型约束的应用

参考资料

  1. Vue3官方文档
  2. TypeScript官方文档
  3. Vite官方文档
  4. Vue Router文档
  5. Pinia文档

幸运的人用童年治愈一生,不幸的人用一生治愈童年 —— 强爸