關於React + Redux文件目錄結構
初期
按照文件內容類型分:
-- project/
-- app/
-- assets/
-- icons/
-- components/
-- MyComponent/
-- index.jsx
-- style.scss
-- constants/
-- pages/
-- HomePage/
-- index.jsx
-- style.scss
-- redux/
-- model1/
-- model2/
-- store.js
-- styles/
-- global.scss
-- _variables.scss
-- index.js
-- .bablerc
-- webpack.config.js (如果用webpack的話)
-- package.json
-- ...
優點是直觀省力,多人團隊也較少放錯地方。缺點是項目越長越大後會變得非常雜亂。
一種新的方案
-- project/
-- app/
-- feature1/
-- components/
-- redux/
-- constants.js
-- utils.js
-- feature2/
-- pages/
-- Page1/
-- components/
-- index.jsx
-- style.scss
-- store.js
-- index.js
-- uiComponents
-- utils/
-- .bablerc
-- webpack.config.js (如果用webpack的話)
-- package.json
-- ...
這個方案首先嚴格要求區分業務邏輯和非業務邏輯,然後業務邏輯還要區分業務線。這對每個隊員的要求更高一些,分割文件的時候也更費力一些。
但好處也是顯而易見的,這直接在文件結構的層次就要求大家減少代碼間的耦合,讓人在代碼放哪多一些思考。另外在某個業務足夠壯大需要獨立出來時,也會比較省心省力
數據模型
如我在對Redux理解一問文中所說,將Redux最爲一個前端本地數據庫來對待的話,那麼文件的組織和分割根據數據模型(Model)來分就自然而然了。
這裏所謂的數據模型也就是MVC模式中的Model。在流行的RESTful API設計中通常按照某一數據模型來提供GET讀取,POST創建,DELETE刪除,PUT修改操作的接口,來最大化資源重複利用的可能性。
而在新近流行的GraphQL中更是讓前端自己將多個數據模型組織成一個請求的返回數據… 咳咳, 扯遠了。
只有讀取操作
現在的應用只讀不寫的需求還是比較多的。只讀需要處理的問題也比較少,可以簡單分成如下結構:
-- model/
-- actions.js
-- reducer.js
-- constants.js
-- selectors.js
-- index.js
index.js中就只需要暴露action, selector, contants以及Typescript或者flow使用的state的類型,在UI部件中就可以一行import優雅地拿到所有數據模型相關的方法和常量了。
讀寫操作並存
讀寫操作並存的結構就相對複雜了。
在用戶體驗要求極高的今天,寫操作意味着:
- 用戶要看到寫的進度
- 數據寫入後,用戶要看到界面上的更新
- 並且可能根據寫入的結果決定下一步的操作
根據以上條件,我可以知道:
- 寫操作需要跟讀用同一個state。
- 寫API請求成功後,API必須返回更新成功的完整數據模型。
原因之一是創建新數據的時候,只有寫入數據庫後這條數據纔有ID,有了ID前端纔有憑據找到用戶剛剛操作的數據。
原因之二是修改數據的過程是:先讀取現存的數據 > 用戶修改 > 提交更新。
在用戶修改的這個時間段內,很可能其他數據模型的更改會導致用戶修改的這個數據模型中有一個字段發生了前端無法預測的改動,然後這些無法預測的變化又引發其他無法預測的蝴蝶效應(可能就是用戶投訴,緊急修bug之類的連鎖反應哈哈哈)。 - 因爲上一條寫完直接拿到了新數據的關係,既然寫完需要反饋給用戶他的更新結果,此時就不需要再一次讀取同樣的數據了,浪費API請求。
那麼這個時候就要求寫API成功時,reducer將返回的新數據更新到state中。使用React-redux的話,因爲state的更新會觸發新的渲染,我們的UI就自動更新爲新數據了。
基於以上條件,一個讀寫操作並存的數據模型,我採取以下結構來乾淨地拆分文件:
-- model/
-- read/
-- actions.js
-- reducer.js
-- write/
-- actions.js
-- reducer.js
-- reducer.js
-- selectors.js
-- constants.js
只寫
這種情況比較少見,只寫的情況完全不需要用redux,所以這裏掠過。
根據數據模型來組織Redux文件的優點
一般來說,一個UI部件只需要對1~2個數據模型的讀寫操作。這些讀寫操作就包含了:
- 用action來讀寫state
- 用selector從state中拿相關數據,或者直接從state中讀取
- 而以上操作均需要綁定reducer
- 代碼裏可能還需要用一些state中使用的constants
- 如果使用Typescript或者flow之類類型定義的還需要使用相關的type
這樣在Code Splitting代碼拆分時,可減少無用代碼一起被打包,減少客戶端需要下載的包的大小。
儘量減少單個數據模型的深度
很多人容易將通同一業務線,哪怕是完全不同接口獲取的不同數據綁定在一個reducer中。這樣顯得更加整潔容易管理。
這在多數情況下都不成問題,但注意要是使用比如react-persist之類的工具,還有在更新迭代卻不想影響所有使用該數據模型的頁面時就會造成一些麻煩。
所以我建議儘量在combineReducer時將數據模型鋪鋪平。
舉個Blog的例子:
{
postList: { [key: string]: number[] },
post: {
[id: number]: {
title: string,
content: string,
comments: number[]
}
},
user: { [id: number]: User },
comment: { [id: number]: Comment },
...
};
而不要搞成:
{
posts: {
list: { [key: string]: Post[] },
post: { [key: id]: Post },
comment: { [id: number]: Comment }
},
user: { [id: number]: User },
...
}