《Clean Code 2: Vue 3 檔案/資料夾結構》
目錄 (10 主題版)
Part 1: 最簡目錄總覽 🚀
- Vue 3 專案結構入門 🏗️
- 元件與模組化組織 🧩
- 路由與頁面架構 🗺️
- 狀態管理結構 🗄️
- API 服務層設計 🌐
- 共享資源與工具 🛠️
- 測試結構 🧪
- 特性導向結構 🏢
- 命名規範與原則 🏷️
- 實戰範例與總結 ✨
Part 2: 詳細目錄內容 📖
第一部分:基礎與核心結構 (Foundations & Core Structure) 🏗️
- Vue 3 專案結構入門與原則
- 為何需要清晰的檔案結構?🤔
- Vue CLI/Vite 預設結構解析 🧐
src目錄的組織策略 📁- 核心目錄 (
components,views,assets,utils等) 的職責 🗂️
- 元件與模組化組織的最佳實踐
- 元件的職責劃分與拆分 🧩
- 單檔案元件 (SFC) 的結構規範 📝
- 組織元件目錄 (
components,ui,layout等) 🌳 - 共享元件與複合元件的區分 🤝
- 路由與頁面架構的最佳實踐
vue-router配置與目錄結構 🗺️- 頁面 (Views/Pages) 與佈局 (Layouts) 的組織 🏘️
- 動態路由與嵌套路由的結構化 🧭
- 路由守衛與權限管理的結構化 🛡️
- 狀態管理結構 (Vuex/Pinia)
- Pinia Store 的模組化結構建議 🗄️
- Vuex Modules 的組織方式 📦
- Actions, Mutations, Getters 的邏輯劃分 🗃️
- 狀態共享與跨 Store 依賴管理 🌐
- API 服務層設計與組織
- 建立專門的 API 服務目錄 (
services,api) 📡 - Axios/Fetch 的封裝與配置 🌐
- 錯誤處理、響應格式統一與 Mock API 結構 ⚠️🎭
- 建立專門的 API 服務目錄 (
第二部分:進階組織與實踐 (Advanced Organization & Practices) 🚀
- 共享資源與工具的結構化
utils,helpers,constants目錄的職責與組織 🛠️- TypeScript 類型定義 (
types,interfaces) 的結構 📏 - 全局配置、指令與插件的組織 ⚙️
- 測試結構與檔案組織
- 單元測試 (
__tests__或tests) 的位置與組織 🧪 - 整合測試與端到端測試的結構 🧩
- 測試輔助函數與 Mock 數據的組織 📊
- 單元測試 (
- 特性 (Feature) 導向的目錄結構
- 按業務功能劃分目錄 (
features/user,features/products) 🏢 - 特性內部元件、服務、狀態的組織 🧩🗄️
- 特性之間的依賴管理與解耦 🔗
- 按業務功能劃分目錄 (
- 命名規範與程式碼風格
- 檔案與目錄的命名原則 (kebab-case, camelCase) 🏷️
- 元件、Props、Methods 的命名規範 📝
- 團隊內部命名規範的建立與執行 🤝
第三部分:總結與實戰 (Conclusion & Practical Examples) ✨
- 實戰範例、常見陷阱與總結
- 小型與大型專案的結構範例對比 🏠🏢
- 重構現有專案結構的策略 🪜
- 常見結構陷阱與解決方案 늪
- 打造可持續、可擴展的 Vue 3 專案結構 💎
第 1 章:Vue 3 專案結構入門與原則 🏗️
Part 1: 最簡說明 🚀
本章將介紹 Vue 3 專案的基本檔案結構,說明各個主要目錄和檔案的作用,並強調建立清晰結構的重要性。這能幫助你快速理解一個新專案的佈局,並為後續的程式碼組織打下基礎。
Part 2: 詳細內容 📖
1.1 為何需要清晰的檔案結構? 🤔
- 可維護性 (Maintainability): 良好的結構讓程式碼更容易理解、修改和除錯。當專案變大時,這點尤為重要。
- 可擴展性 (Scalability): 清晰的結構能讓新功能或模組更容易被整合進來,而不會破壞現有程式碼。
- 團隊協作 (Team Collaboration): 標準化的結構能讓團隊成員快速上手,減少溝通成本,提高開發效率。
- 可讀性 (Readability): 結構清晰的專案,程式碼的邏輯和關係一目了然。
1.2 Vue CLI 與 Vite 預設結構解析 🧐
- Vue CLI 預設結構 (以
vue create為例):public/: 存放靜態資源,如index.html、favicon.ico等,這些資源不會被 Webpack 處理。index.html: 應用程式的入口 HTML 文件。
src/: 存放所有 Vue 應用程式的原始碼。這是我們最常打交道的目錄。main.js(或main.ts): Vue 應用程式的入口檔案,創建 Vue 實例並掛載到 DOM。App.vue: 應用程式的根元件。assets/: 存放需要經過 Webpack 處理的資源,如圖片、CSS、Sass 等。components/: 存放可複用的 Vue 元件。views/(或pages/): 存放頁面級的元件,通常與路由對應。router/: 存放路由配置相關檔案 (如果使用vue-router)。store/: 存放狀態管理相關檔案 (如果使用 Vuex 或 Pinia)。utils/或helpers/: 存放通用的工具函數。
tests/: 存放測試檔案。.gitignore: Git 忽略檔案設定。package.json: 專案的依賴和腳本配置。vue.config.js(或vue.config.ts): Vue CLI 的自訂配置。
- Vite 預設結構 (以
create-vue為例):public/: 與 Vue CLI 類似,存放不需要構建的靜態資源。src/: 存放應用程式的原始碼。main.js(或main.ts): Vue 應用程式的入口檔案。App.vue: 應用程式的根元件。assets/: 存放需要處理的靜態資源。components/: 存放元件。views/(或pages/): 存放頁面級元件。router/: 路由配置。store/: 狀態管理配置。
index.html: Vite 的入口 HTML 文件,與 Webpack 不同的是,它位於根目錄,並且可以直接在其中引入 JS。vite.config.js(或vite.config.ts): Vite 的配置檔案。package.json: 專案依賴和腳本配置。
1.3 src 目錄的組織策略 📁
- 核心原則: 保持目錄結構的扁平化,避免過深的巢狀結構,除非有明確的分類需求。
- 常見的頂級目錄:
components/: 可重用 UI 元件。views/或pages/: 與路由直接對應的頁面級元件。layouts/: 應用程式的佈局元件 (如 Header, Footer, Sidebar)。router/: 路由設定。store/: 狀態管理 (Pinia/Vuex)。services/或api/: API 請求邏輯。utils/或helpers/: 通用工具函數。assets/: 靜態資源 (圖片、字體、CSS)。composables/: Vue 3 的 Composition API 相關的組合式函數。types/: TypeScript 型別定義。styles/: 全域樣式、變數、mixin 等。
1.4 核心目錄 (components, views, assets, utils 等) 的職責 🗂️
components/:- 存放可複用的、獨立的 UI 單元。
- 通常是無狀態或僅管理自身狀態的元件。
- 可以進一步劃分,例如:
components/common/(基礎 UI 元件),components/modules/(業務邏輯相關的元件)。
views/(或pages/):- 代表應用程式中的一個頁面或路由目的地。
- 通常會組合多個
components來構成頁面。 - 負責與路由、狀態管理、API 請求進行互動。
assets/:- 存放需要經過構建工具(如 Webpack 或 Vite)處理的靜態資源。
- 例如:圖片 (
.png,.jpg,.svg)、字體 (.woff,.ttf)、CSS、Sass 等。 - 這些資源會被打包進最終的構建產物中。
utils/(或helpers/):- 存放不依賴於 Vue 特定邏輯的通用函數。
- 例如:日期格式化、字串處理、數學計算、數據驗證等。
- 保持這些函數的純粹性,便於測試和複用。
第 2 章:元件與模組化組織的最佳實踐 🧩
Part 1: 最簡說明 🚀
本章將深入探討如何在 Vue 3 專案中有效地組織和拆分元件。我們將學習如何區分不同類型的元件,掌握單檔案元件 (SFC) 的結構規範,並了解組織元件目錄的策略,以建立一個可維護且易於擴展的元件系統。
Part 2: 詳細內容 📖
2.1 元件的職責劃分與拆分 🧩
- 單一職責原則 (Single Responsibility Principle - SRP):
- 每個元件應該只做一件事,並且做好。
- 當一個元件變得過於龐大或包含多個不相關的職責時,就應該考慮將其拆分。
- Presentational vs. Container Components (展示型元件 vs. 容器型元件):
- 展示型元件 (Presentational):
- 主要負責 UI 的呈現。
- 接收 props,觸發 events。
- 通常是無狀態的,或只管理自身的 UI 狀態。
- 例子:按鈕 (
Button.vue)、輸入框 (Input.vue)、列表項 (ListItem.vue)。
- 容器型元件 (Container):
- 負責邏輯的處理,如數據獲取、狀態管理、事件處理。
- 通常會調用 API、與 Store 互動。
- 將數據和方法通過 props 和 events 傳遞給展示型元件。
- 例子:用戶列表頁 (
UserList.vue),它可能負責獲取用戶數據並將數據傳遞給一個UserList.vue的子元件。
- 展示型元件 (Presentational):
- Composition API 的助力: Composition API (特別是
composables) 非常適合將邏輯抽離出來,讓元件更專注於 UI 呈現。
2.2 單檔案元件 (SFC) 的結構規範 📝
一個典型的 Vue 單檔案元件 (
.vue 檔案) 通常包含三個部分:<template>, <script>, 和 <style>。<template>:- 放置元件的 HTML 結構。
- 保持結構的清晰和簡潔,避免過於複雜的嵌套。
- 使用 Slot 來實現內容的插入和分發。
<script>(Composition API 推薦):- 使用
<script setup>語法糖,可以更簡潔地編寫邏輯。 - 將邏輯組織成可讀性高的函數 (
setup,computed,watch,methods等)。 - 將複雜邏輯抽離到
composables中。 - 清晰地定義 props 和 emits。
- 使用
<style>:- 使用
scoped屬性來限制樣式只作用於當前元件,避免樣式污染。 - 可以根據需要組織 CSS 結構,例如使用 BEM 命名約定。
- 可以引入 SCSS/Less 等預處理器。
- 使用
- 推薦順序:
<template>,<script>,<style>。
2.3 組織元件目錄 (components, ui, layout 等) 🌳
- 基礎目錄結構:
src/components/: 存放所有可複用的 UI 元件。
- 進一步細分策略:
- 按功能/業務模組劃分:
src/components/user/: 用戶相關元件 (如UserProfile.vue,UserAvatar.vue)。src/components/product/: 產品相關元件 (如ProductCard.vue,ProductGallery.vue)。src/components/shared/或src/components/common/: 跨多個模組使用的通用元件 (如Button.vue,Modal.vue,Input.vue)。
- 按層級劃分:
src/components/base/或src/components/ui/: 最基礎的 UI 組件,通常是原子組件 (Atomic Design 中的原子)。src/components/molecules/: 由基礎組件組成的較小功能單元。src/components/organisms/: 由分子組成的更複雜的 UI 組件。
src/layouts/:- 專門存放應用程式的佈局元件,如
DefaultLayout.vue,AuthLayout.vue,AdminLayout.vue。 - 這些佈局元件通常包含 Header, Footer, Sidebar 等結構,並通過
<slot>包裹頁面內容。
- 專門存放應用程式的佈局元件,如
- 按功能/業務模組劃分:
- 命名規則:
- 元件檔案名使用大駝峰命名法 (PascalCase),如
UserProfile.vue。 - 目錄名可以使用小駝峰 (camelCase) 或橫線分隔 (kebab-case),但要保持一致性。
- 元件檔案名使用大駝峰命名法 (PascalCase),如
2.4 共享元件與複合元件的區分 🤝
- 共享元件 (Shared Components):
- 通常放在
components/common/或components/ui/下。 - 設計成通用的、高度可配置的元件,可以在專案的任何地方使用。
- 例子:按鈕、輸入框、下拉選單、圖標。
- 通常放在
- 複合元件 (Composite Components):
- 由多個共享元件或業務元件組合而成,構成一個更複雜的 UI 單元。
- 可能更側重於特定的業務場景。
- 例子:用戶信息卡片 (
UserProfileCard.vue),它可能包含UserAvatar.vue,UserName.vue,UserStatus.vue等元件。 - 這些元件可以放在與其業務相關的目錄下,例如
components/user/UserProfileCard.vue。
第 3 章:路由與頁面架構的最佳實踐 🗺️
Part 1: 最簡說明 🚀
本章將介紹如何在 Vue 3 專案中有效地組織路由配置和頁面級元件。我們將學習如何使用
vue-router 來管理應用程式的導航,如何規劃頁面和佈局的檔案結構,以及如何處理動態路由和嵌套路由,從而構建一個清晰且易於管理的導航系統。Part 2: 詳細內容 📖
3.1 vue-router 配置與目錄結構 🗺️
- 路由配置檔案 (
router/index.js或router/index.ts):- 這是應用程式路由的入口點。
- 通常會在這裡創建
createRouter實例,並定義history(如createWebHistory) 和routes陣列。
- 路由結構的組織:
- 單一路由檔案: 對於小型專案,將所有路由定義在
router/index.js中是可行的。 - 模組化路由: 對於大型專案,將路由按功能或模組劃分到不同的檔案中,然後在
router/index.js中導入和合併。- 例如:
router/modules/user.js,router/modules/product.js,router/modules/dashboard.js。 - 在
router/index.js中:javascriptimport { createRouter, createWebHistory } from 'vue-router'; import UserRoutes from './modules/user'; import ProductRoutes from './modules/product'; const routes = [ { path: '/', component: () => import('@/views/Home.vue') }, // 其他根層級路由 ...UserRoutes, ...ProductRoutes, ]; const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes, }); export default router;
- 例如:
- 單一路由檔案: 對於小型專案,將所有路由定義在
3.2 頁面 (Views/Pages) 與佈局 (Layouts) 的組織 🏘️
src/views/或src/pages/:- 存放與應用程式的 URL 路徑直接對應的頁面級元件。
- 每個檔案通常代表一個獨立的頁面。
- 例如:
views/Home.vue,views/About.vue,views/users/UserList.vue,views/users/UserProfile.vue。
src/layouts/:- 存放應用程式的佈局結構元件。
- 佈局元件通常包含應用程式的公共部分,如導航欄 (Header)、側邊欄 (Sidebar)、頁腳 (Footer) 等。
- 頁面元件會渲染在佈局元件的特定位置 (通常是
<slot>標籤)。 - 範例:
layouts/DefaultLayout.vue: 預設佈局。layouts/AuthLayout.vue: 認證頁面的佈局 (可能沒有導航欄)。layouts/AdminLayout.vue: 管理後台的佈局。
- 路由配置中的佈局應用:
- 可以在路由元資訊 (meta) 中指定頁面使用的佈局。
- 或者,在根層級路由中使用佈局元件包裹子路由。
- 範例 (使用 meta):javascript
const routes = [ { path: '/dashboard', component: () => import('@/layouts/AdminLayout.vue'), children: [ { path: '', // AdminLayout 的默認子路由 name: 'Dashboard', component: () => import('@/views/admin/Dashboard.vue'), meta: { layout: 'AdminLayout' } // 可選,用於動態選擇佈局 }, { path: 'users', name: 'AdminUsers', component: () => import('@/views/admin/AdminUsers.vue'), meta: { layout: 'AdminLayout' } } ] }, { path: '/login', component: () => import('@/layouts/AuthLayout.vue'), children: [ { path: '', name: 'Login', component: () => import('@/views/Login.vue'), meta: { layout: 'AuthLayout' } } ] } ]; - 在
App.vue中根據meta.layout動態渲染對應的佈局元件。
3.3 動態路由與嵌套路由的結構化 🧭
- 動態路由:
- 用於匹配帶有參數的路由,例如用戶 ID、文章 Slug 等。
- 在路由配置中使用冒號 (
:) 來定義參數。 - 範例:javascript
{ path: '/users/:id', // 匹配 /users/123, /users/abc name: 'UserProfile', component: () => import('@/views/users/UserProfile.vue'), props: true // 將路由參數作為 props 傳遞給元件 } - 在元件中,可以通過
$route.params.id或元件的 props 來訪問參數。
- 嵌套路由:
- 用於創建具有父子層級結構的路由,常見於帶有側邊欄或多區域佈局的頁面。
- 在父路由的配置中,使用
children陣列來定義子路由。 - 父路由元件需要包含一個
<router-view>元件,用於渲染匹配的子路由。 - 範例:javascript
{ path: '/users', component: () => import('@/views/users/UserLayout.vue'), // 包含 <router-view> 的佈局元件 children: [ { path: '', // UserLayout 的默認子路由 name: 'UserList', component: () => import('@/views/users/UserList.vue') }, { path: ':id', // 動態嵌套路由 name: 'UserProfile', component: () => import('@/views/users/UserProfile.vue'), props: true } ] }
3.4 路由守衛與權限管理的結構化 🛡️
- 路由守衛 (Navigation Guards):
- 提供了一種在路由導航過程中執行邏輯的機制。
- 全局守衛:
beforeEach,beforeResolve,afterEach。 - 路由獨享守衛:
beforeEnter。 - 元件內守衛:
beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave。
- 權限管理結構:
- 在路由配置的
meta欄位中定義權限要求。javascript{ path: '/admin', component: () => import('@/layouts/AdminLayout.vue'), meta: { requiresAuth: true, roles: ['admin'] }, // 需要登錄且為 admin 角色 children: [ /* ... */ ] } - 在全局守衛
beforeEach中檢查路由的meta資訊,驗證用戶是否有權限訪問。 - 如果沒有權限,可以重定向到登錄頁或顯示錯誤訊息。
- 範例 (在
router/index.js):javascriptrouter.beforeEach((to, from, next) => { const requiresAuth = to.matched.some(record => record.meta.requiresAuth); const user = { loggedIn: true, role: 'admin' }; // 假設這是用戶狀態 if (requiresAuth && !user.loggedIn) { next('/login'); // 未登錄,跳轉到登錄頁 } else if (requiresAuth && user.role !== 'admin' && to.meta.roles?.includes('admin')) { next('/forbidden'); // 權限不足 } else { next(); // 允許訪問 } });
- 在路由配置的
第 5 章:API 服務層設計與組織 🌐
第 9 章:命名規範與程式碼風格 🏷️
Part 1: 最簡說明 🚀
本章將介紹如何為 Vue 3 專案設計一個清晰、可擴展的 API 服務層。我們將學習如何封裝 API 請求邏輯,處理異步操作,並確保錯誤處理的一致性。同時,我們將探討一套實用的命名規範和程式碼風格,這將貫穿整個專案,特別是在 API 服務層的組織和命名中,以提升程式碼的可讀性和可維護性。
Part 2: 詳細內容 📖
5.1 建立專門的 API 服務目錄 (services, api) 📡
- 目的: 將所有與後端 API 交互的邏輯集中管理,與元件邏輯分離。
- 目錄結構:
src/services/或src/api/
- 按資源或功能劃分:
src/services/userService.js(或userService.ts): 處理用戶相關的 API 請求 (如獲取用戶資料、更新用戶資訊)。productService.js: 處理產品相關的 API 請求。authService.js: 處理認證相關的 API 請求 (登錄、註冊、登出)。http.js(或http.ts): 封裝 Axios 或 Fetch 的實例配置。
http.js的作用:- 創建 Axios 或 Fetch 的實例。
- 設置基礎 URL (
baseURL)。 - 配置請求和響應攔截器 (interceptors)。
- 設置默認的請求頭 (如
Content-Type,Authorization)。
5.2 Axios 或 Fetch 的封裝與配置 🌐
- 使用 Axios 的優勢:
- 更豐富的功能,如請求/響應攔截器、取消請求、超時設置等。
- 更簡潔的 API。
http.js範例 (使用 Axios):javascriptimport axios from 'axios'; const apiClient = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || '/api', // 從環境變數讀取 API Base URL headers: { 'Content-Type': 'application/json', }, timeout: 10000, // 請求超時設置 }); // 請求攔截器:在請求發送前添加 Authorization token apiClient.interceptors.request.use( (config) => { const token = localStorage.getItem('authToken'); // 假設 token 存儲在 localStorage if (token) { config.headers.Authorization = `Bearer ${token}`; } return config; }, (error) => { return Promise.reject(error); } ); // 響應攔截器:處理錯誤或統一的響應格式 apiClient.interceptors.response.use( (response) => { // 可以選擇性地處理響應數據,例如提取 data 字段 // return response.data; return response; }, (error) => { // 統一錯誤處理 if (error.response) { // 請求已發送但服務器返回錯誤狀態碼 console.error('API Error:', error.response.status, error.response.data); if (error.response.status === 401) { // 處理未授權錯誤,例如跳轉到登錄頁 // router.push('/login'); } } else if (error.request) { // 請求已發送但沒有收到響應 console.error('API No Response:', error.request); } else { // 設置請求時出錯 console.error('API Setup Error:', error.message); } return Promise.reject(error); } ); export default apiClient;- 服務檔案範例 (
userService.js):javascriptimport apiClient from './http'; // 導入配置好的 axios 實例 export const fetchUserProfile = (userId) => { return apiClient.get(`/users/${userId}`); // 返回 Promise }; export const updateUserProfile = (userId, userData) => { return apiClient.put(`/users/${userId}`, userData); }; export const createUser = (userData) => { return apiClient.post('/users', userData); };
5.3 錯誤處理與響應格式統一 ⚠️
- 統一的錯誤處理: 在響應攔截器中集中處理常見錯誤 (如 401, 404, 500),減少重複代碼。
- 統一的響應格式:
- 與後端約定統一的 API 響應結構,例如:json
// 成功響應 { "success": true, "data": { /* ... 實際數據 ... */ }, "message": "操作成功" } // 失敗響應 { "success": false, "data": null, "message": "錯誤訊息" } - 可以在響應攔截器中解析這種結構,方便前端調用。
- 與後端約定統一的 API 響應結構,例如:
- 在元件或 Store 中處理錯誤:
- 調用服務層函數時,使用
.then()和.catch()或async/await的try...catch來處理 API 返回的 Promise。 - 將錯誤訊息顯示給用戶,或記錄日誌。
- 調用服務層函數時,使用
第 9 章:命名規範與程式碼風格 🏷️
9.1 檔案與目錄的命名原則 (kebab-case, camelCase) 🏷️
- 檔案和目錄:
- 推薦使用
kebab-case(橫線分隔): 這在 Web 開發中是一個廣泛接受的約定,尤其適用於 URL、HTML 屬性等。- 例如:
user-profile.vue,api-service.js,auth-module。
- 例如:
- Vue 元件: 嚴格推薦使用
PascalCase(大駝峰),因為 Vue 會自動將其轉換為kebab-case供模板使用。- 例如:
UserProfile.vue(在模板中使用<user-profile>)。
- 例如:
- 工具函數、服務: 推薦使用
camelCase(小駝峰)。- 例如:
userService.js,utils.js,formatDate.js。
- 例如:
- 路由模組: 可以使用
kebab-case或camelCase,保持團隊一致性即可。- 例如:
userRoutes.js或user-routes.js。
- 例如:
- 推薦使用
- 一致性是關鍵: 無論選擇哪種風格,最重要的是在整個專案中保持一致。
9.2 元件、Props、Methods 的命名規範 📝
- 元件:
- 使用具體的名詞或名詞短語,描述元件的用途。
- 使用
PascalCase。 - 例如:
Button,Modal,UserProfileCard,ProductList。 - 對於佈局元件,可以使用
LayoutHeader,DefaultLayout。
- Props:
- 使用
camelCase。 - 名稱應清晰表達其用途和預期數據類型。
- 例如:
userName,isLoading,userList,isActive。 - 如果 prop 是用於事件處理的函數,可以命名為
onEventName(如onClick,onUpdate)。
- 使用
- Methods (包括 Actions):
- 使用
camelCase。 - 名稱應描述操作的行為或意圖。
- 動詞開頭是個好習慣。
- 例如:
fetchData,updateProfile,handleSubmit,validateForm,addToCart。 - 對於 Vuex Mutations,使用全大寫加底線 (
SET_PROFILE)。
- 使用
9.3 團隊內部命名規範的建立與執行 🤝
- 定義標準: 在專案開始時或早期階段,與團隊討論並確定一套統一的命名規範。
- 文件化: 將這些規範記錄在專案的 README 或開發指南中。
- Code Review: 在程式碼審查過程中,檢查命名是否符合規範。
- Linting 工具: 使用 ESLint 和 Prettier 等工具來自動檢查和格式化程式碼,包括命名風格。可以配置規則來強制執行特定的命名約定。
留言
發佈留言