如何合理地設(shè)計(jì) state
- 象設(shè)計(jì)數(shù)據(jù)庫(kù)一樣設(shè)計(jì) state锈拨,state 中的每一部分狀態(tài)看作數(shù)據(jù)庫(kù)中的一張表十拣。
- 把整個(gè)應(yīng)用的狀態(tài)按照領(lǐng)域(Domain)分成若干子 state叙身,子 state 之間不能保存重復(fù)的數(shù)據(jù)渔扎。
- state 以鍵值對(duì)的結(jié)構(gòu)存儲(chǔ)數(shù)據(jù),以記錄的 key/ID 作為記錄的索引信轿,記錄中的其他字段都依賴(lài)于索引(即類(lèi)似數(shù)據(jù)庫(kù)表中的主鍵)晃痴。
- state 中不能保存可以通過(guò)已有數(shù)據(jù)計(jì)算而來(lái)的數(shù)據(jù),即 state 中的字段不互相依賴(lài)财忽。
- 一個(gè)狀態(tài)節(jié)點(diǎn)只屬于一個(gè)模塊愧旦。
- 盡量避免冗余數(shù)據(jù)。
- 樹(shù)形結(jié)構(gòu)扁平化定罢,層次盡量不要太深笤虫,最好保持在三層以?xún)?nèi)。
設(shè)計(jì) state 總結(jié)
設(shè)計(jì) Redux State 的關(guān)鍵在于祖凫,像設(shè)計(jì)數(shù)據(jù)庫(kù)一樣設(shè)計(jì) state琼蚯。把 state 看作應(yīng)用在內(nèi)存中的一個(gè)數(shù)據(jù)庫(kù),action惠况、reducer 等看作操作這個(gè)數(shù)據(jù)庫(kù)的 SQL 語(yǔ)句遭庶。
state 的 key
- createStore 的參數(shù) reducer 的 key 即為 store 樹(shù)上的 key,類(lèi)似表名稠屠,在 reducer 里僅能操作 reducer 的 key 對(duì)應(yīng)的 store峦睡,不能操作別的 key 下的數(shù)據(jù),所以权埠,也不用給前綴榨了。
- 而在 mapStateToProps 函數(shù)里,是需要寫(xiě) key攘蔽,即需要指定“表名”的龙屉。
redux 的三大原則
單一數(shù)據(jù)源
- 整個(gè)應(yīng)用的 state 被儲(chǔ)存在一棵 object tree 中,并且這個(gè) object tree 只存在于唯一一個(gè) store 中满俗。
- 這讓同構(gòu)應(yīng)用開(kāi)發(fā)變得非常容易转捕。來(lái)自服務(wù)端的 state 可以在無(wú)需編寫(xiě)更多代碼的情況下被序列化并注入到客戶(hù)端中作岖。由于是單一的 state tree ,調(diào)試也變得非常容易五芝。在開(kāi)發(fā)中痘儡,你可以把應(yīng)用的 state 保存在本地,從而加快開(kāi)發(fā)速度枢步。此外谤辜,受益于單一的 state tree ,以前難以實(shí)現(xiàn)的如“撤銷(xiāo)/重做”這類(lèi)功能也變得輕而易舉价捧。
state 是只讀的
- 唯一改變 state 的方法就是觸發(fā) action,action 是一個(gè)用于描述已發(fā)生事件的普通對(duì)象涡戳。
- 這樣確保了視圖和網(wǎng)絡(luò)請(qǐng)求都不能直接修改 state结蟋,相反它們只能表達(dá)想要修改的意圖。因?yàn)樗械男薷亩急患谢幚碛嬲茫覈?yán)格按照一個(gè)接一個(gè)的順序執(zhí)行嵌屎,因此不用擔(dān)心 race condition 的出現(xiàn)。 Action 就是普通對(duì)象而已恍涂,因此它們可以被日志打印宝惰、序列化、儲(chǔ)存再沧、后期調(diào)試或測(cè)試時(shí)回放出來(lái)尼夺。
使用純函數(shù)來(lái)執(zhí)行修改
- 為了描述 action 如何改變 state tree ,你需要編寫(xiě) reducers炒瘸。
- Reducer 只是一些純函數(shù)淤堵,它接收先前的 state 和 action,并返回新的 state顷扩。剛開(kāi)始你可以只有一個(gè) reducer拐邪,隨著應(yīng)用變大,你可以把它拆成多個(gè)小的 reducers隘截,分別獨(dú)立地操作 state tree 的不同部分扎阶,因?yàn)?reducer 只是函數(shù),你可以控制它們被調(diào)用的順序婶芭,傳入附加數(shù)據(jù)东臀,甚至編寫(xiě)可復(fù)用的 reducer 來(lái)處理一些通用任務(wù),如分頁(yè)器犀农。
Redux 三個(gè)基本概念
action
- action 是把數(shù)據(jù)從應(yīng)用層傳遞到 store 的有效載體啡邑,它是 store 數(shù)據(jù)的唯一來(lái)源。
- action 本質(zhì)上是 JavaScript 普通對(duì)象井赌。
- 我們約定谤逼,action 內(nèi)必須使用一個(gè)字符串類(lèi)型的 type 字段來(lái)表示將要執(zhí)行的動(dòng)作瑰剃。
- action 僅僅表示某對(duì)象發(fā)生了什么行為,我們應(yīng)該盡量減少在 action 中傳遞的數(shù)據(jù)脊另。
reducer
- action 只是描述了有事情發(fā)生了這一事實(shí)塘娶,并沒(méi)有指明應(yīng)用如何更新 state。
- 而這正是 reducer 要做的事情枝冀。reducer 就是一個(gè)純函數(shù)舞丛,接收當(dāng)前 state 和 action,返回新的 state果漾。函數(shù)形式:
(previousState, action) => newState
- 只要傳入?yún)?shù)相同球切,返回的 newState 就一定相同。沒(méi)有特殊情況绒障、沒(méi)有副作用吨凑,沒(méi)有 API 請(qǐng)求、沒(méi)有變量修改户辱,單純執(zhí)行計(jì)算鸵钝。
- 保持 reducer 純凈非常重要。永遠(yuǎn)不要在 reducer 里做這些操作:
- 修改傳入?yún)?shù)庐镐;
- 執(zhí)行有副作用的操作恩商,如 API 請(qǐng)求和路由跳轉(zhuǎn);
- 調(diào)用非純函數(shù)必逆,如 Date.now() 或 Math.random()怠堪。
- 記得不要修改 previousState 的值,創(chuàng)建一個(gè)新的對(duì)象返回給 newState名眉。
store
使用 reducers 來(lái)根據(jù) action 更新 state, 存儲(chǔ)在 store 中研叫。store 把之前創(chuàng)建的 action 和 reducer 聯(lián)系在一起。
一個(gè) Redux 應(yīng)用中只有一個(gè) store璧针,store 保存了唯一數(shù)據(jù)源嚷炉。
store 的職責(zé)有:
- 持有應(yīng)用的 state;
- 提供 getState() 方法獲取 state探橱;
- 提供 dispatch(action) 方法更新 state申屹;
- 通過(guò) subscribe(listener) 注冊(cè)監(jiān)聽(tīng)器;
- 通過(guò) subscribe(listener) 返回的函數(shù)注銷(xiāo)監(jiān)聽(tīng)器。
let unsubscribe = store.subscribe(() => console.log(store.getState()) ); unsubscribe();
redux 數(shù)據(jù)流
redux 架構(gòu)使用嚴(yán)格的單向數(shù)據(jù)流動(dòng)方式隧膏,其生命周期分為以下四步:
- 應(yīng)用調(diào)用 store.dispatch(action) 發(fā)送 Action
- redux 根據(jù)傳入的 action 調(diào)用對(duì)應(yīng)的 reducer 方法
- 根 reducer 把子 reducer 的結(jié)果合并成一顆 state 樹(shù)
- redux store 保存根 reducer 生成的 state 樹(shù)
得到的 state 樹(shù)即為當(dāng)前應(yīng)用的下一個(gè) state哗讥,所有訂閱 store.subscribe(listener) 的監(jiān)聽(tīng)器都將被調(diào)用。監(jiān)聽(tīng)器可以調(diào)用 store.getState() 獲得當(dāng)前 state胞枕。
React-Redux 組件
- 如果一個(gè)組件既有 UI 又有業(yè)務(wù)邏輯杆煞,那怎么辦?回答是,將它拆分成:外面是一個(gè)容器組件决乎,里面包了一個(gè) UI 組件队询。前者負(fù)責(zé)與外部的通信,將數(shù)據(jù)傳給后者构诚,由后者渲染出視圖蚌斩。
- React-Redux 規(guī)定,所有的 UI 組件都由用戶(hù)提供范嘱,容器組件則是由 React-Redux 自動(dòng)生成送膳。也就是說(shuō),用戶(hù)負(fù)責(zé)視覺(jué)層丑蛤,狀態(tài)管理則是全部交給它叠聋。
UI 組件(presentational component)
UI 組件負(fù)責(zé) UI 的呈現(xiàn)
- 只負(fù)責(zé) UI 的呈現(xiàn),不帶有任何業(yè)務(wù)邏輯
- 沒(méi)有狀態(tài)(即不使用 this.state 這個(gè)變量)
- 所有數(shù)據(jù)都由參數(shù)(this.props)提供
- 不使用任何 Redux 的 API
容器組件(container component)
負(fù)責(zé)管理數(shù)據(jù)和邏輯
- 負(fù)責(zé)管理數(shù)據(jù)和業(yè)務(wù)邏輯受裹,不負(fù)責(zé) UI 的呈現(xiàn)
- 帶有內(nèi)部狀態(tài)
- 使用 Redux 的 API
react-redux
react-redux 提供了一個(gè) connect 函數(shù)碌补,用于把 react 組件和 redux 的 store 連接起來(lái),生成一個(gè)容器組件名斟,負(fù)責(zé)數(shù)據(jù)管理和業(yè)務(wù)邏輯,其簽名如下:
connect(mapStateToProps, mapDispatchToProps)(componentName)
mapStateToProps
輸入邏輯:外部的數(shù)據(jù)(即state對(duì)象)如何轉(zhuǎn)換為 UI 組件的參數(shù)魄眉。
- mapStateToProps 是一個(gè)函數(shù)砰盐。它的作用就是像它的名字那樣,建立一個(gè)從(外部的)state 對(duì)象到(UI 組件的)props 對(duì)象的映射關(guān)系坑律。
- 作為函數(shù)岩梳,mapStateToProps 執(zhí)行后應(yīng)該返回一個(gè)對(duì)象,里面的每一個(gè)鍵值對(duì)就是一個(gè)映射晃择。
- mapStateToProps 會(huì)訂閱 store冀值,每當(dāng) state 更新的時(shí)候,就會(huì)自動(dòng)執(zhí)行宫屠,重新計(jì)算 UI 組件的參數(shù)列疗,從而觸發(fā) UI 組件的重新渲染。
- mapStateToProps 的第一個(gè)參數(shù)總是 state 對(duì)象浪蹂,還可以使用第二個(gè)參數(shù)(假如參數(shù)名為:ownProps)抵栈,代表容器組件的 props 對(duì)象。
- 使用 ownProps 作為參數(shù)后坤次,如果容器組件的參數(shù)發(fā)生變化古劲,也會(huì)引發(fā) UI 組件重新渲染。
- connect 方法可以省略 mapStateToProps 參數(shù)缰猴,那樣的話产艾,UI 組件就不會(huì)訂閱 store,就是說(shuō) store 的更新不會(huì)引起 UI 組件的更新。
mapDispatchToProps
輸出邏輯:用戶(hù)發(fā)出的動(dòng)作如何變?yōu)?Action 對(duì)象闷堡,繼而從 UI 組件傳出去隘膘。
- mapDispatchToProps 是 connect 函數(shù)的第二個(gè)參數(shù),用來(lái)建立 UI 組件的參數(shù)到 store.dispatch 方法的映射缚窿。
- 換一種說(shuō)法棘幸,它負(fù)責(zé)把需要用到的 action 映射到展示組件的 props 上。
- 也就是說(shuō)倦零,它定義了哪些用戶(hù)的操作應(yīng)該當(dāng)作 Action误续,傳給 store。
- mapDispatchToProps 可以是一個(gè)函數(shù)扫茅,也可以是一個(gè)對(duì)象蹋嵌。
- 如果 mapDispatchToProps 是一個(gè)函數(shù),會(huì)得到 dispatch 和 ownProps(容器組件的 props 對(duì)象)兩個(gè)參數(shù)葫隙。
- mapDispatchToProps 作為函數(shù)栽烂,應(yīng)該返回一個(gè)對(duì)象,該對(duì)象的每個(gè)鍵值對(duì)都是一個(gè)映射恋脚,定義了 UI 組件的參數(shù)怎樣發(fā)出 Action腺办。
const mapDispatchToProps = (
dispatch,
ownProps
) => {
return {
onClick: () => {
dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: ownProps.filter
});
}
};
}
- 如果 mapDispatchToProps 是一個(gè)對(duì)象,它的每個(gè)鍵名也是對(duì)應(yīng) UI 組件的同名參數(shù)糟描,鍵值應(yīng)該是一個(gè)函數(shù)怀喉,會(huì)被當(dāng)作 Action creator,返回的 Action 會(huì)由 Redux 自動(dòng)發(fā)出船响。
- 舉例來(lái)說(shuō)躬拢,上面的 mapDispatchToProps 寫(xiě)成對(duì)象就是下面這樣。
const mapDispatchToProps = {
onClick: (filter) => {
type: 'SET_VISIBILITY_FILTER',
filter: filter
};
}
參考
- 有關(guān) mapStateToProps 與 mapDispatchToProps见间,還可以參考《深入淺出React和Redux》P71-73聊闯,【3.2.5 React-Redux】章節(jié)。
-
阮一峰的網(wǎng)絡(luò)日志 -> Redux 入門(mén)教程(三):React-Redux 的用法
有對(duì) connect(mapStateToProps, mapDispatchToProps)(componentName) 的詳細(xì)解讀米诉,上面有關(guān) mapStateToProps 與 mapDispatchToProps 的解讀文字菱蔬,均來(lái)自這篇文章(略有修改)。
- Redux 官方文檔中文翻譯
- Redux 文檔 -> 英文原版
- 前端手記 TodoMVC 之 Redux 篇
擴(kuò)展閱讀
- 劉一奇 -> React 與 Redux 系列教程史侣,一共八篇文章
- 理解Redux應(yīng)用架構(gòu)——(一)Redux結(jié)構(gòu)概覽
- 理解Redux應(yīng)用架構(gòu)——(二)創(chuàng)建store的createStore
- 理解Redux應(yīng)用架構(gòu)——(三)讓你愛(ài)上的Redux middleware
個(gè)人學(xué)習(xí)心得項(xiàng)目地址
- 托管在 gitee 上的項(xiàng)目鏈接 :https://gitee.com/uncleAndyChen/react-full-stack-learning
- 托管在 github 上的項(xiàng)目鏈接:https://github.com/uncleAndyChen/react-full-stack-learning
關(guān)于作者
- github: https://github.com/uncleAndyChen
- gitee: https://gitee.com/uncleAndyChen
- 個(gè)人博客:https://www.lovesofttech.com
- CSDN博客:https://blog.csdn.net/runAndRun
- 郵箱:andy@lovesofttech.com