React+Redux工程目錄結構,最佳實踐
參考
- Redux進階系列1: React+Redux專案結構最佳實踐
- 《深入淺出React和Redux》一書的第四章,P76,【4.2 程式碼檔案的組織方式】。
React+Redux 工程目錄結構組織
按角色型別組織
如果你用 MVC 框架開發過應用(無論是前端開發還是後端開發),應該知道 MVC 框架之下,通常有這樣一種程式碼組織方式:
controllers/ todoController.js filterController.js models/ todoModel.js filterModel.js views/ todo.js todoItem.js filter.js
Controller、Model、View 分別代表三種模組角色。這種思想,對應到 Redux 應用,就有這種組織方式:
reducers/
todoReducer.js
filterReducer.js
actions/
todoAction.js
filterActions.js
components/
todoList.js
todoItem.js
filter.js
containers/
todoListContainer.js
todoItemContainer.js
角色如下:
- components 目錄包含所有的展示元件。
- containers 目錄包含所有的容器元件。
- actions 目錄包含所有 action 建構函式。
- reducers 目錄包含所有 Redux 的 reducer。
充當component、container、action、reducer等不同角色的檔案,分別放在對應的多個資料夾下。
Redux 官網的 todomvc 示例採用了這種專案結構劃分,如果專案功能比較簡單,也還是可以採用這種方式的。
└─src │ index.js │ ├─actions │ index.js │ ├─components │ App.js │ Footer.js │ Header.js │ Link.js │ MainSection.js │ TodoItem.js │ TodoList.js │ TodoTextInput.js │ ├─constants │ ActionTypes.js │ TodoFilters.js │ ├─containers │ FilterLink.js │ Header.js │ MainSection.js │ VisibleTodoList.js │ ├─reducers │ index.js │ todos.js │ visibilityFilter.js │ └─selectors index.js
按角色型別組織的缺點
使用這種結構組織專案,每當增加一個新功能時,需要在containers和components資料夾下增加這個功能需要的元件,還需要在actions和reducers資料夾下,分別新增Redux管理這個功能使用到的action和reducer,如果action type是放在另外一個資料夾的話,還需要在這個資料夾下增加新的action type檔案。所以,開發一個功能時,你需要頻繁的切換路徑,修改不同的檔案。當專案逐漸變大時,這種專案結構是非常不方便的。
按照功能組織
在Redux框架下,我們可以嚴格按照模組化思想來開發我們的應用,以最大化的解耦應用,提高程式碼重用和可維護性。按照功能組織應用的程式碼,就是這種思想的應用。
一個功能模組對應一個資料夾,這個功能所用到的component、container、action、reducer等檔案,都存放在這個資料夾下。
拿Todo應用來說,兩個基本的功能就是TodoList和Filter,所以按功能組織就是這樣子:
todoList/
components/
componentA.js
componentB.js
containers.js
actions.js
actionTypes.js
index.js
reducer.js
filter/
components/
componentA.js
componentB.js
container.js
actions.js
actionTypes.js
index.js
reducer.js
參考了《深入淺出React和Redux》一書中給出的示例,同時結合自己的理解,有部分調整。
每個功能模組對應一個目錄,分別是todoList和filter,每個目錄下包含同樣的角色檔案:
- components 目錄下包含功能模組中所有的展示元件。
- container.js 定義容器元件。
- actionTypes.js 定義action型別。
- actions.js 定義action建構函式。
- reducer.js 定義這個功能模組如果響應actions.js定義的動作。
- index.js 把所有的角色匯入,然後統一匯出。
這種組織方式下,當你要修改某個模組時,只要關注對應的目錄即可,所有需要修改的程式碼檔案都能在這個目錄下找到。
需要注意的是,按功能組織下的每個模組,都有一個index.js,明確了模組對外的介面。
按照功能組織也有缺點
這種結構也有一個問題,Redux會將整個應用的狀態作為一個store來管理,不同的功能模組之間可以共享store中的部分狀態(專案越複雜,這種場景就會越多),於是當你在feature1的container中dispatch一個action,很可能會影響feature2的狀態,因為feature1和feature2共享了部分狀態,會響應相同的action。這種情況下,不同模組間的功能被耦合到了一起。
混合前面兩種方式–Ducks
Ducks: Redux Reducer Bundles,是一種新的Redux專案結構組織方式的提議。它提倡將相關聯的reducer、action types和action寫到一個檔案裡。本質上是以應用的狀態作為模組的劃分依據,而不是以介面功能作為劃分模組的依據。這樣的一個檔案(模組)如下:
// widget.js
// Actions
export const types = {
const LOAD : 'widget/LOAD',
const CREATE : 'widget/CREATE',
const UPDATE : 'widget/UPDATE',
const REMOVE : 'widget/REMOVE'
}
const initialState = {
widget: null,
isLoading: false,
}
// Reducer
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
types.LOAD:
//...
types.CREATE:
//...
types.UPDATE:
//...
types.REMOVE:
//...
default: return state;
}
}
// Action Creators
// 這樣定義而不是每個action都export避免了import時把所有action都列出來的繁瑣。
export const actions = {
loadWidget: function() {
return { type: types.LOAD };
},
createWidget: createWidget(widget) {
return { type: types.CREATE, widget };
},
updateWidget: function(widget) {
return { type: types.UPDATE, widget };
},
removeWidget: function(widget) {
return { type: types.REMOVE, widget };
}
}
由於使用Ducks提議的目錄結構,action creators和reducer定義在同一個檔案中,為了避免了引入額外的物件,import *
的匯入方式已經不推薦。為了使得匯入更加方便以及程式碼更加簡潔,比較好的辦法就是,把action creators和action types定義到一個名稱空間中(請看以上程式碼)。
這樣,我們在container中使用actions時,可以通過import { actions } from 'path/to/module.js'
引入,避免了import時把所有action都列出來的繁瑣。
同樣的,我們要在container中使用action types時,可以通過import { types } from 'path/to/module.js'
引入。
整體的目錄結構如下:
components/ (應用級別的通用元件)
containers/
feature1/
components/ (功能拆分出的專用元件)
feature1.js (容器元件)
index.js (feature1對外暴露的介面)
redux/
index.js (combineReducers)
module1.js (reducer, action types, actions creators)
module2.js (reducer, action types, actions creators)
index.js
應用
應用Ducks工程目錄組織方式的思想,《React進階之路》一書的示例程式碼第9章,專案bbs-redux-reselect工程目錄結構如下:
└─src
│ index.js
│
├─components
│ ├─Header
│ │ index.js
│ │ style.css
│ │
│ ├─Loading
│ │ index.js
│ │ style.css
│ │
│ └─ModalDialog
│ index.js
│ style.css
│
├─containers
│ ├─App
│ │ index.js
│ │
│ ├─Home
│ │ index.js
│ │
│ ├─Login
│ │ index.js
│ │ style.css
│ │
│ ├─Post
│ │ │ index.js
│ │ │ style.css
│ │ │
│ │ └─components
│ │ ├─CommentList
│ │ │ index.js
│ │ │ style.css
│ │ │
│ │ ├─CommentsView
│ │ │ index.js
│ │ │ style.css
│ │ │
│ │ ├─PostEditor
│ │ │ index.js
│ │ │ style.css
│ │ │
│ │ └─PostView
│ │ index.js
│ │ style.css
│ │
│ └─PostList
│ │ index.js
│ │ style.css
│ │
│ └─components
│ ├─PostItem
│ │ index.js
│ │ style.css
│ │
│ └─PostsView
│ index.js
│
├─images
│ like-default.png
│ like.png
│
├─redux
│ │ configureStore.js
│ │
│ └─modules
│ app.js
│ auth.js
│ comments.js
│ index.js
│ posts.js
│ ui.js
│ users.js
│
└─utils
AsyncComponent.js
connectRoute.js
date.js
request.js
SHA1.js
url.js