背景
Redux 是 React 全家桶的重要一員,理解 Redux 可以更好的幫助我們開(kāi)發(fā)前端項(xiàng)目霍比。本文只談?wù)摳拍畹慕庾x幕袱,不講解具體的用法。
Redux 和 React-Redux
首先聲明 Redux 和 React-Redux 的區(qū)別悠瞬。 Redux 這個(gè)項(xiàng)目嚴(yán)格來(lái)說(shuō)们豌,并不局限于 React,它可以用于 ES6, Angular 浅妆,React望迎。而 React-Redux 是 React 官方提供的一個(gè)集成 Redux 的包。
為什么要 Redux
這個(gè)要從 React 的哲學(xué)講起凌外,在 React 中辩尊,有兩個(gè)重要的概念 props 和 state,
props 一般是外部組件傳入的趴乡,而 state 是組件內(nèi)部的狀態(tài)对省。也就是說(shuō)數(shù)據(jù)從外部更大的組件傳遞到內(nèi)部被包含的數(shù)據(jù)。
這樣的方式晾捏,非常直觀蒿涎,對(duì)于一些平鋪式的頁(yè)面非常有優(yōu)勢(shì),比如資訊類(lèi)應(yīng)用惦辛,資訊只需要瀑布流式的展示劳秋,資訊之間沒(méi)有交互。
可是并不是所有的頁(yè)面都那么簡(jiǎn)單,也有很多場(chǎng)景玻淑,我們頁(yè)面組件之間還是需要交互的嗽冒,這個(gè)時(shí)候就不一樣了。組件之間的關(guān)系是平級(jí)的补履,沒(méi)法直接傳遞數(shù)據(jù)的添坊。
沒(méi)有 Redux 的情況
針對(duì)非包含關(guān)系的組件間傳遞數(shù)據(jù)的問(wèn)題,React 官方文檔提到了一種解決辦法箫锤,見(jiàn) Lifting State Up, 其思路其實(shí)是添加一個(gè)組件贬蛙,我們暫時(shí)稱(chēng)它為外包組件,讓它包含需要交互的兩個(gè)組件谚攒,然后通過(guò)被包含的組件觸發(fā) callback 方法阳准,引起外包組件變化,然后外包組件再修改另一個(gè)組件馏臭。
這當(dāng)然是解決問(wèn)題的一種方式野蝇,但是這種方式卻會(huì)帶來(lái)一個(gè)問(wèn)題,因?yàn)橐坏┬枰换サ慕M件最近公共外包組件特別遠(yuǎn)的話括儒,需要大量地修改代碼,一層一層的傳遞 callback 函數(shù)绕沈。這樣的工作量簡(jiǎn)直不要太大。
Redux 是怎么做的
在寫(xiě) React 的時(shí)候七冲,通常我看到一個(gè)前端組件的時(shí)候,我們總是很自然的認(rèn)為組件應(yīng)該包含 view 和 state 兩個(gè)部分规婆。組件應(yīng)該高內(nèi)聚的維護(hù)其 view 和 state澜躺。 但是 Redux 不這么思考, Redux 認(rèn)為組件最好只維護(hù) view , 而 state 應(yīng)該在一個(gè)地方集中的維護(hù)抒蚜。
Redux 將所有的數(shù)據(jù)掘鄙,集中在一個(gè)地方,組件可以直接引用這個(gè)數(shù)據(jù)源嗡髓,同樣也有了修改這個(gè)公共數(shù)據(jù)源的能力操漠。通過(guò)這樣的方式解決了組件間數(shù)據(jù)流動(dòng)的問(wèn)題。
Redux 中重要的概念
知道了 Redux 做了什么饿这,現(xiàn)在我們來(lái)看看 Redux 為了實(shí)現(xiàn)這些功能浊伙,都提供了哪些概念。
Store
store 就是存儲(chǔ)全局?jǐn)?shù)據(jù)的地方长捧。組件可以通過(guò)連接到 store 來(lái)獲取到全局的數(shù)據(jù)嚣鄙。
Action
當(dāng)組件可以通過(guò)連接 store 來(lái)查看和修改全局?jǐn)?shù)據(jù),用于觸發(fā)變更串结。但是比起直接修改 store哑子,更好的方法是使用命令模式
舅列,組件只需要發(fā)布命令就好了,然后讓具體的執(zhí)行者去執(zhí)行卧蜓,這樣就可以將命令和執(zhí)行解耦開(kāi)帐要。 Redux 使用了這樣的設(shè)計(jì)模式,組件發(fā)送命令,然后由 Reducers 去執(zhí)行具體的命令弥奸。而 Redux 的 action 正是命令模式中的 command 榨惠。action 中包含命令的類(lèi)型 type 和命令的內(nèi)容。
Reducers
Reducers 就是命令模式
中的命令接受者盛霎,Reducers 接受到 action冒冬,然后根據(jù) action 的內(nèi)容去對(duì) store 執(zhí)行相應(yīng)的變更操作。
pure function (純函數(shù))
Reducers 的具體實(shí)現(xiàn)其實(shí)就是一個(gè)大的函數(shù)摩渺,Redux 官方文檔聲明成為 Reducers 的函數(shù)必須是一個(gè) pure funciton。所謂純函數(shù)
和我們常說(shuō)的無(wú)副作用函數(shù)
是有區(qū)別的剂邮。
純函數(shù): 在任何時(shí)刻摇幻,對(duì)于給定的一個(gè)入?yún)ⅲ挥形ㄒ灰粋€(gè)對(duì)應(yīng)的結(jié)果
根據(jù)上述的定義挥萌,類(lèi)似 f(x) = x + 1
绰姻。 這樣簡(jiǎn)單的函數(shù)就是純函數(shù),純函數(shù)最主要的概念是將時(shí)間區(qū)分出來(lái)了引瀑,所以類(lèi)似向服務(wù)端發(fā)起請(qǐng)求獲取文章 pv 的接口就不是純函數(shù)狂芋,因?yàn)?pv 會(huì)隨著時(shí)間的推移而發(fā)生改變。之所以要求是純函數(shù)憨栽,據(jù) Redux 作者在自己的博客 The Evolution of Flux Frameworks 中所說(shuō)的
Stores and actions are just pure functions. They are easily testable in isolation.
主要是為了易于測(cè)試帜矾,因?yàn)榧兒瘮?shù)便于做重放操作。
dispatch
顧名思義屑柔,就是分發(fā)器方法屡萤,負(fù)責(zé)將 action 分發(fā)到 Reducers 。
subsribe
訂閱方法掸宛,參數(shù)是監(jiān)聽(tīng)器死陆,監(jiān)聽(tīng) store 的變更事件,當(dāng) store 發(fā)生變化的時(shí)候唧瘾,觸發(fā)回調(diào)函數(shù)
connect
這個(gè)方法功能主要的是將當(dāng)前組件和全局的 store 連接措译,并將 dispatch 注入到當(dāng)前的組件的 props 中。 當(dāng) store 更新的時(shí)候饰序,會(huì)通過(guò) connect 傳入的回調(diào)函數(shù)將更新后的數(shù)據(jù)通知到當(dāng)前組件领虹。而且由于 connect 可以方便的指定當(dāng)前組件需要關(guān)心的數(shù)據(jù),所以用了 connect 后菌羽, subsribe 基本上就沒(méi)有使用必要了掠械。
Redux 數(shù)據(jù)流程
Store 與 render 的觸發(fā)
當(dāng) store 發(fā)生變化的時(shí)候由缆,會(huì)通過(guò) connect 中的回調(diào)函數(shù)觸發(fā)組件重新 render,以此來(lái)改變頁(yè)面內(nèi)容猾蒂。
Store 的局部改變
當(dāng) store 發(fā)生改變的時(shí)候均唉,改變的數(shù)據(jù)可能只是局部的一小部分,當(dāng)小部分?jǐn)?shù)據(jù)發(fā)生變更的時(shí)候肚菠,redux 會(huì)只更新局部的 view 舔箭,而其它不發(fā)生改變,這樣有助于提升性能蚊逢。
如何判斷數(shù)據(jù)是否更新
數(shù)據(jù)要更新了才能觸發(fā) render层扶,但是如何判斷數(shù)據(jù)已經(jīng)發(fā)生改變了呢。眾所周知烙荷,對(duì)于嵌套層次特別深的數(shù)據(jù)镜会,很難判斷數(shù)據(jù)是否 update,而且如果要判斷终抽,必須遞歸地判斷戳表,這樣性能是比較差的,所以 react-redux 使用了淺層判斷的方式昼伴,類(lèi)比 C++ 和 Java 其實(shí)就是判斷對(duì)象的內(nèi)存地址匾旭,在 JavaScript 中其實(shí)就是 ==
判斷。
嵌套判斷
因?yàn)槭褂昧藴\層判斷圃郊,所以對(duì)于一個(gè)數(shù)據(jù)价涝,比如 a.b.c.d ,如果我們要更新 d 數(shù)據(jù)持舆,如果我們只更新了 d色瘩,是不夠的,因?yàn)?a 沒(méi)有更新逸寓,無(wú)法通過(guò)淺層判斷泞遗。但是僅僅更新 a,d 也不夠,因?yàn)榈搅?b 依舊無(wú)法通過(guò)淺層判斷席覆,所以如果要更新d ,則 d,c,b,a 都要更新史辙,也即更新一個(gè)數(shù)據(jù),其所有的父對(duì)象也都要更新佩伤。
這個(gè)要特別關(guān)注聊倔,因?yàn)榻?jīng)常出現(xiàn)的頁(yè)面沒(méi)有重新 render 的原因,大多是因?yàn)闆](méi)有重新生成父對(duì)象生巡。
Redux 和傳統(tǒng)的 發(fā)布/訂閱 系統(tǒng)
雖然 Redux 中也有發(fā)布和訂閱耙蔑,但和我們通常使用的基于觀察者模式的 發(fā)布/訂閱系統(tǒng)
卻有所不同。
Redux 添加組件不符合開(kāi)閉原則
我們常用的發(fā)布/訂閱模型孤荣,訂閱者向消息中心訂閱消息甸陌,然后收到消息须揣,在這個(gè)過(guò)程中,不需要修改現(xiàn)有代碼钱豁,而在 Redux 中添加組件耻卡,不僅需要修改初始化 store,還需要修改 redurces ,用于配合修改新添加的組件的 state。在這個(gè)過(guò)程中存在代碼修改牲尺,不符合開(kāi)閉原則卵酪。
沒(méi)有消息堆積功能
Redux 中沒(méi)有原生的消息堆積的概念。
組件不符合內(nèi)聚性
使用了 Redux 的組件谤碳,因?yàn)槠浜腿孔兞拷壎ɡ?ǎ簿鸵馕吨驼麄€(gè)應(yīng)用緊耦合了,這是不符合高內(nèi)聚原則的蜒简,也即意味著使用了 Redux 的組件跨應(yīng)用的復(fù)用性差瘸羡,所以 Redux 更適合開(kāi)發(fā)有復(fù)雜交互的 webApp,而不適合用于開(kāi)發(fā)需要高可復(fù)用性的組件搓茬。如果自己開(kāi)發(fā)的組件也有一定的交互最铁,可以考慮自行開(kāi)發(fā)一套簡(jiǎn)易的發(fā)布/訂閱
工具。我在我自己的項(xiàng)目中垮兑,開(kāi)發(fā)的組件,為了能不用 redux 實(shí)現(xiàn)消息的訂閱和發(fā)布漱挎,自行開(kāi)發(fā)了如下簡(jiǎn)易的發(fā)布/訂閱
工具
const dispatchMap = [];
export function registerListener(topic,callback,tags = '*'){
let listener = {
topic: topic,
tags: tags,
callback: callback
};
dispatchMap.push(listener);
}
export function publish(msg){
dispatchMap.forEach( (listener) => {
if(listener.topic == msg.topic){
if(listener.tags === '*' || listener.tags.includes(msg.tag)){
listener.callback(msg.content);
}
}
});
}
總結(jié)
redux 之所以能在 react 中流行系枪,歸根結(jié)底是因?yàn)樗芙鉀Q react 中數(shù)據(jù)流動(dòng)性差的問(wèn)題,搞明白這點(diǎn)之后磕谅,很多 redux 的設(shè)計(jì)也就能理解了私爷。所以說(shuō)學(xué)習(xí)一個(gè)新的東西,我認(rèn)為最重要的部分膊夹,是先學(xué)習(xí)我們?yōu)槭裁葱枰幕耄?dāng)我們搞明白為什么需要它之后,然后再學(xué)習(xí)它是怎么解決的放刨,就會(huì)少很多疑惑工秩,不會(huì)一來(lái)看到一大把術(shù)語(yǔ)而不知所措。