# 檔案結構 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";
```