---
# System prepended metadata

title: 檔案結構 File Structure

---

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