Vue3+TypeScript項目架構實踐指南
1. 引言
Vue 3 + TypeScript + Vite + Pinia 技術棧已經(jīng)成為現(xiàn)代前端開發(fā)的主流選擇,其優(yōu)秀的性能和開發(fā)體驗備受開發(fā)者青睞。然而,要充分發(fā)揮這一技術棧的潛力,合理的項目架構設計至關重要。
一個良好的項目架構能夠:
- 提高代碼的可維護性和可擴展性
- 減少團隊協(xié)作中的沖突和問題
- 加速開發(fā)效率和代碼質量
- 便于測試和部署
本文將詳細介紹 Vue 3 + TypeScript 項目的架構實踐,包括項目結構設計、狀態(tài)管理架構、路由架構、API 層設計、工具類和公共組件的組織,以及企業(yè)級項目案例分析。
2. 項目結構設計
2.1 目錄結構組織
一個合理的目錄結構是項目架構的基礎,它能夠清晰地分離不同功能模塊,便于開發(fā)者理解和維護代碼。
// 推薦的 Vue 3 + TypeScript 項目目錄結構 // 該結構清晰分離了不同功能模塊,便于開發(fā)者理解和維護代碼 // 每個目錄都有明確的職責和用途,遵循關注點分離原則 // 設計意圖:通過模塊化組織提高代碼可維護性和可擴展性 src/ // 源代碼目錄 ├── assets/ # 靜態(tài)資源文件 │ ├── images/ # 圖片資源(如產(chǎn)品圖片、背景圖等) │ ├── styles/ # 全局樣式(如重置樣式、主題樣式等) │ └── icons/ # 圖標資源(如 SVG 圖標、字體圖標等) ├── components/ # 公共組件 │ ├── common/ # 通用基礎組件(如按鈕、輸入框等) │ ├── layout/ # 布局相關組件(如頭部、側邊欄等) │ └── business/ # 業(yè)務相關組件(如用戶卡片、產(chǎn)品列表等) ├── composables/ # 組合式 API 邏輯 │ ├── useAuth.ts # 認證相關邏輯(如登錄、注冊等) │ ├── useApi.ts # API 調用邏輯(如請求封裝、錯誤處理等) │ └── useLocalStorage.ts # 本地存儲邏輯(如數(shù)據(jù)持久化等) ├── constants/ # 常量定義 │ ├── api.ts # API 相關常量(如接口地址、請求超時等) │ ├── routes.ts # 路由相關常量(如路由名稱、路徑等) │ └── storage.ts # 存儲相關常量(如存儲鍵名、過期時間等) ├── enums/ # 枚舉類型定義 │ ├── user.ts # 用戶相關枚舉(如用戶角色、狀態(tài)等) │ └── status.ts # 狀態(tài)相關枚舉(如訂單狀態(tài)、審批狀態(tài)等) ├── hooks/ # 自定義鉤子(與 composables 類似,可根據(jù)團隊習慣選擇其一) ├── layouts/ # 布局組件 │ ├── DefaultLayout.vue # 默認布局(如包含側邊欄和頭部的布局) │ └── AuthLayout.vue # 認證布局(如登錄、注冊頁面的布局) ├── models/ # 數(shù)據(jù)模型定義 │ ├── user.ts # 用戶模型(如用戶信息結構、類型定義等) │ ├── product.ts # 產(chǎn)品模型(如產(chǎn)品信息結構、類型定義等) │ └── common.ts # 通用模型(如分頁結構、響應結構等) ├── router/ # 路由配置 │ ├── index.ts # 路由主配置(如創(chuàng)建路由實例、應用守衛(wèi)等) │ ├── routes.ts # 路由定義(如路由路徑、組件映射等) │ └── guards.ts # 路由守衛(wèi)(如認證守衛(wèi)、權限守衛(wèi)等) ├── services/ # 服務層 │ ├── api/ # API 服務 │ │ ├── user.ts # 用戶相關 API(如登錄、獲取用戶信息等) │ │ ├── product.ts # 產(chǎn)品相關 API(如獲取產(chǎn)品列表、創(chuàng)建產(chǎn)品等) │ │ └── index.ts # API 服務主文件(如創(chuàng)建 axios 實例、配置攔截器等) │ └── utils/ # 工具服務(如第三方服務集成、業(yè)務工具等) ├── stores/ # Pinia 狀態(tài)管理 │ ├── user.ts # 用戶狀態(tài)(如用戶信息、認證狀態(tài)等) │ ├── product.ts # 產(chǎn)品狀態(tài)(如產(chǎn)品列表、詳情等) │ └── common.ts # 通用狀態(tài)(如全局加載狀態(tài)、錯誤信息等) ├── types/ # TypeScript 類型定義 │ ├── api.ts # API 相關類型(如請求參數(shù)、響應類型等) │ ├── components.ts # 組件相關類型(如 props 類型、事件類型等) │ └── common.ts # 通用類型(如通用接口、工具類型等) ├── utils/ # 工具函數(shù) │ ├── format.ts # 格式化工具(如日期格式化、金額格式化等) │ ├── validation.ts # 驗證工具(如郵箱驗證、密碼強度驗證等) │ └── storage.ts # 存儲工具(如本地存儲封裝、會話存儲封裝等) ├── views/ # 頁面組件 │ ├── auth/ # 認證相關頁面 │ │ ├── Login.vue # 登錄頁面 │ │ └── Register.vue # 注冊頁面 │ ├── dashboard/ # 儀表盤頁面 │ └── products/ # 產(chǎn)品相關頁面 ├── App.vue # 根組件(應用的入口組件) ├── main.ts # 應用入口(如初始化 Vue 應用、注冊插件等) └── env.d.ts # 環(huán)境變量類型定義(如 Vite 環(huán)境變量類型聲明)
2.2 目錄結構設計原則
- 功能模塊化:將相關功能的代碼組織在一起,便于理解和維護
- 關注點分離:將不同職責的代碼分離到不同目錄,如視圖、組件、狀態(tài)管理等
- 層次清晰:建立清晰的代碼層次結構,如 API 層、服務層、業(yè)務邏輯層等
- 可擴展性:預留合理的擴展空間,便于后續(xù)功能的添加和修改
- 命名規(guī)范:采用一致的命名規(guī)范,提高代碼的可讀性
2.3 模塊劃分策略
按業(yè)務功能劃分
將代碼按照業(yè)務功能進行劃分,每個功能模塊包含完整的組件、狀態(tài)、服務等。
// 按業(yè)務功能劃分的目錄結構
// 該結構將代碼按照業(yè)務功能進行劃分,每個功能模塊包含完整的組件、狀態(tài)、服務等
// 優(yōu)點:業(yè)務邏輯內聚,便于團隊協(xié)作和代碼維護
// 缺點:可能導致某些通用代碼重復
// 適用場景:大型應用,多個團隊負責不同業(yè)務模塊
// 設計意圖:通過業(yè)務模塊劃分,實現(xiàn)團隊協(xié)作的隔離和業(yè)務邏輯的內聚
src/
├── modules/ # 業(yè)務模塊目錄(存放各個獨立的業(yè)務模塊)
│ ├── auth/ # 認證模塊(處理登錄、注冊等認證相關功能)
│ │ ├── components/ # 認證相關組件(如登錄表單、注冊表單等)
│ │ ├── views/ # 認證相關頁面(如登錄頁面、注冊頁面等)
│ │ ├── services/ # 認證相關服務(如認證 API 調用等)
│ │ └── stores/ # 認證相關狀態(tài)(如認證狀態(tài)管理等)
│ ├── product/ # 產(chǎn)品模塊(處理產(chǎn)品相關功能)
│ │ ├── components/ # 產(chǎn)品相關組件(如產(chǎn)品卡片、產(chǎn)品列表項等)
│ │ ├── views/ # 產(chǎn)品相關頁面(如產(chǎn)品列表頁、產(chǎn)品詳情頁等)
│ │ ├── services/ # 產(chǎn)品相關服務(如產(chǎn)品 API 調用等)
│ │ └── stores/ # 產(chǎn)品相關狀態(tài)(如產(chǎn)品列表狀態(tài)、詳情狀態(tài)等)
│ └── user/ # 用戶模塊(處理用戶相關功能)
│ ├── components/ # 用戶相關組件(如用戶信息卡片、用戶列表項等)
│ ├── views/ # 用戶相關頁面(如用戶列表頁、用戶詳情頁等)
│ ├── services/ # 用戶相關服務(如用戶 API 調用等)
│ └── stores/ # 用戶相關狀態(tài)(如用戶列表狀態(tài)、詳情狀態(tài)等)
└── shared/ # 共享資源(存放各個模塊共用的代碼)
├── components/ # 共享組件(如通用按鈕、輸入框等)
├── composables/ # 共享組合式 API(如通用認證邏輯、API 調用邏輯等)
├── utils/ # 共享工具函數(shù)(如格式化工具、驗證工具等)
└── types/ # 共享類型定義(如通用接口、類型聲明等)
按技術類型劃分
將代碼按照技術類型進行劃分,如組件、服務、狀態(tài)管理等。
// 按技術類型劃分的目錄結構 // 該結構將代碼按照技術類型進行劃分,如組件、服務、狀態(tài)管理等 // 優(yōu)點:技術職責清晰,便于代碼復用和維護 // 缺點:業(yè)務邏輯分散在不同目錄,可能增加跨模塊理解難度 // 適用場景:中小型應用,團隊成員技術棧全面 // 設計意圖:通過技術類型劃分,實現(xiàn)代碼的分類管理和復用 src/ ├── components/ # 所有組件(按技術類型組織的組件集合) ├── services/ # 所有服務(按技術類型組織的服務集合) ├── stores/ # 所有狀態(tài)管理(按技術類型組織的狀態(tài)管理集合) ├── views/ # 所有頁面(按技術類型組織的頁面集合) └── utils/ # 所有工具函數(shù)(按技術類型組織的工具函數(shù)集合)
混合劃分策略
結合業(yè)務功能和技術類型的劃分策略,在頂層按技術類型劃分,在具體模塊內按業(yè)務功能劃分。
// 混合劃分策略的目錄結構
// 該結構結合業(yè)務功能和技術類型的劃分策略,在頂層按技術類型劃分,在具體模塊內按業(yè)務功能劃分
// 優(yōu)點:既保持了技術職責清晰,又保證了業(yè)務邏輯的內聚性
// 缺點:目錄結構可能相對復雜
// 適用場景:中大型應用,需要平衡技術管理和業(yè)務邏輯
// 設計意圖:通過混合劃分策略,兼顧技術管理的清晰性和業(yè)務邏輯的內聚性
src/
├── components/ # 所有組件(頂層按技術類型劃分)
│ ├── common/ # 通用組件(如按鈕、輸入框等通用基礎組件)
│ ├── auth/ # 認證相關組件(如登錄表單、注冊表單等)
│ └── product/ # 產(chǎn)品相關組件(如產(chǎn)品卡片、產(chǎn)品列表等)
├── services/ # 所有服務(頂層按技術類型劃分)
│ ├── api/ # API 服務(按業(yè)務功能劃分的 API 服務)
│ │ ├── auth.ts # 認證 API(處理認證相關的 API 調用)
│ │ └── product.ts # 產(chǎn)品 API(處理產(chǎn)品相關的 API 調用)
│ └── utils/ # 服務工具(如 API 請求封裝、錯誤處理等)
└── stores/ # 所有狀態(tài)管理(頂層按技術類型劃分)
├── auth.ts # 認證狀態(tài)(處理認證相關的狀態(tài)管理)
└── product.ts # 產(chǎn)品狀態(tài)(處理產(chǎn)品相關的狀態(tài)管理)
2.4 推薦的目錄結構
根據(jù)項目規(guī)模和團隊習慣,推薦使用以下目錄結構:
// 推薦的目錄結構 // 該結構根據(jù)項目規(guī)模和團隊習慣,綜合考慮了技術管理和業(yè)務邏輯的平衡 // 每個目錄都有明確的職責,便于開發(fā)者理解和維護代碼 // 適用場景:大多數(shù) Vue 3 + TypeScript 項目 // 設計意圖:提供一個通用的、可擴展的目錄結構,適用于各種規(guī)模的項目 src/ ├── assets/ # 靜態(tài)資源(如圖片、樣式、圖標等) ├── components/ # 公共組件(如通用組件、業(yè)務組件等) ├── composables/ # 組合式 API(如認證邏輯、API 調用邏輯等) ├── constants/ # 常量定義(如 API 地址、路由名稱等) ├── enums/ # 枚舉類型(如用戶角色、訂單狀態(tài)等) ├── layouts/ # 布局組件(如默認布局、認證布局等) ├── models/ # 數(shù)據(jù)模型(如用戶模型、產(chǎn)品模型等) ├── router/ # 路由配置(如路由定義、守衛(wèi)等) ├── services/ # 服務層(如 API 服務、工具服務等) ├── stores/ # Pinia 狀態(tài)管理(如用戶狀態(tài)、產(chǎn)品狀態(tài)等) ├── types/ # TypeScript 類型(如 API 類型、組件類型等) ├── utils/ # 工具函數(shù)(如格式化工具、驗證工具等) ├── views/ # 頁面組件(如認證頁面、儀表盤頁面等) ├── App.vue # 根組件(應用的入口組件) ├── main.ts # 入口文件(初始化 Vue 應用、注冊插件等) └── env.d.ts # 環(huán)境變量類型(Vite 環(huán)境變量類型聲明)
3. 狀態(tài)管理架構
3.1 Pinia 狀態(tài)管理實踐
Pinia 作為 Vue 3 官方推薦的狀態(tài)管理庫,提供了更簡潔、更靈活的狀態(tài)管理方案。
狀態(tài)管理結構設計
- 按模塊劃分 Store:將狀態(tài)按照業(yè)務模塊進行劃分,每個模塊對應一個 Store
- 使用 Composition API:采用 Composition API 風格定義 Store,提高代碼的可讀性和可維護性
- 類型安全:充分利用 TypeScript 的類型系統(tǒng),為 Store 定義明確的類型
Store 組織方式
// stores/user.ts
// 用戶狀態(tài)管理 Store
// 該文件使用 Pinia 的 Composition API 風格定義用戶相關的狀態(tài)管理
// 包含用戶信息、認證狀態(tài)、登錄/登出等功能
// 特點:類型安全,代碼結構清晰,邏輯組織合理
// 設計意圖:通過 Pinia 管理用戶認證狀態(tài)和用戶信息,實現(xiàn)狀態(tài)的集中管理和持久化
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { User } from '@/models/user'
import { userApi } from '@/services/api/user'
/**
* 用戶狀態(tài)管理 Store
* 管理用戶認證狀態(tài)、用戶信息等
* 使用 Composition API 風格定義,提供更好的類型推斷和代碼組織
* @returns 用戶狀態(tài)管理 Store 實例
*/
export const useUserStore = defineStore('user', () => {
// 狀態(tài)(State)
const currentUser = ref<User | null>(null) // 當前用戶信息,類型為 User 或 null
const token = ref<string | null>(null) // 認證令牌,用于 API 調用的身份驗證
const loading = ref(false) // 加載狀態(tài),用于控制加載指示器的顯示
const error = ref<string | null>(null) // 錯誤信息,用于顯示錯誤提示
// Getters(計算屬性)
const isAuthenticated = computed(() => !!token.value) // 是否已認證,根據(jù) token 是否存在判斷
const userDisplayName = computed(() => {
return currentUser.value ? `${currentUser.value.firstName} ${currentUser.value.lastName}` : ''
}) // 用戶顯示名稱,組合 firstName 和 lastName
// Actions(方法)
/**
* 用戶登錄
* @param email 郵箱
* @param password 密碼
* @returns 登錄響應,包含用戶信息和令牌
* @throws 登錄失敗時拋出錯誤
*/
async function login(email: string, password: string) {
loading.value = true // 開始加載
error.value = null // 清空錯誤信息
try {
const response = await userApi.login({ email, password }) // 調用登錄 API
currentUser.value = response.user // 更新當前用戶信息
token.value = response.token // 更新認證令牌
localStorage.setItem('token', response.token) // 持久化存儲令牌到 localStorage
return response // 返回登錄響應
} catch (err) {
error.value = 'Login failed' // 設置錯誤信息
throw err // 重新拋出錯誤,以便調用方處理
} finally {
loading.value = false // 結束加載
}
}
/**
* 用戶登出
* @throws 登出失敗時拋出錯誤
*/
async function logout() {
loading.value = true // 開始加載
try {
await userApi.logout() // 調用登出 API
currentUser.value = null // 清空當前用戶信息
token.value = null // 清空認證令牌
localStorage.removeItem('token') // 清除 localStorage 中的令牌
} catch (err) {
error.value = 'Logout failed' // 設置錯誤信息
throw err // 重新拋出錯誤,以便調用方處理
} finally {
loading.value = false // 結束加載
}
}
/**
* 獲取當前用戶信息
* @returns 用戶信息
* @throws 獲取失敗時拋出錯誤
*/
async function fetchCurrentUser() {
loading.value = true // 開始加載
error.value = null // 清空錯誤信息
try {
const response = await userApi.getCurrentUser() // 調用獲取用戶信息 API
currentUser.value = response // 更新當前用戶信息
return response // 返回用戶信息
} catch (err) {
error.value = 'Failed to fetch user' // 設置錯誤信息
throw err // 重新拋出錯誤,以便調用方處理
} finally {
loading.value = false // 結束加載
}
}
/**
* 初始化用戶狀態(tài)
* 從本地存儲恢復令牌并獲取用戶信息
* 在應用啟動時調用,用于保持用戶登錄狀態(tài)
*/
function initialize() {
const storedToken = localStorage.getItem('token') // 從 localStorage 獲取存儲的令牌
if (storedToken) {
token.value = storedToken // 更新認證令牌
fetchCurrentUser() // 獲取用戶信息
}
}
return {
// 狀態(tài)(State)
currentUser,
token,
loading,
error,
// Getters(計算屬性)
isAuthenticated,
userDisplayName,
// Actions(方法)
login,
logout,
fetchCurrentUser,
initialize
}
})
3.2 跨 Store 通信策略
在復雜的應用中,不同 Store 之間可能需要進行通信。以下是幾種跨 Store 通信的策略:
1. 直接引用其他 Store
在一個 Store 中直接引用和使用另一個 Store。
// stores/cart.ts
// 購物車狀態(tài)管理 Store
// 該文件演示了如何通過直接引用其他 Store 來實現(xiàn)跨 Store 通信
// 優(yōu)點:代碼簡潔直接,易于理解
// 缺點:Store 之間耦合度較高
// 設計意圖:演示跨 Store 通信的直接引用方法,適用于簡單場景
import { defineStore } from 'pinia'
import { useUserStore } from './user'
/**
* 購物車狀態(tài)管理 Store
* 管理購物車商品、數(shù)量等
* @returns 購物車狀態(tài)管理 Store 實例
*/
export const useCartStore = defineStore('cart', () => {
// 直接引用用戶 Store
// 優(yōu)點:代碼簡潔直接,易于理解
// 缺點:Store 之間耦合度較高,可能導致循環(huán)依賴
const userStore = useUserStore()
/**
* 獲取用戶購物車
* 根據(jù)用戶認證狀態(tài)獲取對應的購物車數(shù)據(jù)
* 演示了如何使用其他 Store 的狀態(tài)(isAuthenticated)
*/
function getUserCart() {
if (userStore.isAuthenticated) {
// 獲取登錄用戶的購物車
// 當用戶已認證時,獲取與用戶賬號關聯(lián)的購物車
console.log('Getting cart for authenticated user')
} else {
// 獲取訪客購物車
// 當用戶未認證時,獲取基于本地存儲的訪客購物車
console.log('Getting cart for guest user')
}
}
return {
getUserCart
}
})
2. 使用事件總線
通過事件總線在不同 Store 之間傳遞消息。
// utils/eventBus.ts
// 事件總線工具
// 用于在不同組件和 Store 之間傳遞消息
// 基于 mitt 庫實現(xiàn)
// 設計意圖:通過事件總線實現(xiàn)組件和 Store 之間的解耦通信
import mitt from 'mitt'
/**
* 事件總線實例
* 用于跨組件和跨 Store 通信
* 優(yōu)點:組件和 Store 之間解耦,減少直接依賴
* 缺點:事件流難以追蹤,可能導致調試困難
*/
export const eventBus = mitt()
// stores/user.ts
// 用戶狀態(tài)管理 Store
// 該文件演示了如何通過事件總線發(fā)送事件
// 設計意圖:登錄成功后通知其他模塊,實現(xiàn)跨模塊通信
import { defineStore } from 'pinia'
import { eventBus } from '@/utils/eventBus'
/**
* 用戶狀態(tài)管理 Store
* @returns 用戶狀態(tài)管理 Store 實例
*/
export const useUserStore = defineStore('user', () => {
/**
* 用戶登錄
* 登錄成功后通過事件總線通知其他 Store
* 演示了如何通過事件總線發(fā)送事件
*/
function login() {
// 登錄邏輯
console.log('User logging in...')
// 登錄成功后發(fā)送事件
// 事件名稱:'user:login',事件數(shù)據(jù):{ userId: 123 }
eventBus.emit('user:login', { userId: 123 })
}
return {
login
}
})
// stores/cart.ts
// 購物車狀態(tài)管理 Store
// 該文件演示了如何通過事件總線監(jiān)聽事件
// 設計意圖:監(jiān)聽用戶登錄事件,同步購物車數(shù)據(jù)
import { defineStore } from 'pinia'
import { eventBus } from '@/utils/eventBus'
/**
* 購物車狀態(tài)管理 Store
* @returns 購物車狀態(tài)管理 Store 實例
*/
export const useCartStore = defineStore('cart', () => {
/**
* 初始化購物車
* 監(jiān)聽用戶登錄事件,以便同步購物車數(shù)據(jù)
* 演示了如何通過事件總線監(jiān)聽事件
*/
function initialize() {
// 監(jiān)聽用戶登錄事件
// 事件名稱:'user:login',回調函數(shù)處理事件數(shù)據(jù)
eventBus.on('user:login', (data) => {
// 處理用戶登錄事件
console.log('User logged in:', data.userId)
// 可以在這里同步購物車數(shù)據(jù)
// 例如:將訪客購物車數(shù)據(jù)同步到登錄用戶的購物車
})
}
return {
initialize
}
})
3. 使用 Composition API 共享邏輯
將共享邏輯提取到 composables 中,供多個 Store 使用。
// composables/useApi.ts
// API 調用邏輯
// 該文件演示了如何通過 Composition API 封裝共享邏輯
// 優(yōu)點:邏輯復用性高,Store 之間耦合度低
// 設計意圖:封裝 API 請求的通用邏輯,供多個 Store 和組件復用
import { ref } from 'vue'
/**
* API 調用邏輯
* 封裝了 API 請求的通用邏輯,如加載狀態(tài)、錯誤處理等
* 使用 Composition API 風格,提供更好的代碼組織和復用性
* @returns API 調用相關的狀態(tài)和方法
*/
export function useApi() {
const loading = ref(false) // 加載狀態(tài),用于控制加載指示器的顯示
const error = ref<string | null>(null) // 錯誤信息,用于顯示錯誤提示
/**
* 發(fā)起 API 請求
* 封裝了 API 請求的通用邏輯,包括加載狀態(tài)管理和錯誤處理
* @param apiCall API 調用函數(shù),返回 Promise
* @returns API 響應數(shù)據(jù)
* @throws API 請求失敗時拋出錯誤
*/
async function request<T>(apiCall: () => Promise<T>): Promise<T> {
loading.value = true // 開始加載
error.value = null // 清空錯誤信息
try {
return await apiCall() // 執(zhí)行 API 調用
} catch (err) {
error.value = 'API request failed' // 設置錯誤信息
throw err // 重新拋出錯誤,以便調用方處理
} finally {
loading.value = false // 結束加載
}
}
return {
loading, // 加載狀態(tài)
error, // 錯誤信息
request // API 請求方法
}
}
// stores/user.ts
// 用戶狀態(tài)管理 Store
// 該文件演示了如何通過 Composition API 共享邏輯
// 設計意圖:通過使用共享的 useApi composable,減少代碼重復,提高可維護性
import { defineStore } from 'pinia'
import { useApi } from '@/composables/useApi'
import { userApi } from '@/services/api/user' // 假設已導入
/**
* 用戶狀態(tài)管理 Store
* @returns 用戶狀態(tài)管理 Store 實例
*/
export const useUserStore = defineStore('user', () => {
// 使用共享的 API 邏輯
// 優(yōu)點:代碼復用,減少重復邏輯,Store 之間耦合度低
const { loading, error, request } = useApi()
/**
* 用戶登錄
* 使用共享的 request 方法發(fā)起登錄請求
* 演示了如何使用共享的 composable 邏輯
* @param email 郵箱
* @param password 密碼
* @returns 登錄響應,包含用戶信息和令牌
* @throws 登錄失敗時拋出錯誤
*/
async function login(email: string, password: string) {
return request(() => userApi.login({ email, password }))
}
return {
loading, // 加載狀態(tài)
error, // 錯誤信息
login // 登錄方法
}
})
3.3 狀態(tài)持久化策略
對于需要持久化的狀態(tài),如用戶認證信息、用戶偏好設置等,可以使用以下策略:
1. 使用 localStorage/sessionStorage
// stores/user.ts
// 用戶狀態(tài)管理 Store
// 該文件演示了如何通過 localStorage 實現(xiàn)狀態(tài)持久化
// 優(yōu)點:實現(xiàn)簡單直接,適用于基本的狀態(tài)持久化需求
// 缺點:需要手動管理存儲邏輯,代碼冗余
// 設計意圖:通過 localStorage 實現(xiàn)狀態(tài)持久化,保持用戶登錄狀態(tài)
import { defineStore } from 'pinia'
import { ref } from 'vue'
/**
* 用戶狀態(tài)管理 Store
* @returns 用戶狀態(tài)管理 Store 實例
*/
export const useUserStore = defineStore('user', () => {
// 從 localStorage 初始化令牌
// 在 Store 初始化時,從 localStorage 中讀取之前存儲的令牌
const token = ref<string | null>(localStorage.getItem('token'))
/**
* 設置令牌
* 同時更新 localStorage 中的令牌,實現(xiàn)狀態(tài)持久化
* @param newToken 新令牌,字符串或 null
*/
function setToken(newToken: string | null) {
token.value = newToken // 更新內存中的令牌狀態(tài)
if (newToken) {
localStorage.setItem('token', newToken) // 存儲令牌到 localStorage
} else {
localStorage.removeItem('token') // 清除 localStorage 中的令牌
}
}
return {
token, // 令牌狀態(tài)
setToken // 設置令牌的方法
}
})
2. 使用 pinia-plugin-persistedstate
// stores/user.ts
// 用戶狀態(tài)管理 Store
// 該文件演示了如何通過 pinia-plugin-persistedstate 實現(xiàn)狀態(tài)持久化
// 優(yōu)點:配置簡單,自動管理存儲邏輯,代碼簡潔
// 缺點:需要安裝額外的插件
// 設計意圖:通過 pinia-plugin-persistedstate 插件實現(xiàn)狀態(tài)持久化,簡化存儲邏輯
import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { User } from '@/models/user' // 假設已導入
/**
* 用戶狀態(tài)管理 Store
* @returns 用戶狀態(tài)管理 Store 實例
*/
export const useUserStore = defineStore('user', () => {
const currentUser = ref<User | null>(null) // 當前用戶信息,類型為 User 或 null
const token = ref<string | null>(null) // 認證令牌,用于 API 調用的身份驗證
/**
* 用戶登錄
* @param user 用戶信息,類型為 User
* @param userToken 認證令牌,字符串
*/
function login(user: User, userToken: string) {
currentUser.value = user // 更新當前用戶信息
token.value = userToken // 更新認證令牌
// 由于配置了 persist,狀態(tài)會自動持久化到 localStorage
}
/**
* 用戶登出
*/
function logout() {
currentUser.value = null // 清空當前用戶信息
token.value = null // 清空認證令牌
// 由于配置了 persist,狀態(tài)會自動從 localStorage 中移除
}
return {
currentUser, // 當前用戶信息
token, // 認證令牌
login, // 登錄方法
logout // 登出方法
}
}, {
persist: {
key: 'user-store', // 存儲鍵名,用于在 localStorage 中標識存儲的數(shù)據(jù)
storage: localStorage, // 存儲方式,使用 localStorage 實現(xiàn)持久化
paths: ['currentUser', 'token'] // 需要持久化的狀態(tài)路徑,只持久化 currentUser 和 token
}
})
4. 路由架構
4.1 路由配置組織
一個合理的路由配置能夠清晰地定義應用的導航結構,便于開發(fā)者理解和維護。
路由配置文件結構
// router/index.ts
// 路由主配置文件
// 該文件負責創(chuàng)建路由實例并應用全局守衛(wèi)
// 設計意圖:配置和初始化 Vue Router,應用全局守衛(wèi)
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'
import { routes } from './routes'
import { authGuard } from './guards'
/**
* 創(chuàng)建路由實例
* 配置路由的歷史模式和路由表
* @returns 路由實例
*/
const router = createRouter({
history: createWebHistory(), // 使用 HTML5 History API,移除 URL 中的 # 符號
routes // 路由配置,從 ./routes 文件導入
})
/**
* 全局前置守衛(wèi)
* 應用認證守衛(wèi),處理所有路由的認證和權限檢查
*/
router.beforeEach(authGuard)
export default router
// router/routes.ts
// 路由定義文件
// 該文件負責定義應用的所有路由
// 設計意圖:集中管理應用的路由配置,包括路由路徑、組件映射、元信息等
import { RouteRecordRaw } from 'vue-router'
import DefaultLayout from '@/layouts/DefaultLayout.vue'
import AuthLayout from '@/layouts/AuthLayout.vue'
/**
* 路由元信息接口
* 定義路由的額外信息,如認證要求、頁面標題、角色權限等
*/
export interface RouteMeta {
/**
* 是否需要認證
* @default true
*/
requiresAuth?: boolean
/**
* 頁面標題
*/
title?: string
/**
* 角色權限
* 只有具有指定角色的用戶才能訪問該路由
*/
roles?: string[]
/**
* 是否在側邊欄顯示
*/
sidebar?: boolean
/**
* 側邊欄圖標
* 用于側邊欄導航的圖標名稱
*/
icon?: string
}
/**
* 擴展的路由記錄類型
* 集成自定義的元信息接口,提供更好的類型支持
*/
export type AppRouteRecordRaw = RouteRecordRaw & {
meta?: RouteMeta
children?: AppRouteRecordRaw[]
}
/**
* 路由配置數(shù)組
* 定義應用的所有路由,包括布局、頁面組件、元信息等
*/
export const routes: AppRouteRecordRaw[] = [
{
path: '/',
component: DefaultLayout, // 使用默認布局(包含側邊欄和頭部)
meta: {
requiresAuth: true // 需要認證
},
children: [
{
path: '',
name: 'Dashboard',
component: () => import('@/views/dashboard/index.vue'), // 懶加載儀表盤頁面
meta: {
title: '儀表盤',
sidebar: true, // 在側邊欄顯示
icon: 'dashboard' // 側邊欄圖標
}
},
{
path: 'products',
name: 'Products',
component: () => import('@/views/products/index.vue'), // 懶加載產(chǎn)品管理頁面
meta: {
title: '產(chǎn)品管理',
sidebar: true, // 在側邊欄顯示
icon: 'shopping-cart' // 側邊欄圖標
}
},
{
path: 'users',
name: 'Users',
component: () => import('@/views/users/index.vue'), // 懶加載用戶管理頁面
meta: {
title: '用戶管理',
sidebar: true, // 在側邊欄顯示
icon: 'users', // 側邊欄圖標
roles: ['admin'] // 只有 admin 角色可以訪問
}
}
]
},
{
path: '/auth',
component: AuthLayout, // 使用認證布局(簡潔布局,無側邊欄)
meta: {
requiresAuth: false // 不需要認證
},
children: [
{
path: 'login',
name: 'Login',
component: () => import('@/views/auth/login.vue'), // 懶加載登錄頁面
meta: {
title: '登錄'
}
},
{
path: 'register',
name: 'Register',
component: () => import('@/views/auth/register.vue'), // 懶加載注冊頁面
meta: {
title: '注冊'
}
}
]
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/error/404.vue'), // 懶加載 404 頁面
meta: {
title: '頁面不存在'
}
}
]
// router/guards.ts
// 路由守衛(wèi)文件
// 該文件負責定義路由守衛(wèi)邏輯
// 設計意圖:實現(xiàn)路由的認證和權限控制,保護需要認證的路由
import { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
import { useUserStore } from '@/stores/user'
/**
* 認證守衛(wèi)
* 處理路由的認證和權限檢查
* 在每次路由導航前執(zhí)行,確保用戶有足夠的權限訪問目標路由
* @param to 目標路由對象
* @param from 來源路由對象
* @param next 導航函數(shù),決定導航是否繼續(xù)
*/
export function authGuard(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
const userStore = useUserStore() // 獲取用戶狀態(tài)管理 Store
// 設置頁面標題
if (to.meta.title) {
document.title = `${to.meta.title} - My App`
}
// 權限檢查
const requiresAuth = to.meta.requiresAuth !== false // 默認需要認證
const isAuthenticated = userStore.isAuthenticated // 用戶是否已認證
const roles = to.meta.roles || [] // 路由所需的角色權限
if (requiresAuth && !isAuthenticated) {
// 未登錄,重定向到登錄頁
next({ name: 'Login' })
} else if (roles.length > 0 && !roles.some(role => userStore.hasRole(role))) {
// 無權限,重定向到 403 頁面
next({ name: 'Forbidden' })
} else {
// 已登錄或不需要認證,繼續(xù)導航
next()
}
}
4.2 路由模塊化
對于大型應用,可以將路由配置按照模塊進行拆分,提高代碼的可維護性。
// router/modules/auth.ts
// 認證模塊路由配置
// 設計意圖:將認證相關的路由配置拆分到獨立的模塊,提高代碼可維護性
import { AppRouteRecordRaw } from '../types'
/**
* 認證模塊路由配置
* 包含登錄、注冊等認證相關頁面的路由
*/
export const authRoutes: AppRouteRecordRaw[] = [
{
path: '/auth',
component: () => import('@/layouts/AuthLayout.vue'), // 懶加載認證布局
meta: {
requiresAuth: false // 認證頁面不需要認證
},
children: [
{
path: 'login',
name: 'Login',
component: () => import('@/views/auth/login.vue'), // 懶加載登錄頁面
meta: {
title: '登錄'
}
},
{
path: 'register',
name: 'Register',
component: () => import('@/views/auth/register.vue'), // 懶加載注冊頁面
meta: {
title: '注冊'
}
}
]
}
]
// router/modules/dashboard.ts
// 儀表盤模塊路由配置
// 設計意圖:將儀表盤相關的路由配置拆分到獨立的模塊,提高代碼可維護性
import { AppRouteRecordRaw } from '../types'
/**
* 儀表盤模塊路由配置
* 包含儀表盤頁面的路由
*/
export const dashboardRoutes: AppRouteRecordRaw[] = [
{
path: '/dashboard',
component: () => import('@/layouts/DefaultLayout.vue'), // 懶加載默認布局
meta: {
requiresAuth: true // 需要認證
},
children: [
{
path: '',
name: 'Dashboard',
component: () => import('@/views/dashboard/index.vue'), // 懶加載儀表盤頁面
meta: {
title: '儀表盤',
sidebar: true, // 在側邊欄顯示
icon: 'dashboard' // 側邊欄圖標
}
}
]
}
]
// router/routes.ts
// 路由配置主文件
// 設計意圖:組合所有路由模塊,形成完整的應用路由配置
import { AppRouteRecordRaw } from './types'
import { authRoutes } from './modules/auth' // 認證模塊路由
import { dashboardRoutes } from './modules/dashboard' // 儀表盤模塊路由
import { productRoutes } from './modules/product' // 產(chǎn)品模塊路由
import { userRoutes } from './modules/user' // 用戶模塊路由
/**
* 路由配置數(shù)組
* 組合所有路由模塊,形成完整的應用路由配置
*/
export const routes: AppRouteRecordRaw[] = [
...authRoutes, // 認證模塊路由
...dashboardRoutes, // 儀表盤模塊路由
...productRoutes, // 產(chǎn)品模塊路由
...userRoutes, // 用戶模塊路由
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
component: () => import('@/views/error/404.vue'), // 懶加載 404 頁面
meta: {
title: '頁面不存在'
}
}
]
4.3 路由守衛(wèi)策略
路由守衛(wèi)是控制路由訪問權限的重要手段,合理使用路由守衛(wèi)能夠提高應用的安全性和用戶體驗。
全局守衛(wèi)
全局守衛(wèi)適用于所有路由,可以用于處理認證、權限檢查、頁面標題設置等通用邏輯。
// router/guards.ts
// 路由守衛(wèi)文件
// 設計意圖:實現(xiàn)路由的認證和權限控制,保護需要認證的路由
import { NavigationGuardNext, RouteLocationNormalized } from 'vue-router'
import { useUserStore } from '@/stores/user'
/**
* 認證守衛(wèi)
* 全局前置守衛(wèi),處理所有路由的認證和權限檢查
* @param to 目標路由對象
* @param from 來源路由對象
* @param next 導航函數(shù),決定導航是否繼續(xù)
*/
export function authGuard(to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) {
const userStore = useUserStore() // 獲取用戶狀態(tài)管理 Store
// 設置頁面標題
if (to.meta.title) {
document.title = `${to.meta.title} - My App`
}
// 權限檢查
const requiresAuth = to.meta.requiresAuth !== false // 默認需要認證
const isAuthenticated = userStore.isAuthenticated // 用戶是否已認證
const roles = to.meta.roles || [] // 路由所需的角色權限
if (requiresAuth && !isAuthenticated) {
// 未登錄,重定向到登錄頁
next({ name: 'Login' })
} else if (roles.length > 0 && !roles.some(role => userStore.hasRole(role))) {
// 無權限,重定向到 403 頁面
next({ name: 'Forbidden' })
} else {
// 已登錄或不需要認證,繼續(xù)導航
next()
}
}
/**
* 全局后置守衛(wèi)
* 在路由導航完成后執(zhí)行,用于處理頁面加載完成后的邏輯
* @param to 目標路由對象
* @param from 來源路由對象
*/
export function globalAfterGuard(to: RouteLocationNormalized, from: RouteLocationNormalized) {
// 頁面加載完成后的邏輯,如埋點統(tǒng)計、頁面性能監(jiān)控等
console.log(`Navigated from ${from.path} to ${to.path}`)
}
路由獨享守衛(wèi)
路由獨享守衛(wèi)只適用于特定路由,可以用于處理該路由的特殊邏輯。
// router/routes.ts
// 路由定義文件
// 設計意圖:演示如何使用路由獨享守衛(wèi)處理特定路由的特殊邏輯
import { AppRouteRecordRaw } from './types'
/**
* 路由配置數(shù)組
*/
export const routes: AppRouteRecordRaw[] = [
{
path: '/admin',
name: 'Admin',
component: () => import('@/views/admin/index.vue'), // 懶加載管理員頁面
meta: {
requiresAuth: true, // 需要認證
roles: ['admin'] // 只有 admin 角色可以訪問
},
beforeEnter: (to, from, next) => {
// 路由獨享守衛(wèi)邏輯
// 只適用于當前路由,用于處理該路由的特殊邏輯
console.log('Entering admin route')
// 可以在這里添加額外的權限檢查或其他邏輯
next() // 繼續(xù)導航
}
}
]
組件內守衛(wèi)
組件內守衛(wèi)適用于組件級別,可以用于處理組件的進入、離開等邏輯。
<script setup lang="ts">
// 組件內守衛(wèi)示例
// 設計意圖:演示如何在組件級別使用路由守衛(wèi),處理組件的進入、離開等邏輯
import { onBeforeRouteEnter, onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
/**
* 路由進入組件前
* 在路由進入組件之前執(zhí)行,此時組件實例還未創(chuàng)建
* @param to 目標路由對象
* @param from 來源路由對象
* @param next 導航函數(shù),決定導航是否繼續(xù)
*/
onBeforeRouteEnter((to, from, next) => {
console.log('Before route enter')
// 可以在這里進行數(shù)據(jù)預加載、權限檢查等
// 例如:獲取組件需要的數(shù)據(jù),避免組件渲染時出現(xiàn)空白
next() // 繼續(xù)導航
})
/**
* 路由離開組件前
* 在路由離開組件之前執(zhí)行,此時組件實例仍然存在
* @param to 目標路由對象
* @param from 來源路由對象
* @param next 導航函數(shù),決定導航是否繼續(xù)
*/
onBeforeRouteLeave((to, from, next) => {
console.log('Before route leave')
// 可以在這里進行表單驗證、確認提示等
// 例如:檢查用戶是否有未保存的修改,提示用戶確認離開
next() // 繼續(xù)導航
})
/**
* 路由更新但組件被復用時
* 在路由更新但組件被復用時執(zhí)行,例如從 /user/1 導航到 /user/2
* @param to 目標路由對象
* @param from 來源路由對象
* @param next 導航函數(shù),決定導航是否繼續(xù)
*/
onBeforeRouteUpdate((to, from, next) => {
console.log('Before route update')
// 可以在這里更新組件數(shù)據(jù)、重新獲取數(shù)據(jù)等
// 例如:根據(jù)新的路由參數(shù)重新獲取用戶信息
next() // 繼續(xù)導航
})
</script>
5. API 層設計
5.1 API 服務組織
一個合理的 API 層設計能夠清晰地管理與后端的通信,提高代碼的可維護性和可測試性。
API 服務文件結構
// services/api/index.ts
// API 客戶端配置文件
// 設計意圖:創(chuàng)建和配置 axios 實例,設置請求/響應攔截器,統(tǒng)一處理 API 調用
import axios from 'axios'
import { useUserStore } from '@/stores/user'
/**
* 創(chuàng)建 axios 實例
* 配置基礎 URL、超時時間、默認請求頭等
*/
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // API 基礎 URL,從環(huán)境變量獲取或使用默認值
timeout: 10000, // 請求超時時間,10秒
headers: {
'Content-Type': 'application/json' // 默認請求頭,使用 JSON 格式
}
})
/**
* 請求攔截器
* 在發(fā)送請求之前執(zhí)行,用于添加認證令牌、請求日志等
*/
apiClient.interceptors.request.use(
(config) => {
const userStore = useUserStore() // 獲取用戶狀態(tài)管理 Store
const token = userStore.token // 獲取認證令牌
if (token) {
// 如果有令牌,添加到請求頭的 Authorization 字段
config.headers.Authorization = `Bearer ${token}`
}
return config // 返回修改后的配置
},
(error) => {
// 請求錯誤處理
return Promise.reject(error)
}
)
/**
* 響應攔截器
* 在收到響應之后執(zhí)行,用于統(tǒng)一處理響應數(shù)據(jù)、錯誤處理等
*/
apiClient.interceptors.response.use(
(response) => {
// 只返回響應數(shù)據(jù),忽略其他響應信息
return response.data
},
(error) => {
// 錯誤處理
if (error.response?.status === 401) {
// 401 未授權錯誤,清除用戶登錄狀態(tài)
const userStore = useUserStore()
userStore.logout()
}
return Promise.reject(error) // 重新拋出錯誤,以便調用方處理
}
)
export default apiClient
// services/api/user.ts
// 用戶 API 服務
// 設計意圖:封裝用戶相關的 API 調用,提供類型安全的 API 服務
import apiClient from './index'
import { User, LoginRequest, RegisterRequest } from '@/models/user'
/**
* 用戶 API 服務
* 封裝用戶相關的 API 調用,包括登錄、注冊、獲取用戶信息、更新用戶信息等
*/
export const userApi = {
/**
* 用戶登錄
* @param data 登錄數(shù)據(jù),包含郵箱和密碼
* @returns 登錄響應,包含用戶信息和認證令牌
*/
login: (data: LoginRequest) => {
return apiClient.post<{ user: User; token: string }>('/auth/login', data)
},
/**
* 用戶注冊
* @param data 注冊數(shù)據(jù),包含用戶基本信息
* @returns 注冊響應,包含用戶信息和認證令牌
*/
register: (data: RegisterRequest) => {
return apiClient.post<{ user: User; token: string }>('/auth/register', data)
},
/**
* 獲取當前用戶信息
* @returns 當前用戶信息
*/
getCurrentUser: () => {
return apiClient.get<User>('/users/me')
},
/**
* 更新用戶信息
* @param data 用戶信息,部分更新
* @returns 更新后的用戶信息
*/
updateUser: (data: Partial<User>) => {
return apiClient.put<User>('/users/me', data)
}
}
// services/api/product.ts
// 產(chǎn)品 API 服務
// 設計意圖:封裝產(chǎn)品相關的 API 調用,提供類型安全的 API 服務
import apiClient from './index'
import { Product, CreateProductRequest, UpdateProductRequest } from '@/models/product'
/**
* 產(chǎn)品 API 服務
* 封裝產(chǎn)品相關的 API 調用,包括獲取產(chǎn)品列表、獲取產(chǎn)品詳情、創(chuàng)建產(chǎn)品、更新產(chǎn)品、刪除產(chǎn)品等
*/
export const productApi = {
/**
* 獲取產(chǎn)品列表
* @param params 查詢參數(shù),包含分頁和篩選條件
* @returns 產(chǎn)品列表
*/
getProducts: (params?: { page?: number; limit?: number; category?: string }) => {
return apiClient.get<Product[]>('/products', { params })
},
/**
* 獲取產(chǎn)品詳情
* @param id 產(chǎn)品 ID
* @returns 產(chǎn)品詳情
*/
getProduct: (id: number) => {
return apiClient.get<Product>(`/products/${id}`)
},
/**
* 創(chuàng)建產(chǎn)品
* @param data 產(chǎn)品數(shù)據(jù),包含產(chǎn)品基本信息
* @returns 創(chuàng)建的產(chǎn)品
*/
createProduct: (data: CreateProductRequest) => {
return apiClient.post<Product>('/products', data)
},
/**
* 更新產(chǎn)品
* @param id 產(chǎn)品 ID
* @param data 產(chǎn)品數(shù)據(jù),部分更新
* @returns 更新后的產(chǎn)品
*/
updateProduct: (id: number, data: UpdateProductRequest) => {
return apiClient.put<Product>(`/products/${id}`, data)
},
/**
* 刪除產(chǎn)品
* @param id 產(chǎn)品 ID
* @returns 刪除結果
*/
deleteProduct: (id: number) => {
return apiClient.delete(`/products/${id}`)
}
}
5.2 API 錯誤處理策略
合理的錯誤處理策略能夠提高應用的穩(wěn)定性和用戶體驗,以下是幾種常見的錯誤處理方式:
全局錯誤處理
通過 axios 響應攔截器統(tǒng)一處理 API 錯誤。
// services/api/index.ts
// API 客戶端配置文件
// 設計意圖:通過響應攔截器實現(xiàn)全局錯誤處理,統(tǒng)一處理不同類型的 API 錯誤
import axios from 'axios'
import { useUserStore } from '@/stores/user'
import { ElMessage } from 'element-plus'
/**
* 響應攔截器
* 統(tǒng)一處理 API 錯誤,根據(jù)錯誤狀態(tài)碼顯示不同的錯誤提示
*/
apiClient.interceptors.response.use(
(response) => {
return response.data // 只返回響應數(shù)據(jù)
},
(error) => {
// 處理網(wǎng)絡錯誤(無響應)
if (!error.response) {
ElMessage.error('網(wǎng)絡連接失敗,請檢查網(wǎng)絡設置')
return Promise.reject(error)
}
// 處理 401 錯誤(未授權)
if (error.response.status === 401) {
const userStore = useUserStore()
userStore.logout() // 清除用戶登錄狀態(tài)
ElMessage.error('登錄已過期,請重新登錄')
return Promise.reject(error)
}
// 處理 403 錯誤(禁止訪問)
if (error.response.status === 403) {
ElMessage.error('沒有權限訪問該資源')
return Promise.reject(error)
}
// 處理 404 錯誤(資源不存在)
if (error.response.status === 404) {
ElMessage.error('請求的資源不存在')
return Promise.reject(error)
}
// 處理 500 錯誤(服務器錯誤)
if (error.response.status >= 500) {
ElMessage.error('服務器錯誤,請稍后重試')
return Promise.reject(error)
}
// 處理其他錯誤
const errorMessage = error.response.data?.message || '請求失敗,請稍后重試'
ElMessage.error(errorMessage)
return Promise.reject(error)
}
)
局部錯誤處理
在具體的 API 調用處進行錯誤處理,適用于需要特殊處理的場景。
// composables/useAuth.ts
// 認證相關邏輯
// 設計意圖:在具體的 API 調用處進行錯誤處理,適用于需要特殊處理的場景
import { ref } from 'vue'
import { userApi } from '@/services/api/user'
import { LoginRequest, RegisterRequest } from '@/models/user'
/**
* 認證相關邏輯
* 封裝登錄、注冊等認證操作,并在具體的 API 調用處進行錯誤處理
*/
export function useAuth() {
const loading = ref(false) // 加載狀態(tài)
const error = ref<string | null>(null) // 錯誤信息
/**
* 用戶登錄
* 在具體的 API 調用處進行錯誤處理,提供更具體的錯誤提示
* @param email 郵箱
* @param password 密碼
* @returns 登錄響應
* @throws 登錄失敗時拋出錯誤
*/
async function login(email: string, password: string) {
loading.value = true // 開始加載
error.value = null // 清空錯誤信息
try {
const response = await userApi.login({ email, password }) // 調用登錄 API
return response // 返回登錄響應
} catch (err: any) {
// 自定義錯誤信息,提供更具體的錯誤提示
error.value = err.response?.data?.message || '登錄失敗,請檢查郵箱和密碼'
throw err // 重新拋出錯誤,以便調用方處理
} finally {
loading.value = false // 結束加載
}
}
/**
* 用戶注冊
* 在具體的 API 調用處進行錯誤處理,提供更具體的錯誤提示
* @param userData 注冊數(shù)據(jù)
* @returns 注冊響應
* @throws 注冊失敗時拋出錯誤
*/
async function register(userData: RegisterRequest) {
loading.value = true // 開始加載
error.value = null // 清空錯誤信息
try {
const response = await userApi.register(userData) // 調用注冊 API
return response // 返回注冊響應
} catch (err: any) {
// 自定義錯誤信息,提供更具體的錯誤提示
error.value = err.response?.data?.message || '注冊失敗,請稍后重試'
throw err // 重新拋出錯誤,以便調用方處理
} finally {
loading.value = false // 結束加載
}
}
return {
loading, // 加載狀態(tài)
error, // 錯誤信息
login, // 登錄方法
register // 注冊方法
}
}
5.3 API 數(shù)據(jù)緩存策略
合理的 API 數(shù)據(jù)緩存策略能夠減少重復請求,提高應用的性能和用戶體驗。
內存緩存
使用內存緩存存儲臨時數(shù)據(jù),適用于單次會話中的數(shù)據(jù)。
// services/api/product.ts
// 產(chǎn)品 API 服務
// 設計意圖:通過內存緩存減少重復請求,提高應用性能和用戶體驗
import apiClient from './index'
import { Product } from '@/models/product'
// 產(chǎn)品列表緩存
// 使用內存變量存儲產(chǎn)品列表數(shù)據(jù)和緩存時間戳
let productsCache: { data: Product[]; timestamp: number } | null = null
const CACHE_DURATION = 5 * 60 * 1000 // 緩存持續(xù)時間,5 分鐘
/**
* 產(chǎn)品 API 服務
*/
export const productApi = {
/**
* 獲取產(chǎn)品列表
* 使用內存緩存減少重復請求,提高應用性能
* @param params 查詢參數(shù),包含分頁和篩選條件
* @returns 產(chǎn)品列表
*/
getProducts: async (params?: { page?: number; limit?: number; category?: string }) => {
// 檢查緩存是否有效
// 如果緩存存在且未過期,直接返回緩存數(shù)據(jù)
if (productsCache && Date.now() - productsCache.timestamp < CACHE_DURATION) {
return productsCache.data
}
// 緩存無效或不存在,發(fā)起 API 請求
const data = await apiClient.get<Product[]>('/products', { params })
// 更新緩存
// 存儲響應數(shù)據(jù)和當前時間戳
productsCache = {
data,
timestamp: Date.now()
}
return data
},
/**
* 清除產(chǎn)品列表緩存
* 在產(chǎn)品數(shù)據(jù)發(fā)生變化時調用,確保下次獲取最新數(shù)據(jù)
*/
clearProductsCache: () => {
productsCache = null
}
}
本地存儲緩存
使用 localStorage 或 sessionStorage 存儲持久化數(shù)據(jù),適用于需要跨會話保持的數(shù)據(jù)。
// services/api/user.ts
// 用戶 API 服務
// 設計意圖:通過本地存儲緩存實現(xiàn)數(shù)據(jù)持久化,適用于需要跨會話保持的數(shù)據(jù)
import apiClient from './index'
import { User } from '@/models/user'
const USER_CACHE_KEY = 'user_cache' // 本地存儲的緩存鍵名
const CACHE_DURATION = 24 * 60 * 60 * 1000 // 緩存持續(xù)時間,24 小時
/**
* 用戶 API 服務
*/
export const userApi = {
/**
* 獲取當前用戶信息
* 使用本地存儲緩存實現(xiàn)數(shù)據(jù)持久化,減少重復請求
* @returns 用戶信息
*/
getCurrentUser: async () => {
// 檢查緩存
const cachedUser = localStorage.getItem(USER_CACHE_KEY)
if (cachedUser) {
const { data, timestamp } = JSON.parse(cachedUser)
// 如果緩存存在且未過期,直接返回緩存數(shù)據(jù)
if (Date.now() - timestamp < CACHE_DURATION) {
return data
}
}
// 緩存無效或不存在,發(fā)起 API 請求
const data = await apiClient.get<User>('/users/me')
// 更新緩存
// 將響應數(shù)據(jù)和當前時間戳存儲到本地存儲
localStorage.setItem(USER_CACHE_KEY, JSON.stringify({
data,
timestamp: Date.now()
}))
return data
},
/**
* 清除用戶緩存
* 在用戶信息發(fā)生變化時調用,確保下次獲取最新數(shù)據(jù)
*/
clearUserCache: () => {
localStorage.removeItem(USER_CACHE_KEY)
}
}
6. 工具類和公共組件
6.1 工具函數(shù)組織
工具函數(shù)是項目中常用的輔助函數(shù),合理組織工具函數(shù)能夠提高代碼的復用性和可維護性。
工具函數(shù)目錄結構
src/utils/ ├── format.ts # 格式化工具 ├── validation.ts # 驗證工具 ├── storage.ts # 存儲工具 ├── http.ts # HTTP 工具 ├── date.ts # 日期工具 ├── number.ts # 數(shù)字工具 ├── string.ts # 字符串工具 ├── array.ts # 數(shù)組工具 └── object.ts # 對象工具
工具函數(shù)示例
// utils/format.ts
// 格式化工具函數(shù)
// 設計意圖:提供常用的格式化功能,如金額格式化、日期格式化等
/**
* 格式化金額
* 將數(shù)字金額格式化為帶有貨幣符號和千分位的字符串
* @param amount 金額,數(shù)字類型
* @param currency 貨幣符號,默認為 ¥
* @returns 格式化后的金額字符串,如 "¥1,234.56"
*/
export function formatCurrency(amount: number, currency: string = '¥'): string {
// 使用 toFixed(2) 保留兩位小數(shù),然后使用正則表達式添加千分位
return `${currency}${amount.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`
}
/**
* 格式化日期
* 將日期對象、字符串或時間戳格式化為指定格式的日期字符串
* @param date 日期,可以是 Date 對象、日期字符串或時間戳
* @param format 格式化模板,默認為 'YYYY-MM-DD'
* @returns 格式化后的日期字符串,如 "2023-12-25"
*/
export function formatDate(date: Date | string | number, format: string = 'YYYY-MM-DD'): string {
const d = new Date(date) // 創(chuàng)建 Date 對象
const year = d.getFullYear() // 獲取年份
const month = String(d.getMonth() + 1).padStart(2, '0') // 獲取月份(0-11,所以加 1),并補零
const day = String(d.getDate()).padStart(2, '0') // 獲取日期,并補零
const hours = String(d.getHours()).padStart(2, '0') // 獲取小時,并補零
const minutes = String(d.getMinutes()).padStart(2, '0') // 獲取分鐘,并補零
const seconds = String(d.getSeconds()).padStart(2, '0') // 獲取秒數(shù),并補零
// 替換格式化模板中的占位符
return format
.replace('YYYY', String(year))
.replace('MM', month)
.replace('DD', day)
.replace('HH', hours)
.replace('mm', minutes)
.replace('ss', seconds)
}
// utils/validation.ts
// 驗證工具函數(shù)
// 設計意圖:提供常用的驗證功能,如郵箱驗證、密碼強度驗證等
/**
* 驗證郵箱
* 檢查郵箱地址是否符合標準格式
* @param email 郵箱地址,字符串類型
* @returns 是否為有效郵箱,布爾值
*/
export function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ // 簡單的郵箱格式正則表達式
return emailRegex.test(email) // 測試郵箱地址是否匹配正則表達式
}
/**
* 驗證密碼強度
* 根據(jù)密碼長度、包含的字符類型等計算密碼強度等級
* @param password 密碼,字符串類型
* @returns 密碼強度等級 (0-4),數(shù)字越大表示密碼強度越高
*/
export function getPasswordStrength(password: string): number {
let strength = 0 // 初始強度為 0
if (password.length >= 8) strength++ // 長度至少 8 位
if (/[A-Z]/.test(password)) strength++ // 包含大寫字母
if (/[a-z]/.test(password)) strength++ // 包含小寫字母
if (/[0-9]/.test(password)) strength++ // 包含數(shù)字
if (/[^A-Za-z0-9]/.test(password)) strength++ // 包含特殊字符
return strength // 返回密碼強度等級
}
// utils/storage.ts
// 存儲工具函數(shù)
// 設計意圖:封裝本地存儲操作,提供類型安全的存儲工具函數(shù)
/**
* 設置本地存儲
* 將值存儲到 localStorage 中,自動進行 JSON 序列化
* @param key 存儲鍵,字符串類型
* @param value 存儲值,可以是任何可 JSON 序列化的類型
*/
export function setLocalStorage(key: string, value: any): void {
try {
// 將值轉換為 JSON 字符串并存儲
localStorage.setItem(key, JSON.stringify(value))
} catch (error) {
// 捕獲并記錄錯誤,避免存儲失敗導致應用崩潰
console.error('Error setting localStorage:', error)
}
}
/**
* 獲取本地存儲
* 從 localStorage 中獲取值,自動進行 JSON 反序列化
* @param key 存儲鍵,字符串類型
* @param defaultValue 默認值,當存儲不存在或解析失敗時返回
* @returns 存儲值或默認值,類型為 T
*/
export function getLocalStorage<T>(key: string, defaultValue: T): T {
try {
const value = localStorage.getItem(key) // 獲取存儲的 JSON 字符串
return value ? JSON.parse(value) : defaultValue // 解析 JSON 字符串,失敗則返回默認值
} catch (error) {
// 捕獲并記錄錯誤,返回默認值
console.error('Error getting localStorage:', error)
return defaultValue
}
}
/**
* 刪除本地存儲
* 從 localStorage 中刪除指定的存儲項
* @param key 存儲鍵,字符串類型
*/
export function removeLocalStorage(key: string): void {
try {
localStorage.removeItem(key) // 刪除存儲項
} catch (error) {
// 捕獲并記錄錯誤
console.error('Error removing localStorage:', error)
}
}
6.2 公共組件組織
公共組件是項目中可復用的 UI 組件,合理組織公共組件能夠提高開發(fā)效率和 UI 一致性。
公共組件目錄結構
src/components/
├── common/ # 通用基礎組件
│ ├── Button.vue # 按鈕組件
│ ├── Input.vue # 輸入框組件
│ ├── Dialog.vue # 對話框組件
│ └── Loading.vue # 加載組件
├── layout/ # 布局相關組件
│ ├── Header.vue # 頭部組件
│ ├── Sidebar.vue # 側邊欄組件
│ └── Footer.vue # 底部組件
└── business/ # 業(yè)務相關組件
├── UserCard.vue # 用戶卡片組件
├── ProductList.vue # 產(chǎn)品列表組件
└── OrderForm.vue # 訂單表單組件
公共組件設計原則
- 單一職責:每個組件只負責一個功能,便于理解和維護
- 可配置性:通過 props 提供足夠的配置選項,提高組件的靈活性
- 可擴展性:設計合理的組件結構,便于后續(xù)功能的擴展
- 類型安全:充分利用 TypeScript 的類型系統(tǒng),為組件定義明確的類型
- 文檔完備:為組件提供詳細的文檔和使用示例
公共組件示例
<!-- components/common/Button.vue -->
<!-- 通用按鈕組件 -->
<!-- 設計意圖:提供一個可配置的通用按鈕組件,支持多種類型、尺寸和狀態(tài) -->
<template>
<button
:class="[
'btn',
`btn-${variant}`, // 按鈕類型樣式
`btn-${size}`, // 按鈕尺寸樣式
{ 'btn-block': block }, // 塊級按鈕樣式
{ 'btn-disabled': disabled } // 禁用狀態(tài)樣式
]"
:disabled="disabled" // 禁用狀態(tài)
@click="$emit('click', $event)" // 點擊事件
>
<slot></slot> <!-- 按鈕內容插槽 -->
</button>
</template>
<script setup lang="ts">
// 使用 Composition API 語法
import { defineProps, defineEmits } from 'vue'
/**
* 按鈕類型
* 支持多種預設的按鈕樣式
*/
export type ButtonVariant = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info'
/**
* 按鈕尺寸
* 支持多種預設的按鈕尺寸
*/
export type ButtonSize = 'sm' | 'md' | 'lg'
/**
* 按鈕組件屬性
* 使用 TypeScript 類型定義,提供更好的類型提示
*/
const props = defineProps<{
/**
* 按鈕類型
* 決定按鈕的顏色樣式
* @default 'primary'
*/
variant?: ButtonVariant
/**
* 按鈕尺寸
* 決定按鈕的大小
* @default 'md'
*/
size?: ButtonSize
/**
* 是否為塊級按鈕
* 塊級按鈕會占滿父容器的寬度
* @default false
*/
block?: boolean
/**
* 是否禁用
* 禁用狀態(tài)下按鈕不可點擊
* @default false
*/
disabled?: boolean
}>()
/**
* 按鈕組件事件
* 使用 TypeScript 類型定義,提供更好的類型提示
*/
const emit = defineEmits<{
/**
* 點擊事件
* 當用戶點擊按鈕時觸發(fā)
* @param event 點擊事件對象
*/
(e: 'click', event: MouseEvent): void
}>()
</script>
<style scoped>
/* 按鈕基礎樣式 */
.btn {
display: inline-block;
padding: 0.5rem 1rem;
border: none;
border-radius: 0.25rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
/* 按鈕類型樣式 */
.btn-primary {
background-color: #3b82f6;
color: white;
}
.btn-secondary {
background-color: #6b7280;
color: white;
}
.btn-success {
background-color: #10b981;
color: white;
}
.btn-danger {
background-color: #ef4444;
color: white;
}
.btn-warning {
background-color: #f59e0b;
color: white;
}
.btn-info {
background-color: #3b82f6;
color: white;
}
/* 按鈕尺寸樣式 */
.btn-sm {
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.btn-md {
padding: 0.5rem 1rem;
font-size: 1rem;
}
.btn-lg {
padding: 0.75rem 1.5rem;
font-size: 1.125rem;
}
/* 塊級按鈕樣式 */
.btn-block {
display: block;
width: 100%;
}
/* 禁用狀態(tài)樣式 */
.btn-disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>
6.3 組合式 API 邏輯組織
組合式 API 是 Vue 3 的重要特性,合理組織組合式 API 邏輯能夠提高代碼的復用性和可維護性。
組合式 API 目錄結構
src/composables/ ├── useAuth.ts # 認證相關邏輯 ├── useApi.ts # API 調用邏輯 ├── useLocalStorage.ts # 本地存儲邏輯 ├── useValidation.ts # 驗證相關邏輯 ├── useDebounce.ts # 防抖邏輯 └── useThrottle.ts # 節(jié)流邏輯
組合式 API 示例
// composables/useAuth.ts
// 認證相關邏輯
// 設計意圖:封裝認證相關的邏輯,供多個組件復用,實現(xiàn)邏輯的模塊化和可維護性
import { ref, computed } from 'vue'
import { userApi } from '@/services/api/user'
import { useUserStore } from '@/stores/user'
import { LoginRequest, RegisterRequest } from '@/models/user'
/**
* 認證相關邏輯
* 封裝登錄、注冊、登出等認證操作,提供統(tǒng)一的認證狀態(tài)管理
* 使用 Composition API 風格,便于在組件中使用
* @returns 認證相關的狀態(tài)和方法
*/
export function useAuth() {
const userStore = useUserStore() // 獲取用戶狀態(tài)管理 Store
const loading = ref(false) // 加載狀態(tài)
const error = ref<string | null>(null) // 錯誤信息
/**
* 是否已認證
* 從用戶 Store 中獲取認證狀態(tài)
*/
const isAuthenticated = computed(() => userStore.isAuthenticated)
/**
* 當前用戶
* 從用戶 Store 中獲取當前用戶信息
*/
const currentUser = computed(() => userStore.currentUser)
/**
* 用戶登錄
* 調用登錄 API,更新用戶狀態(tài)
* @param email 郵箱
* @param password 密碼
* @returns 登錄響應
* @throws 登錄失敗時拋出錯誤
*/
async function login(email: string, password: string) {
loading.value = true // 開始加載
error.value = null // 清空錯誤信息
try {
const response = await userApi.login({ email, password }) // 調用登錄 API
userStore.login(response.user, response.token) // 更新用戶狀態(tài)
return response // 返回登錄響應
} catch (err: any) {
// 自定義錯誤信息
error.value = err.response?.data?.message || '登錄失敗,請檢查郵箱和密碼'
throw err // 重新拋出錯誤
} finally {
loading.value = false // 結束加載
}
}
/**
* 用戶注冊
* 調用注冊 API,更新用戶狀態(tài)
* @param userData 用戶注冊數(shù)據(jù)
* @returns 注冊響應
* @throws 注冊失敗時拋出錯誤
*/
async function register(userData: RegisterRequest) {
loading.value = true // 開始加載
error.value = null // 清空錯誤信息
try {
const response = await userApi.register(userData) // 調用注冊 API
userStore.login(response.user, response.token) // 更新用戶狀態(tài)
return response // 返回注冊響應
} catch (err: any) {
// 自定義錯誤信息
error.value = err.response?.data?.message || '注冊失敗,請稍后重試'
throw err // 重新拋出錯誤
} finally {
loading.value = false // 結束加載
}
}
/**
* 用戶登出
* 調用登出 API,清除用戶狀態(tài)
* @throws 登出失敗時拋出錯誤
*/
async function logout() {
loading.value = true // 開始加載
error.value = null // 清空錯誤信息
try {
await userApi.logout() // 調用登出 API
userStore.logout() // 清除用戶狀態(tài)
} catch (err: any) {
// 自定義錯誤信息
error.value = err.response?.data?.message || '登出失敗,請稍后重試'
throw err // 重新拋出錯誤
} finally {
loading.value = false // 結束加載
}
}
/**
* 獲取當前用戶信息
* 調用獲取用戶信息 API,更新用戶狀態(tài)
* @returns 用戶信息
* @throws 獲取失敗時拋出錯誤
*/
async function fetchCurrentUser() {
loading.value = true // 開始加載
error.value = null // 清空錯誤信息
try {
const user = await userApi.getCurrentUser() // 調用獲取用戶信息 API
userStore.setCurrentUser(user) // 更新用戶狀態(tài)
return user // 返回用戶信息
} catch (err: any) {
// 自定義錯誤信息
error.value = err.response?.data?.message || '獲取用戶信息失敗'
throw err // 重新拋出錯誤
} finally {
loading.value = false // 結束加載
}
}
return {
loading, // 加載狀態(tài)
error, // 錯誤信息
isAuthenticated, // 是否已認證
currentUser, // 當前用戶
login, // 登錄方法
register, // 注冊方法
logout, // 登出方法
fetchCurrentUser // 獲取當前用戶信息方法
}
}
// composables/useApi.ts
// API 調用邏輯
// 設計意圖:封裝 API 請求的通用邏輯,供多個組件和 composables 復用,實現(xiàn)錯誤處理和加載狀態(tài)的統(tǒng)一管理
import { ref } from 'vue'
/**
* API 調用邏輯
* 封裝 API 請求的通用邏輯,包括加載狀態(tài)管理和錯誤處理
* 使用 Composition API 風格,便于在組件和其他 composables 中使用
* @returns API 調用相關的狀態(tài)和方法
*/
export function useApi() {
const loading = ref(false) // 加載狀態(tài)
const error = ref<string | null>(null) // 錯誤信息
/**
* 發(fā)起 API 請求
* 封裝 API 請求的通用邏輯,處理加載狀態(tài)和錯誤
* @param apiCall API 調用函數(shù),返回 Promise
* @returns API 響應數(shù)據(jù)
* @throws API 請求失敗時拋出錯誤
*/
async function request<T>(apiCall: () => Promise<T>): Promise<T> {
loading.value = true // 開始加載
error.value = null // 清空錯誤信息
try {
return await apiCall() // 執(zhí)行 API 調用
} catch (err: any) {
// 自定義錯誤信息
error.value = err.response?.data?.message || '請求失敗,請稍后重試'
throw err // 重新拋出錯誤
} finally {
loading.value = false // 結束加載
}
}
return {
loading, // 加載狀態(tài)
error, // 錯誤信息
request // API 請求方法
}
}
7. 企業(yè)級項目案例分析
7.1 項目架構演進
初始架構
在項目初期,通常會采用相對簡單的架構,以快速實現(xiàn)核心功能。
// 初始架構 // 項目初期的簡單架構,以快速實現(xiàn)核心功能為目標 // 設計意圖:在項目初期,使用簡單的目錄結構,快速搭建項目框架 // 特點: // - 目錄結構簡單,易于理解和快速上手 // - 功能模塊劃分明確,便于初期開發(fā) // - 適用于中小型項目或項目初期階段 // 適用場景:項目初期,功能相對簡單,團隊規(guī)模較小 src/ ├── assets/ # 靜態(tài)資源(圖片、樣式等) ├── components/ # 公共組件 ├── views/ # 頁面組件 ├── router/ # 路由配置 ├── store/ # 狀態(tài)管理(如 Vuex) ├── services/ # API 服務 ├── utils/ # 工具函數(shù) ├── App.vue # 根組件 └── main.ts # 應用入口文件
演進架構
隨著項目規(guī)模的擴大,架構會逐漸演進,添加更多的功能模塊和組織結構。
// 演進架構 // 隨著項目規(guī)模擴大,架構逐漸演進,添加更多功能模塊和組織結構 // 設計意圖:通過完善的目錄結構,提高代碼的可維護性和可擴展性,適應項目的不斷發(fā)展 // 特點: // - 目錄結構更加完善,功能模塊劃分更加細致 // - 引入了更多的 TypeScript 相關目錄,如 types、enums 等 // - 使用 Pinia 替代 Vuex,采用 Composition API 風格 // - 增加了更多的功能模塊,如 composables、layouts、models 等 // 適用場景:中大型項目,功能相對復雜,團隊規(guī)模較大 src/ ├── assets/ # 靜態(tài)資源(圖片、樣式、圖標等) ├── components/ # 公共組件(按功能或類型劃分) ├── composables/ # 組合式 API 邏輯(可復用的業(yè)務邏輯) ├── constants/ # 常量定義(API 地址、路由名稱等) ├── enums/ # 枚舉類型(用戶角色、訂單狀態(tài)等) ├── layouts/ # 布局組件(默認布局、認證布局等) ├── models/ # 數(shù)據(jù)模型(用戶模型、產(chǎn)品模型等) ├── router/ # 路由配置(路由定義、守衛(wèi)等) ├── services/ # 服務層(API 服務、工具服務等) ├── stores/ # Pinia 狀態(tài)管理(按模塊劃分的狀態(tài)管理) ├── types/ # TypeScript 類型定義(API 類型、組件類型等) ├── utils/ # 工具函數(shù)(格式化、驗證、存儲等) ├── views/ # 頁面組件(按業(yè)務功能劃分) ├── App.vue # 根組件 ├── main.ts # 應用入口文件 └── env.d.ts # 環(huán)境變量類型聲明
微前端架構
對于超大型應用,可以考慮采用微前端架構,將應用拆分為多個獨立的微應用。
// 微前端架構 // 對于超大型應用,采用微前端架構,將應用拆分為多個獨立的微應用 // 設計意圖:通過微前端架構,實現(xiàn)應用的模塊化和獨立部署,提高開發(fā)效率和團隊協(xié)作 // 特點: // - 將應用拆分為多個獨立的微應用,每個微應用可以獨立開發(fā)、部署和維護 // - 主應用(shell)負責微應用的加載和管理 // - 共享資源(packages)供多個微應用復用 // - 提高了應用的可擴展性和可維護性 // 適用場景:超大型應用,多個團隊負責不同業(yè)務模塊,需要獨立部署和維護 apps/ # 應用目錄,包含所有微應用 ├── shell/ # 主應用(shell),負責微應用的加載和管理 ├── auth/ # 認證微應用,處理用戶登錄、注冊等認證功能 ├── dashboard/ # 儀表盤微應用,展示用戶儀表盤和概覽數(shù)據(jù) └── products/ # 產(chǎn)品微應用,處理產(chǎn)品相關的功能 packages/ # 共享資源目錄,包含多個微應用共用的代碼 ├── components/ # 共享組件,供多個微應用復用 ├── utils/ # 共享工具函數(shù),供多個微應用復用 └── types/ # 共享類型定義,供多個微應用復用
7.2 架構優(yōu)化策略
性能優(yōu)化:
- 代碼分割和懶加載
- 資源壓縮和緩存
- 減少不必要的重渲染
可維護性優(yōu)化:
- 模塊化和組件化
- 代碼規(guī)范和命名約定
- 文檔和注釋
可擴展性優(yōu)化:
- 插件化架構
- 配置驅動開發(fā)
- 依賴注入
安全性優(yōu)化:
- 認證和授權
- 數(shù)據(jù)驗證和 sanitization
- 防止 XSS 和 CSRF 攻擊
7.3 實際項目案例
電商平臺
架構特點:
- 模塊化設計,按業(yè)務功能劃分模塊
- 微前端架構,將不同業(yè)務域拆分為獨立的微應用
- 服務端渲染,提高首屏加載速度和 SEO
- 實時數(shù)據(jù)更新,使用 WebSocket 實現(xiàn)商品價格和庫存的實時更新
核心模塊:
- 用戶認證模塊:處理用戶登錄、注冊、密碼重置等
- 商品模塊:處理商品列表、詳情、搜索等
- 購物車模塊:處理購物車添加、修改、結算等
- 訂單模塊:處理訂單創(chuàng)建、支付、物流等
- 支付模塊:集成多種支付方式
企業(yè)管理系統(tǒng)
架構特點:
- 權限管理系統(tǒng),基于角色的訪問控制
- 工作流引擎,支持自定義業(yè)務流程
- 數(shù)據(jù)可視化,提供豐富的報表和圖表
- 多語言支持,適應國際化需求
核心模塊:
- 用戶管理模塊:處理用戶信息、角色、權限等
- 組織管理模塊:處理部門、職位、員工等
- 資產(chǎn)管理模塊:處理資產(chǎn)登記、折舊、盤點等
- 財務管理模塊:處理預算、報銷、審批等
- 項目管理模塊:處理項目計劃、任務、進度等
8. 總結與實踐
8.1 項目架構實踐
合理的目錄結構:
- 按照功能模塊組織代碼
- 建立清晰的層次結構
- 采用一致的命名規(guī)范
狀態(tài)管理策略:
- 使用 Pinia 進行狀態(tài)管理
- 按模塊劃分 Store
- 合理使用持久化存儲
路由架構設計:
- 模塊化路由配置
- 合理使用路由守衛(wèi)
- 支持動態(tài)路由和權限控制
API 層設計:
- 統(tǒng)一的 API 服務封裝
- 合理的錯誤處理策略
- 有效的數(shù)據(jù)緩存機制
工具類和公共組件:
- 可復用的工具函數(shù)
- 可配置的公共組件
- 類型安全的組合式 API
代碼質量保證:
- 嚴格的 TypeScript 類型檢查
- 統(tǒng)一的代碼規(guī)范和格式化
- 完善的測試用例
8.2 團隊協(xié)作實踐
代碼規(guī)范:
- 制定統(tǒng)一的代碼規(guī)范
- 使用 ESLint 和 Prettier 進行代碼檢查和格式化
- 定期進行代碼審查
版本控制:
- 采用 Git 工作流
- 合理的分支管理策略
- 規(guī)范的提交信息
文檔管理:
- 項目架構文檔
- 組件和 API 文檔
- 開發(fā)和部署指南
自動化工具:
- CI/CD 流程
- 自動化測試
- 代碼質量檢測
8.3 未來發(fā)展趨勢
微前端架構:
- 將大型應用拆分為多個獨立的微應用
- 提高團隊協(xié)作效率和應用可維護性
Serverless 架構:
- 前端和后端都采用 Serverless 架構
- 降低運維成本和提高擴展性
AI 輔助開發(fā):
- 使用 AI 工具輔助代碼生成和優(yōu)化
- 提高開發(fā)效率和代碼質量
WebAssembly:
- 部分計算密集型任務使用 WebAssembly
- 提高應用性能
邊緣計算:
- 將部分計算和存儲遷移到邊緣節(jié)點
- 減少延遲和提高用戶體驗
9. 附錄
9.1 技術棧推薦
| 類別 | 技術 | 版本 | 用途 |
總結
Vue 3 + TypeScript 項目架構設計是一個持續(xù)迭代和優(yōu)化的過程,需要根據(jù)項目的具體需求和團隊的實際情況進行靈活調整和優(yōu)化。通過合理的架構設計和實踐,我們可以構建出高質量、可維護、可擴展的 Vue 3 + TypeScript 項目,為前端開發(fā)團隊帶來更好的開發(fā)體驗和更高的生產(chǎn)效率。
到此這篇關于Vue3+TypeScript項目架構實踐指南的文章就介紹到這了,更多相關Vue3+TS項目架構內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
一文教會你搭建vite項目并配置路由和element-plus
由于項目搭建過程實在繁瑣,容易遺忘,每次新建項目還得百度一下怎么搭建,所以寫下本文提醒自己,下面這篇文章主要給大家介紹了關于搭建vite項目并配置路由和element-plus的相關資料,需要的朋友可以參考下2022-07-07
Vue嵌套iframe時$router.go(-1)后退bug的原因解析
這篇文章主要介紹了Vue嵌套iframe,$router.go(-1)后退bug的問題原因及解決方法,本文給大家分享問題原因所在及解決方案,需要的朋友可以參考下吧2023-09-09
詳細講解如何創(chuàng)建, 發(fā)布自己的 Vue UI 組件庫
當我們自己開發(fā)了一個 _UI Component_, 需要在多個項目中使用的時候呢? 我們首先想到的可能是直接復制一份過去對嗎?我們?yōu)槭裁床话l(fā)布一個 UI 組件庫給自己用呢?下面小編和大家來一起學習下吧2019-05-05

