活动公告

系统通知
05-18 21:22
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

Vue3搭配TypeScript构建现代化前端项目的最佳实践与技巧从项目初始化到部署上线全流程解析实战经验分享

SunJu_FaceMall

3万

主题

2860

科技点

3万

积分

白金月票

碾压王

积分
32872

塔罗立华奏

<font color=白金月票" /> 发表于 2025-9-1 13:40:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

Vue3作为当前最流行的前端框架之一,其组合式API(Composition API)带来了更好的逻辑复用和类型推断。而TypeScript作为JavaScript的超集,提供了静态类型检查,能够显著提高代码质量和开发效率。将Vue3与TypeScript结合使用,可以构建出类型安全、易于维护且高性能的现代化前端应用。

本文将详细介绍从项目初始化到部署上线的完整流程,分享实战中的最佳实践和技巧,帮助开发者充分利用Vue3和TypeScript的优势,构建高质量的现代化前端项目。

项目初始化

使用Vite创建项目

Vite是新一代前端构建工具,由Vue作者尤雨溪开发,提供了极速的开发体验。使用Vite创建Vue3+TypeScript项目非常简单:
  1. # 使用npm创建项目
  2. npm create vite@latest my-vue-app -- --template vue-ts
  3. # 使用yarn创建项目
  4. yarn create vite my-vue-app --template vue-ts
  5. # 使用pnpm创建项目
  6. pnpm create vite my-vue-app --template vue-ts
复制代码

使用Vue CLI创建项目

如果你更习惯使用Vue CLI,也可以通过以下命令创建项目:
  1. # 安装Vue CLI
  2. npm install -g @vue/cli
  3. # 创建项目
  4. vue create my-vue-app
  5. # 在创建过程中选择"Manually select features"
  6. # 然后选择TypeScript、Vue Router、Pinia等特性
复制代码

项目初始化后的调整

创建项目后,我们需要进行一些基础配置:

1. 安装必要的依赖:
  1. # 安装开发依赖
  2. npm install -D sass eslint prettier @typescript-eslint/parser @typescript-eslint/eslint-plugin
  3. # 安装生产依赖
  4. npm install axios vue-router@4 pinia @vueuse/core
复制代码

1. 创建基础目录结构:
  1. mkdir -p src/{components,views,stores,router,utils,api,types,assets}
复制代码

项目结构设计

一个良好的项目结构能够提高代码的可维护性和可读性。以下是推荐的Vue3+TypeScript项目结构:
  1. my-vue-app/
  2. ├── public/              # 静态资源
  3. ├── src/
  4. │   ├── api/             # API请求
  5. │   │   ├── index.ts     # API请求实例
  6. │   │   └── modules/     # 按模块划分的API
  7. │   ├── assets/          # 静态资源
  8. │   │   ├── images/      # 图片
  9. │   │   └── styles/      # 样式文件
  10. │   ├── components/      # 公共组件
  11. │   │   └── common/      # 通用组件
  12. │   ├── composables/     # 组合式函数
  13. │   ├── hooks/           # 自定义hooks
  14. │   ├── layouts/         # 布局组件
  15. │   ├── plugins/         # 插件
  16. │   ├── router/          # 路由配置
  17. │   │   ├── index.ts     # 路由实例
  18. │   │   └── modules/     # 路由模块
  19. │   ├── stores/          # 状态管理
  20. │   │   ├── index.ts     # Pinia实例
  21. │   │   └── modules/     # 按模块划分的store
  22. │   ├── types/           # TypeScript类型定义
  23. │   │   ├── api.ts       # API相关类型
  24. │   │   ├── common.ts    # 通用类型
  25. │   │   └── store.ts     # Store相关类型
  26. │   ├── utils/           # 工具函数
  27. │   ├── views/           # 页面组件
  28. │   ├── App.vue          # 根组件
  29. │   └── main.ts          # 入口文件
  30. ├── .env                 # 环境变量
  31. ├── .env.development     # 开发环境变量
  32. ├── .env.production      # 生产环境变量
  33. ├── .eslintrc.js         # ESLint配置
  34. ├── .prettierrc          # Prettier配置
  35. ├── index.html           # HTML模板
  36. ├── package.json         # 项目依赖
  37. ├── tsconfig.json        # TypeScript配置
  38. ├── vite.config.ts       # Vite配置
  39. └── README.md            # 项目说明
复制代码

TypeScript配置

基础tsconfig.json配置
  1. {
  2.   "compilerOptions": {
  3.     "target": "ESNext",
  4.     "useDefineForClassFields": true,
  5.     "module": "ESNext",
  6.     "moduleResolution": "Node",
  7.     "strict": true,
  8.     "jsx": "preserve",
  9.     "sourceMap": true,
  10.     "resolveJsonModule": true,
  11.     "isolatedModules": true,
  12.     "esModuleInterop": true,
  13.     "lib": ["ESNext", "DOM"],
  14.     "skipLibCheck": true,
  15.     "baseUrl": ".",
  16.     "paths": {
  17.       "@/*": ["src/*"]
  18.     },
  19.     "types": ["vite/client"]
  20.   },
  21.   "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  22.   "references": [{ "path": "./tsconfig.node.json" }]
  23. }
复制代码

tsconfig.node.json配置
  1. {
  2.   "compilerOptions": {
  3.     "composite": true,
  4.     "module": "ESNext",
  5.     "moduleResolution": "Node",
  6.     "allowSyntheticDefaultImports": true
  7.   },
  8.   "include": ["vite.config.ts"]
  9. }
复制代码

类型声明文件

在src目录下创建shims-vue.d.ts文件,以支持.vue文件的类型检查:
  1. declare module '*.vue' {
  2.   import type { DefineComponent } from 'vue'
  3.   const component: DefineComponent<{}, {}, any>
  4.   export default component
  5. }
复制代码

Vue3组件开发

组件基本结构

一个典型的Vue3+TypeScript组件结构如下:
  1. <template>
  2.   <div class="example-component">
  3.     <h1>{{ title }}</h1>
  4.     <p>{{ count }}</p>
  5.     <button @click="increment">Increment</button>
  6.   </div>
  7. </template>
  8. <script lang="ts">
  9. import { defineComponent, ref } from 'vue'
  10. export default defineComponent({
  11.   name: 'ExampleComponent',
  12.   props: {
  13.     title: {
  14.       type: String,
  15.       required: true
  16.     }
  17.   },
  18.   setup(props) {
  19.     const count = ref(0)
  20.    
  21.     const increment = () => {
  22.       count.value++
  23.     }
  24.    
  25.     return {
  26.       count,
  27.       increment
  28.     }
  29.   }
  30. })
  31. </script>
  32. <style scoped>
  33. .example-component {
  34.   padding: 20px;
  35.   border: 1px solid #eee;
  36.   border-radius: 4px;
  37. }
  38. </style>
复制代码

使用组合式API(Composition API)

组合式API是Vue3的亮点,结合TypeScript可以提供更好的类型推断:
  1. <script setup lang="ts">
  2. import { ref, computed } from 'vue'
  3. interface Props {
  4.   title: string
  5.   initialCount?: number
  6. }
  7. const props = withDefaults(defineProps<Props>(), {
  8.   initialCount: 0
  9. })
  10. const emit = defineEmits<{
  11.   (e: 'count-change', count: number): void
  12. }>()
  13. const count = ref(props.initialCount)
  14. const doubleCount = computed(() => count.value * 2)
  15. const increment = () => {
  16.   count.value++
  17.   emit('count-change', count.value)
  18. }
  19. </script>
复制代码

组件类型定义

对于复杂的组件,我们可以单独定义类型:
  1. // types/component.d.ts
  2. export interface User {
  3.   id: number
  4.   name: string
  5.   email: string
  6.   avatar?: string
  7. }
  8. export interface UserCardProps {
  9.   user: User
  10.   showEmail?: boolean
  11. }
复制代码

然后在组件中使用:
  1. <script setup lang="ts">
  2. import type { UserCardProps } from '@/types/component'
  3. const props = defineProps<UserCardProps>()
  4. </script>
复制代码

使用泛型组件

Vue3支持泛型组件,可以创建更加灵活的组件:
  1. <script setup lang="ts" generic="T extends { id: number }, U extends string">
  2. import { ref } from 'vue'
  3. const props = defineProps<{
  4.   data: T[]
  5.   title: U
  6. }>()
  7. const selectedItem = ref<T | null>(null)
  8. </script>
复制代码

状态管理

Pinia与TypeScript

Pinia是Vue官方推荐的状态管理库,它与TypeScript有很好的集成。首先安装Pinia:
  1. npm install pinia
复制代码

然后在main.ts中引入:
  1. import { createApp } from 'vue'
  2. import { createPinia } from 'pinia'
  3. import App from './App.vue'
  4. const app = createApp(App)
  5. app.use(createPinia())
  6. app.mount('#app')
复制代码

定义Store

创建一个类型安全的Store:
  1. // stores/user.ts
  2. import { defineStore } from 'pinia'
  3. import type { User } from '@/types/common'
  4. import { ref, computed } from 'vue'
  5. export const useUserStore = defineStore('user', () => {
  6.   const users = ref<User[]>([])
  7.   const currentUser = ref<User | null>(null)
  8.   const loading = ref(false)
  9.   
  10.   const getUserById = (id: number) => {
  11.     return computed(() => users.value.find(user => user.id === id))
  12.   }
  13.   
  14.   const fetchUsers = async () => {
  15.     loading.value = true
  16.     try {
  17.       // 模拟API请求
  18.       const response = await fetch('/api/users')
  19.       const data = await response.json()
  20.       users.value = data
  21.     } catch (error) {
  22.       console.error('Failed to fetch users:', error)
  23.     } finally {
  24.       loading.value = false
  25.     }
  26.   }
  27.   
  28.   const setCurrentUser = (user: User) => {
  29.     currentUser.value = user
  30.   }
  31.   
  32.   return {
  33.     users,
  34.     currentUser,
  35.     loading,
  36.     getUserById,
  37.     fetchUsers,
  38.     setCurrentUser
  39.   }
  40. })
复制代码

使用Store

在组件中使用Store:
  1. <script setup lang="ts">
  2. import { useUserStore } from '@/stores/user'
  3. import { storeToRefs } from 'pinia'
  4. import { onMounted } from 'vue'
  5. const userStore = useUserStore()
  6. const { users, loading } = storeToRefs(userStore)
  7. const { fetchUsers } = userStore
  8. onMounted(() => {
  9.   fetchUsers()
  10. })
  11. </script>
  12. <template>
  13.   <div v-if="loading">Loading...</div>
  14.   <div v-else>
  15.     <div v-for="user in users" :key="user.id">
  16.       {{ user.name }}
  17.     </div>
  18.   </div>
  19. </template>
复制代码

路由管理

安装和配置Vue Router

首先安装Vue Router:
  1. npm install vue-router@4
复制代码

然后创建路由配置:
  1. // router/index.ts
  2. import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
  3. import HomeView from '@/views/HomeView.vue'
  4. const routes: Array<RouteRecordRaw> = [
  5.   {
  6.     path: '/',
  7.     name: 'Home',
  8.     component: HomeView,
  9.     meta: {
  10.       title: '首页'
  11.     }
  12.   },
  13.   {
  14.     path: '/about',
  15.     name: 'About',
  16.     component: () => import('@/views/AboutView.vue'),
  17.     meta: {
  18.       title: '关于'
  19.     }
  20.   },
  21.   {
  22.     path: '/user/:id',
  23.     name: 'UserDetail',
  24.     component: () => import('@/views/UserView.vue'),
  25.     props: true,
  26.     meta: {
  27.       title: '用户详情'
  28.     }
  29.   }
  30. ]
  31. const router = createRouter({
  32.   history: createWebHistory(import.meta.env.BASE_URL),
  33.   routes
  34. })
  35. // 路由守卫
  36. router.beforeEach((to, from, next) => {
  37.   // 设置页面标题
  38.   document.title = `${to.meta.title} - 我的Vue应用`
  39.   next()
  40. })
  41. export default router
复制代码

在main.ts中引入路由:
  1. import { createApp } from 'vue'
  2. import App from './App.vue'
  3. import router from './router'
  4. const app = createApp(App)
  5. app.use(router)
  6. app.mount('#app')
复制代码

路由类型定义

可以扩展RouteMeta类型来添加自定义元数据:
  1. // types/router.d.ts
  2. import 'vue-router'
  3. declare module 'vue-router' {
  4.   interface RouteMeta {
  5.     title?: string
  6.     requiresAuth?: boolean
  7.     roles?: string[]
  8.   }
  9. }
复制代码

使用路由

在组件中使用路由:
  1. <script setup lang="ts">
  2. import { useRoute, useRouter } from 'vue-router'
  3. import { computed } from 'vue'
  4. const route = useRoute()
  5. const router = useRouter()
  6. const userId = computed(() => route.params.id as string)
  7. const goToUser = (id: string) => {
  8.   router.push({
  9.     name: 'UserDetail',
  10.     params: { id }
  11.   })
  12. }
  13. </script>
复制代码

API请求

创建API实例

使用axios创建类型安全的API实例:
  1. // api/index.ts
  2. import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
  3. import type { ApiResponse } from '@/types/api'
  4. // 创建axios实例
  5. const apiClient: AxiosInstance = axios.create({
  6.   baseURL: import.meta.env.VITE_API_BASE_URL,
  7.   timeout: 10000,
  8.   headers: {
  9.     'Content-Type': 'application/json'
  10.   }
  11. })
  12. // 请求拦截器
  13. apiClient.interceptors.request.use(
  14.   (config: AxiosRequestConfig) => {
  15.     // 添加token等认证信息
  16.     const token = localStorage.getItem('token')
  17.     if (token && config.headers) {
  18.       config.headers.Authorization = `Bearer ${token}`
  19.     }
  20.     return config
  21.   },
  22.   (error) => {
  23.     return Promise.reject(error)
  24.   }
  25. )
  26. // 响应拦截器
  27. apiClient.interceptors.response.use(
  28.   (response: AxiosResponse<ApiResponse>) => {
  29.     // 处理响应数据
  30.     return response.data
  31.   },
  32.   (error) => {
  33.     // 处理错误
  34.     if (error.response) {
  35.       switch (error.response.status) {
  36.         case 401:
  37.           // 处理未授权
  38.           console.error('Unauthorized')
  39.           break
  40.         case 404:
  41.           console.error('Not found')
  42.           break
  43.         case 500:
  44.           console.error('Server error')
  45.           break
  46.         default:
  47.           console.error('Error:', error.response.status)
  48.       }
  49.     }
  50.     return Promise.reject(error)
  51.   }
  52. )
  53. export default apiClient
复制代码

API类型定义

定义API相关的类型:
  1. // types/api.ts
  2. export interface ApiResponse<T = any> {
  3.   code: number
  4.   message: string
  5.   data: T
  6. }
  7. export interface PaginationParams {
  8.   page: number
  9.   pageSize: number
  10. }
  11. export interface PaginatedResponse<T> {
  12.   items: T[]
  13.   total: number
  14.   page: number
  15.   pageSize: number
  16. }
复制代码

API模块化

按模块组织API请求:
  1. // api/modules/user.ts
  2. import apiClient from '@/api'
  3. import type { ApiResponse, PaginatedResponse } from '@/types/api'
  4. import type { User, UserCreateParams, UserUpdateParams } from '@/types/common'
  5. export default {
  6.   // 获取用户列表
  7.   getUsers(params: { page?: number; pageSize?: number }): Promise<ApiResponse<PaginatedResponse<User>>> {
  8.     return apiClient.get('/users', { params })
  9.   },
  10.   
  11.   // 获取用户详情
  12.   getUserById(id: number): Promise<ApiResponse<User>> {
  13.     return apiClient.get(`/users/${id}`)
  14.   },
  15.   
  16.   // 创建用户
  17.   createUser(data: UserCreateParams): Promise<ApiResponse<User>> {
  18.     return apiClient.post('/users', data)
  19.   },
  20.   
  21.   // 更新用户
  22.   updateUser(id: number, data: UserUpdateParams): Promise<ApiResponse<User>> {
  23.     return apiClient.put(`/users/${id}`, data)
  24.   },
  25.   
  26.   // 删除用户
  27.   deleteUser(id: number): Promise<ApiResponse<void>> {
  28.     return apiClient.delete(`/users/${id}`)
  29.   }
  30. }
复制代码

在组件中使用API
  1. <script setup lang="ts">
  2. import { ref, onMounted } from 'vue'
  3. import userApi from '@/api/modules/user'
  4. import type { User } from '@/types/common'
  5. const users = ref<User[]>([])
  6. const loading = ref(false)
  7. const error = ref<string | null>(null)
  8. const fetchUsers = async () => {
  9.   loading.value = true
  10.   error.value = null
  11.   try {
  12.     const response = await userApi.getUsers({ page: 1, pageSize: 10 })
  13.     users.value = response.data.items
  14.   } catch (err) {
  15.     error.value = 'Failed to fetch users'
  16.     console.error(err)
  17.   } finally {
  18.     loading.value = false
  19.   }
  20. }
  21. onMounted(() => {
  22.   fetchUsers()
  23. })
  24. </script>
复制代码

工具链配置

Vite配置

创建一个优化的Vite配置:
  1. // vite.config.ts
  2. import { defineConfig } from 'vite'
  3. import vue from '@vitejs/plugin-vue'
  4. import { resolve } from 'path'
  5. // https://vitejs.dev/config/
  6. export default defineConfig({
  7.   plugins: [vue()],
  8.   resolve: {
  9.     alias: {
  10.       '@': resolve(__dirname, 'src')
  11.     }
  12.   },
  13.   server: {
  14.     port: 3000,
  15.     open: true,
  16.     proxy: {
  17.       '/api': {
  18.         target: 'http://localhost:8080',
  19.         changeOrigin: true,
  20.         rewrite: (path) => path.replace(/^\/api/, '')
  21.       }
  22.     }
  23.   },
  24.   build: {
  25.     outDir: 'dist',
  26.     assetsDir: 'assets',
  27.     sourcemap: false,
  28.     // 代码分割配置
  29.     rollupOptions: {
  30.       output: {
  31.         chunkFileNames: 'js/[name]-[hash].js',
  32.         entryFileNames: 'js/[name]-[hash].js',
  33.         assetFileNames: '[ext]/[name]-[hash].[ext]',
  34.         manualChunks: {
  35.           'vendor': ['vue', 'vue-router', 'pinia'],
  36.           'ui-library': ['element-plus'] // 如果使用UI库
  37.         }
  38.       }
  39.     }
  40.   }
  41. })
复制代码

ESLint配置

配置ESLint以确保代码质量:
  1. // .eslintrc.js
  2. module.exports = {
  3.   root: true,
  4.   env: {
  5.     browser: true,
  6.     es2021: true,
  7.     node: true
  8.   },
  9.   extends: [
  10.     'plugin:vue/vue3-recommended',
  11.     'plugin:@typescript-eslint/recommended',
  12.     'prettier'
  13.   ],
  14.   parser: 'vue-eslint-parser',
  15.   parserOptions: {
  16.     parser: '@typescript-eslint/parser',
  17.     ecmaVersion: 2021,
  18.     sourceType: 'module'
  19.   },
  20.   rules: {
  21.     'vue/multi-word-component-names': 'off',
  22.     '@typescript-eslint/no-explicit-any': 'warn',
  23.     '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
  24.     'vue/script-setup-uses-vars': 'error'
  25.   }
  26. }
复制代码

Prettier配置

配置Prettier以统一代码风格:
  1. // .prettierrc
  2. {
  3.   "semi": false,
  4.   "singleQuote": true,
  5.   "tabWidth": 2,
  6.   "trailingComma": "none",
  7.   "printWidth": 100,
  8.   "bracketSpacing": true,
  9.   "arrowParens": "avoid"
  10. }
复制代码

环境变量配置

创建环境变量文件:
  1. # .env
  2. VITE_APP_TITLE=My Vue App
  3. VITE_API_BASE_URL=/api
  4. # .env.development
  5. VITE_API_BASE_URL=http://localhost:8080/api
  6. # .env.production
  7. VITE_API_BASE_URL=https://api.example.com
复制代码

在代码中使用环境变量:
  1. const apiUrl = import.meta.env.VITE_API_BASE_URL
复制代码

测试策略

单元测试

使用Vitest进行单元测试,首先安装依赖:
  1. npm install -D vitest @vue/test-utils jsdom
复制代码

配置Vitest:
  1. // vitest.config.ts
  2. import { defineConfig } from 'vite'
  3. import vue from '@vitejs/plugin-vue'
  4. import { resolve } from 'path'
  5. export default defineConfig({
  6.   plugins: [vue()],
  7.   resolve: {
  8.     alias: {
  9.       '@': resolve(__dirname, 'src')
  10.     }
  11.   },
  12.   test: {
  13.     globals: true,
  14.     environment: 'jsdom'
  15.   }
  16. })
复制代码

编写测试用例:
  1. // tests/components/ExampleComponent.test.ts
  2. import { mount } from '@vue/test-utils'
  3. import { describe, it, expect } from 'vitest'
  4. import ExampleComponent from '@/components/ExampleComponent.vue'
  5. describe('ExampleComponent', () => {
  6.   it('renders proper title when passed', () => {
  7.     const title = 'Test Title'
  8.     const wrapper = mount(ExampleComponent, {
  9.       props: { title }
  10.     })
  11.     expect(wrapper.text()).toContain(title)
  12.   })
  13.   it('increments count when button is clicked', async () => {
  14.     const wrapper = mount(ExampleComponent, {
  15.       props: { title: 'Test' }
  16.     })
  17.    
  18.     expect(wrapper.text()).toContain('0')
  19.    
  20.     await wrapper.find('button').trigger('click')
  21.    
  22.     expect(wrapper.text()).toContain('1')
  23.   })
  24. })
复制代码

端到端测试

使用Cypress进行端到端测试:
  1. npm install -D cypress
复制代码

配置Cypress:
  1. // cypress.config.ts
  2. import { defineConfig } from 'cypress'
  3. export default defineConfig({
  4.   e2e: {
  5.     baseUrl: 'http://localhost:3000',
  6.     supportFile: 'cypress/support/e2e.ts',
  7.     specPattern: 'cypress/e2e/**/*.cy.{js,jsx,ts,tsx}'
  8.   }
  9. })
复制代码

编写端到端测试:
  1. // cypress/e2e/home.cy.ts
  2. describe('Home Page', () => {
  3.   it('successfully loads', () => {
  4.     cy.visit('/')
  5.     cy.contains('h1', 'Welcome to Your Vue.js + TypeScript App')
  6.   })
  7.   it('navigates to about page', () => {
  8.     cy.visit('/')
  9.     cy.contains('a', 'About').click()
  10.     cy.url().should('include', '/about')
  11.     cy.contains('h1', 'This is an about page')
  12.   })
  13. })
复制代码

性能优化

代码分割

Vue3和Vite支持基于路由的代码分割,可以按需加载页面组件:
  1. // router/index.ts
  2. const routes: Array<RouteRecordRaw> = [
  3.   {
  4.     path: '/',
  5.     name: 'Home',
  6.     component: () => import('@/views/HomeView.vue')
  7.   },
  8.   {
  9.     path: '/about',
  10.     name: 'About',
  11.     component: () => import('@/views/AboutView.vue')
  12.   }
  13. ]
复制代码

懒加载组件

使用defineAsyncComponent实现组件懒加载:
  1. import { defineAsyncComponent } from 'vue'
  2. const AsyncComponent = defineAsyncComponent(() =>
  3.   import('@/components/HeavyComponent.vue')
  4. )
复制代码

虚拟滚动

对于长列表,使用虚拟滚动提高性能:
  1. <template>
  2.   <div class="virtual-scroll-container" @scroll="handleScroll">
  3.     <div class="scroll-content" :style="{ height: `${totalHeight}px` }">
  4.       <div
  5.         v-for="item in visibleItems"
  6.         :key="item.id"
  7.         class="scroll-item"
  8.         :style="{ transform: `translateY(${item.offset}px)` }"
  9.       >
  10.         {{ item.content }}
  11.       </div>
  12.     </div>
  13.   </div>
  14. </template>
  15. <script setup lang="ts">
  16. import { ref, computed, onMounted } from 'vue'
  17. interface ScrollItem {
  18.   id: number
  19.   content: string
  20.   offset: number
  21. }
  22. const itemHeight = 50
  23. const containerHeight = 500
  24. const items = ref<ScrollItem[]>([])
  25. const scrollTop = ref(0)
  26. // 生成大量数据
  27. onMounted(() => {
  28.   const data = []
  29.   for (let i = 0; i < 1000; i++) {
  30.     data.push({
  31.       id: i,
  32.       content: `Item ${i}`,
  33.       offset: i * itemHeight
  34.     })
  35.   }
  36.   items.value = data
  37. })
  38. const totalHeight = computed(() => items.value.length * itemHeight)
  39. const startIndex = computed(() => Math.floor(scrollTop.value / itemHeight))
  40. const endIndex = computed(() => {
  41.   const visibleItemCount = Math.ceil(containerHeight / itemHeight)
  42.   return Math.min(items.value.length - 1, startIndex.value + visibleItemCount)
  43. })
  44. const visibleItems = computed(() => {
  45.   return items.value.slice(startIndex.value, endIndex.value + 1)
  46. })
  47. const handleScroll = (e: Event) => {
  48.   scrollTop.value = (e.target as HTMLElement).scrollTop
  49. }
  50. </script>
  51. <style scoped>
  52. .virtual-scroll-container {
  53.   height: 500px;
  54.   overflow-y: auto;
  55.   border: 1px solid #eee;
  56. }
  57. .scroll-content {
  58.   position: relative;
  59. }
  60. .scroll-item {
  61.   height: 50px;
  62.   line-height: 50px;
  63.   padding: 0 20px;
  64.   border-bottom: 1px solid #eee;
  65.   position: absolute;
  66.   width: 100%;
  67.   box-sizing: border-box;
  68. }
  69. </style>
复制代码

使用shallowRef和shallowReactive

对于大型对象,使用shallowRef和shallowReactive提高性能:
  1. import { shallowRef, shallowReactive } from 'vue'
  2. // 对于大型对象,使用shallowRef避免深度响应式转换
  3. const largeObject = shallowRef({ /* 大型数据 */ })
  4. // 对于大型数组,使用shallowReactive
  5. const largeArray = shallowReactive([])
复制代码

使用v-once指令

对于静态内容,使用v-once指令避免不必要的重新渲染:
  1. <template>
  2.   <div v-once>
  3.     <h1>Static Content</h1>
  4.     <p>This content will not be re-rendered.</p>
  5.   </div>
  6. </template>
复制代码

部署上线

构建项目

使用Vite构建生产版本:
  1. npm run build
复制代码

构建完成后,会在dist目录下生成静态文件。

静态部署

将dist目录下的文件部署到静态服务器,如Nginx:
  1. server {
  2.   listen 80;
  3.   server_name your-domain.com;
  4.   root /path/to/dist;
  5.   index index.html;
  6.   location / {
  7.     try_files $uri $uri/ /index.html;
  8.   }
  9.   # 静态资源缓存
  10.   location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
  11.     expires 1y;
  12.     add_header Cache-Control "public, immutable";
  13.   }
  14. }
复制代码

使用Docker部署

创建Dockerfile:
  1. # 构建阶段
  2. FROM node:16 as build-stage
  3. WORKDIR /app
  4. COPY package*.json ./
  5. RUN npm install
  6. COPY . .
  7. RUN npm run build
  8. # 生产阶段
  9. FROM nginx:stable-alpine as production-stage
  10. COPY --from=build-stage /app/dist /usr/share/nginx/html
  11. EXPOSE 80
  12. CMD ["nginx", "-g", "daemon off;"]
复制代码

构建并运行Docker容器:
  1. # 构建镜像
  2. docker build -t my-vue-app .
  3. # 运行容器
  4. docker run -d -p 80:80 my-vue-app
复制代码

CI/CD配置

使用GitHub Actions进行自动部署:
  1. # .github/workflows/deploy.yml
  2. name: Deploy
  3. on:
  4.   push:
  5.     branches: [ main ]
  6. jobs:
  7.   build-and-deploy:
  8.     runs-on: ubuntu-latest
  9.    
  10.     steps:
  11.     - name: Checkout
  12.       uses: actions/checkout@v3
  13.       
  14.     - name: Setup Node.js
  15.       uses: actions/setup-node@v3
  16.       with:
  17.         node-version: '16'
  18.         cache: 'npm'
  19.         
  20.     - name: Install dependencies
  21.       run: npm ci
  22.       
  23.     - name: Build
  24.       run: npm run build
  25.       
  26.     - name: Deploy to production
  27.       uses: appleboy/scp-action@master
  28.       with:
  29.         host: ${{ secrets.PRODUCTION_HOST }}
  30.         username: ${{ secrets.PRODUCTION_USER }}
  31.         key: ${{ secrets.PRODUCTION_KEY }}
  32.         source: "dist/"
  33.         target: "/var/www/my-vue-app"
  34.         strip_components: 1
复制代码

实战经验分享

常见问题及解决方案

问题:在使用组合式API时,有时TypeScript无法正确推断类型。

解决方案:使用类型注解明确指定类型:
  1. import { ref } from 'vue'
  2. // 错误示例 - TypeScript无法推断数组元素类型
  3. const items = ref([])
  4. // 正确示例 - 明确指定类型
  5. const items = ref<string[]>([])
复制代码

问题:解构响应式对象时,会失去响应性。

解决方案:使用toRefs保持响应性:
  1. import { reactive, toRefs } from 'vue'
  2. const state = reactive({
  3.   count: 0,
  4.   name: 'Vue'
  5. })
  6. // 错误示例 - 失去响应性
  7. const { count, name } = state
  8. // 正确示例 - 保持响应性
  9. const { count, name } = toRefs(state)
复制代码

问题:两个组件相互引用导致循环依赖。

解决方案:使用异步组件或provide/inject模式:
  1. // 父组件
  2. import { defineAsyncComponent } from 'vue'
  3. const ChildComponent = defineAsyncComponent(() => import('./ChildComponent.vue'))
复制代码

问题:开发过程中修改Store后,页面不会自动更新。

解决方案:配置Vite的热更新:
  1. // vite.config.ts
  2. export default defineConfig({
  3.   // ...
  4.   server: {
  5.     hmr: {
  6.       overlay: false
  7.     }
  8.   }
  9. })
复制代码

代码组织最佳实践

将可复用的逻辑提取到组合式函数中:
  1. // composables/useFetch.ts
  2. import { ref, Ref } from 'vue'
  3. export function useFetch<T>(url: string) {
  4.   const data: Ref<T | null> = ref(null)
  5.   const error: Ref<Error | null> = ref(null)
  6.   const loading = ref(false)
  7.   const fetchData = async () => {
  8.     loading.value = true
  9.     error.value = null
  10.    
  11.     try {
  12.       const response = await fetch(url)
  13.       data.value = await response.json()
  14.     } catch (err) {
  15.       error.value = err as Error
  16.     } finally {
  17.       loading.value = false
  18.     }
  19.   }
  20.   return {
  21.     data,
  22.     error,
  23.     loading,
  24.     fetchData
  25.   }
  26. }
复制代码

在组件中使用:
  1. <script setup lang="ts">
  2. import { onMounted } from 'vue'
  3. import { useFetch } from '@/composables/useFetch'
  4. import type { User } from '@/types/common'
  5. const { data: users, error, loading, fetchData } = useFetch<User[]>('/api/users')
  6. onMounted(() => {
  7.   fetchData()
  8. })
  9. </script>
复制代码

创建类型守卫函数,提高类型安全性:
  1. // utils/typeGuards.ts
  2. import type { User, Product } from '@/types/common'
  3. export function isUser(obj: any): obj is User {
  4.   return obj && typeof obj.id === 'number' && typeof obj.name === 'string'
  5. }
  6. export function isProduct(obj: any): obj is Product {
  7.   return obj && typeof obj.id === 'number' && typeof obj.price === 'number'
  8. }
复制代码

使用类型守卫:
  1. function processData(data: User | Product) {
  2.   if (isUser(data)) {
  3.     // TypeScript知道data是User类型
  4.     console.log(data.name)
  5.   } else if (isProduct(data)) {
  6.     // TypeScript知道data是Product类型
  7.     console.log(data.price)
  8.   }
  9. }
复制代码

在types目录下创建全局类型声明文件:
  1. // types/global.d.ts
  2. declare interface Window {
  3.   myApp: {
  4.     version: string
  5.     config: {
  6.       apiUrl: string
  7.     }
  8.   }
  9. }
复制代码

性能优化实战

对于列表渲染,使用v-memo避免不必要的重新渲染:
  1. <template>
  2.   <div v-for="item in items" :key="item.id" v-memo="[item.id === selectedId]">
  3.     {{ item.content }}
  4.   </div>
  5. </template>
复制代码

对于大型对象或第三方库实例,使用markRaw避免响应式转换:
  1. import { markRaw } from 'vue'
  2. import * as echarts from 'echarts'
  3. // 避免echarts实例被转换为响应式对象
  4. const chartInstance = markRaw(echarts.init(document.getElementById('chart')))
复制代码

对于大型列表,使用shallowRef提高性能:
  1. import { shallowRef } from 'vue'
  2. const largeList = shallowRef([])
  3. // 更新列表时,替换整个数组而不是修改原数组
  4. function updateList(newItems) {
  5.   largeList.value = newItems
  6. }
复制代码

总结与展望

Vue3与TypeScript的结合为现代前端开发提供了强大的工具链。通过本文介绍的最佳实践和技巧,开发者可以构建出类型安全、高性能且易于维护的现代化前端应用。

核心要点回顾

1. 项目初始化:使用Vite或Vue CLI创建Vue3+TypeScript项目,并进行基础配置。
2. 项目结构:采用模块化的项目结构,提高代码可维护性。
3. TypeScript配置:合理配置tsconfig.json,充分利用TypeScript的类型检查能力。
4. 组件开发:使用组合式API和TypeScript,创建类型安全的组件。
5. 状态管理:使用Pinia进行状态管理,结合TypeScript提供类型安全的状态操作。
6. 路由管理:使用Vue Router管理应用路由,并通过类型扩展增强路由功能。
7. API请求:创建类型安全的API请求层,统一管理API调用。
8. 工具链配置:配置Vite、ESLint、Prettier等工具,提高开发效率和代码质量。
9. 测试策略:实施单元测试和端到端测试,确保应用质量。
10. 性能优化:采用代码分割、懒加载、虚拟滚动等技术优化应用性能。
11. 部署上线:使用静态部署、Docker或CI/CD流程部署应用。

未来展望

随着Vue3和TypeScript的不断发展,我们可以期待以下方面的进步:

1. 更强大的类型推断:未来的Vue版本可能会提供更强大的类型推断能力,进一步减少类型注解的需要。
2. 更好的开发工具:Vue DevTools和IDE插件将提供更好的TypeScript支持,提升开发体验。
3. 更优的性能:Vue3的响应式系统将继续优化,提供更好的性能表现。
4. 更丰富的生态系统:随着Vue3的普及,将会有更多支持TypeScript的第三方库和工具出现。

通过持续学习和实践,开发者可以充分利用Vue3和TypeScript的优势,构建出更加优秀的现代化前端应用。希望本文的分享能够对你在Vue3和TypeScript的开发道路上有所帮助。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则