# 檔案結構 File Structure ### 主要的分類法 1. 透過檔案類別分類 (components, contexts, hooks) 2. 將全域的 contexts, hooks, etc 分類 3. 將通常會一起修改的 components, contexts, hooks 放在越相鄰的地放(官方稱為colocation) 4. 以功能路徑分類 ### 透過檔案分類 Group by file types 檔案分類為最簡易的方式,可以不用過多的思考 | 資料夾 | 放入檔案 | | -------- | -------- | | components | component | | hooks | hook | | contexts | context | ``` └── src/ ├── components/ │ │ # ...more folder │ ├── button/ │ ├── card/ │ ├── checkbox/ │ ├── footer/ │ ├── header/ │ ├── todo-item/ │ └── todo-list/ │ ├── todo-list.component.js │ └── todo-list.test.js ├── contexts/ │ │ # no idea what this does but I couldn't leave this folder empty │ └── todo-list.context.js └── hooks/ │ # again no idea what this does but I couldn't leave this folder empty └── use-todo-list.js ``` ### 更多檔案 =>巢狀 添加功能 => 編輯待辦事項 功能變多,資料夾也變得更加擁擠 * 雖說效果不差,但資料夾則過於擁擠 * 可以試著把關聯的資料像是 checkBox,text-field 合併 * 且edit-todo-modal和todo-form(父項和子項))相距甚遠 ``` └── src/ ├── components/ │ ├── button/ │ ├── card/ │ ├── checkbox/ │ │ # this modal shows a form to edit a todo item │ ├── edit-todo-modal/ │ ├── footer/ │ ├── header/ │ ├── modal/ │ ├── text-field/ │ │ # here is the form that is shown by the modal │ ├── todo-form/ │ ├── todo-item/ │ │ # the edit modal is shown on top of the todo list │ └── todo-list/ │ ├── todo-list.component.js │ └── todo-list.test.js ├── contexts/ │ ├── modal.context.js │ └── todo-list.context.js └── hooks/ ├── use-modal.js ├── use-todo-form.js └── use-todo-list.js ``` 可以試著分組 和 colocation 使用此檔案架構可清楚了解主要功能 1. 將子組件與其父組件放在一起 2. 對通用 UI 和佈局組件進行分組 ``` └── src/ ├── components/ │ ├── edit-todo-modal/ │ │ ├── edit-todo-modal.component.js │ │ ├── edit-todo-modal.test.js │ │ │ # colocate -> todo-form is only used by edit-todo-modal │ │ ├── todo-form.component.js │ │ └── todo-form.test.js │ ├── todo-list/ │ │ │ # colocate -> todo-item is only used by todo-list │ │ ├── todo-item.component.js │ │ ├── todo-list.component.js │ │ └── todo-list.test.js │ │ # group simple ui components in one folder │ └── ui/ │ ├── button/ │ ├── card/ │ ├── checkbox/ │ ├── footer/ │ ├── header/ │ ├── modal/ │ └── text-field/ ├── contexts/ │ ├── modal.context.js │ └── todo-list.context.js └── hooks/ ├── use-modal.js ├── use-todo-form.js └── use-todo-list.js ``` 折疊後可以看的更清楚 ``` └── src/ ├── components/ │ ├── edit-todo-modal/ │ ├── todo-list/ │ └── ui/ ├── contexts/ └── hooks/ ``` ### 更多頁面 擴充功能頁面 =>用戶能創建待辦事項功能 新增會員認證功能,會使用到創建及編輯列表模組 ,能放入components “create todo page” and the “edit todo modal” ``` └── src/ ├── components/ │ │ # we now have multiple pages │ ├── create-todo-page/ │ ├── edit-todo-modal/ │ ├── login-page/ │ │ # this is where the todo-list is now shown │ ├── home-page/ │ ├── signup-page/ │ │ # the form is now shared between create page and edit modal │ ├── todo-form/ │ ├── todo-list/ │ │ ├── todo-item.component.js │ │ ├── todo-list.component.js │ │ └── todo-list.test.js │ └── ui/ ├── contexts/ │ ├── modal.context.js │ └── todo-list.context.js └── hooks/ │ # handles the authorization ├── use-auth.js ├── use-modal.js ├── use-todo-form.js └── use-todo-list.js ``` 1. 長遠來看,專案無法避面過長的情況發生.但為了保持扁平化,只能忽略這個問題. 1. 頁面的檔案過繁雜: * 頁面的進入點 * 有side effects的複雜組件 (ex:forms) * 簡單的UI組件 (ex:按鈕) pages (which are entry points to the app and thus important for new devs to understand the codebase)不大懂意思 **解決方法:將所有頁面組件及子組件放入獨立的page資料夾,多個頁面使用則放在components** ``` └── src/ ├── components/ │ │ # the form is shown on the home and create todo page │ ├── todo-form/ │ │ # we could also ungroup this folder to make the components folder flat │ └── ui/ ├── contexts/ │ ├── modal.context.js │ └── todo-list.context.js ├── hooks/ │ ├── use-auth.js │ ├── use-modal.js │ ├── use-todo-form.js │ └── use-todo-list.js └── pages/ ├── create-todo/ ├── home/ │ ├── home-page.js │ │ # colocate -> the edit modal is only used on the home page │ ├── edit-todo-modal/ │ └── todo-list/ │ ├── todo-item.component.js │ ├── todo-list.component.js │ └── todo-list.test.js ├── login/ │ # don't forget the legal stuff :) ├── privacy/ ├── signup/ └── terms/ ``` 這樣的資料結構較為簡潔,能使新進人員容易識別頁面,也是許多開發專案常見的模式. 這為他們提供了一個調查代碼庫或調試應用程序的入口點 ### colocation 當contexts與hooks資料夾變的擁擠 許多複雜組件依然分散在各pages資料夾,並共用某些contexts與hooks邏輯.隨著代碼庫的增加,會使得文件關係的追蹤不易. ``` └── src/ ├── components/ ├── contexts/ │ ├── modal.context.js │ ├── ... # imagine more contexts here │ └── todo-list.context.js ├── hooks/ │ ├── use-auth.js │ ├── use-modal.js │ ├── ... # imagine more hooks here │ ├── use-todo-form.js │ └── use-todo-list.js └── pages/ ``` **解決方案:colocation!只要有可能,我們就會將contexts和hooks移動到使用它們的組件旁邊。** ``` └── src/ ├── components/ │ ├── todo-form/ │ └── ui/ ├── hooks/ │ │ # not much left in the global hooks folder │ └── use-auth.js └── pages/ ├── create-todo/ ├── home/ │ ├── home-page.js │ ├── edit-todo-modal/ │ └── todo-list/ │ ├── todo-item.component.js │ ├── todo-list.component.js │ ├── todo-list.context.js │ ├── todo-list.test.js │ │ # colocate -> this hook is only used by the todo-list component │ └── use-todo-list.js ├── login/ ├── privacy/ ├── signup/ └── terms/ ``` 這樣就能免於全域context使資料找尋不易.留下全域hooks供use_auth使用. 全域資料夾越少越好!!! 這種檔案結構的優點:我們可以一次掌握屬於一個特徵的所有文件。無需查看多個不同的文件夾來查找單個組件的代碼。 但這樣的結構會有以下問題: 1. 與“todo”相關的代碼分佈在多個文件夾中。一旦我們開始添加更多實體,將變得混亂。 1. 您是否會僅通過查看文件夾結構就猜到todo-list組件位於文件夾home中? ``` └── src/ ├── components/ ├── hooks/ └── pages/ ├── create-todo/ ├── home/ ├── login/ ├── privacy/ ├── signup/ └── terms/ ``` ### 按功能分組 要將todo拆分給兩個頁面分別做使用,並將工作待辦事項與購物清單事項分開。 解決方法: 新增一實體跟代辦事項列表 我們決定添加兩個新頁面。一個用於創建項目,一個用於顯示項目,包括其待辦事項。主頁也必須更改。它應該顯示所有項目的列表以及所有待辦事項的列表。 todo被多個頁面使用,因此必須將todo移動至componsnts中 ``` └── src/ ├── components/ │ ├── todo-form/ │ │ # is now shared between home and project page │ ├── todo-list/ │ │ ├── todo-item.component.js │ │ ├── todo-list.component.js │ │ ├── todo-list.context.js │ │ ├── todo-list.test.js │ │ └── use-todo-list.js │ └── ui/ └── pages/ ├── create-project/ ├── create-todo/ │ # shows now a list of projects and an overview of all todos ├── home/ │ ├── index.js │ ├── edit-todo-modal/ │ └── project-list/ ├── login/ ├── privacy/ │ # shows a list of todos belonging to a project ├── project/ ├── signup/ └── terms/ ``` 這樣的形式雖然較為乾淨,但仍有兩的問題: * 查看page頁面無法立即知道app是否有todos、users、projects。我們先處理資料夾create-todo與login和其他不重要的功能分開。 * 將頁面重複使用的component移除全域的components資料夾。這可以讓你知道,你在哪些資料夾使用到這些component。 你能自訂功能的定義,組合實體的 todo, project, user。而ui的資料夾也是如此,可以按照按鈕、表格...去分類。 ``` └── src/ ├── features/ │ │ # the todo "feature" contains everything related to todos │ ├── todos/ │ │ │ # this is used to export the relevant modules aka the public API (more on that in a bit) │ │ ├── index.js │ │ ├── create-todo-form/ │ │ ├── edit-todo-modal/ │ │ ├── todo-form/ │ │ └── todo-list/ │ │ │ # the public API of the component (exports the todo-list component and hook) │ │ ├── index.js │ │ ├── todo-item.component.js │ │ ├── todo-list.component.js │ │ ├── todo-list.context.js │ │ ├── todo-list.test.js │ │ └── use-todo-list.js │ ├── projects/ │ │ ├── index.js │ │ ├── create-project-form/ │ │ └── project-list/ │ ├── ui/ │ │ ├── index.js │ │ ├── button/ │ │ ├── card/ │ │ ├── checkbox/ │ │ ├── header/ │ │ ├── footer/ │ │ ├── modal/ │ │ └── text-field/ │ └── users/ │ ├── index.js │ ├── login/ │ ├── signup/ │ └── use-auth.js └── pages/ │ # all that's left in the pages folder are simple JS files │ # each file represents a page (like Next.js) ├── create-project.js ├── create-todo.js ├── index.js ├── login.js ├── privacy.js ├── project.js ├── signup.js └── terms.js ``` 談論功能分類的檔案架構: 你的架構需告訴使用者關於這個系統,而非系統的架構。如果你建構一健康的系統,你的外層必須要一目瞭然。 最外層使用資料型態來分類 ``` └── src/ ├── components/ ├── contexts/ └── hooks/ ``` 以下為最終架構 ``` └── src/ ├── features/ │ ├── todos/ │ ├── projects/ │ ├── ui/ │ └── users/ └── pages/ ├── create-project.js ├── create-todo.js ├── index.js ├── login.js ├── privacy.js ├── project.js ├── signup.js └── terms.js ``` 透過這種架構,你不會知道他使用何種檔案架構方式,但你能知道他是個專案管理工具。 運用這種描述性的架構,featurs與pages提供開發者兩個不同的切入點。 * 如果你想要改變component,你只知道他在pages/home.js裡,就能從此進入。 * 如果你想修改todoList,但不知道在哪,就可以透過feature/todo找到它。 在這環節我們將全域的contexts與hooks給移除,如有必要再建立即可。 ### 關於import 由於這樣的結構會導致../../ 的產生,因此使用絕對路徑 ``` import { Button } from "@features/ui/button"; ... ``` 如此一來無論todoList如何移動,路徑仍會相同 create react app 的絕對import也很容易設定。新增jsconfig.json檔案或 tsconfig.json for TypeScript 來定義路徑別名 ``` { "compilerOptions": { "baseUrl": ".", "paths": { "@features/*": ["src/features/*"], } } } ``` ### 將index.js作為共用API ``` └── src/ ├── features/ │ ├── todos/ │ │ │ # this is used to export the relevant modules aka the public API │ │ ├── index.js │ │ ├── create-todo-form/ │ │ ├── edit-todo-modal/ │ │ ├── todo-form/ │ │ └── todo-list/ │ │ │ # the public API of the component (exports the todo-list component and hook) │ │ ├── index.js │ │ ├── todo-item.component.js │ │ ├── todo-list.component.js │ │ ├── todo-list.context.js │ │ ├── todo-list.test.js │ │ └── use-todo-list.js │ ├── projects/ │ ├── ui/ │ └── users/ └── pages/ ``` 範例的資料夾結構如 :features/todo/todo-list ``` import { TodoList } from "./todo-list.component"; import { useTodoList } from "./use-todo-list"; export { TodoList, useTodoList }; ``` ``` export { TodoList } from "./todo-list.component"; export { useTodoList } from "./use-todo-list"; ``` 可看出 feature/todo/index.js 的子項目被多次利用 ``` export * from "./create-todo-form"; export * from "./todo-list"; // ... and so on ``` 要將todoList component 在page/home做使用,import巢狀如下 ``` import { TodoList } from "@features/todo/todo-list/todo-list.component"; ... ``` 可直接簡化成 ``` import { TodoList } from "@features/todo"; ... ``` 簡化的好處如下: 1. 更容易閱讀 1. 開發者無須了解內部檔案結構即可使用 1. 可自由定義公開哪些組件API 1. 只要公共API的位置不變,其他在feature的檔案皆可隨意改名、移動 ### 使用kekab-case的命名方式 在MacOS將Component.js更改成MyComponent.js在本地端正常運作,但在GitHuB的CI則會報錯。主要是MacOS預設不分大小寫,因此修改了檔名,也未偵測到改變。 ``` import MyComponent from "./MyComponent"; ```