摘要:正如我們前面的教程所提到的,在組件之間流通數(shù)據(jù)更確切的說(shuō),這被叫做單向數(shù)據(jù)流數(shù)據(jù)沿著一個(gè)方向從父組件流到子組件。這就是如何使數(shù)據(jù)流變得更簡(jiǎn)單的原因。它是一種傾向單向數(shù)據(jù)流比如的設(shè)計(jì)模式。這是因?yàn)榭偸墙邮芎头祷貭顟B(tài)用來(lái)更新。
前言
近期接觸React項(xiàng)目,學(xué)到許多新知識(shí)點(diǎn),網(wǎng)上教程甚多,但大多都把知識(shí)點(diǎn)分開(kāi)來(lái)講,初學(xué)者容易陷入學(xué)習(xí)的誤區(qū),摸不著頭腦,本人在學(xué)習(xí)中也遇到許多坑。此篇文章是筆者看過(guò)的寫得比較詳細(xì)的具體的,同時(shí)能把所有的知識(shí)點(diǎn)統(tǒng)一串聯(lián)起來(lái),非常適合初學(xué)者學(xué)習(xí)。由于文檔是英文版,考慮到大伙英語(yǔ)水平各不相同,故做此次翻譯,一來(lái)深化自己對(duì)Redux的體系認(rèn)知,二來(lái)方便大家理解閱讀。
由于文中出現(xiàn)大量技術(shù)名詞,應(yīng)適當(dāng)結(jié)合原文進(jìn)行閱讀,原文連接:
《Leveling Up with React: Redux》 By Brad Westfall On March 28, 2016
此篇教程是原文作者一系列教程的最后一篇,這里只對(duì)該篇進(jìn)行翻譯,剩余的幾篇有時(shí)間會(huì)繼續(xù)進(jìn)行翻譯,對(duì)于文中出現(xiàn)的翻譯錯(cuò)誤,歡迎大家積極指正。
系列文章本教程是 Brad Westfall 三部分系列教程的最后一篇。我們將學(xué)習(xí)如何有效地管理狀態(tài),使其跨越整個(gè)應(yīng)用程序,并且可以在沒(méi)有嚴(yán)重復(fù)雜度的情況下進(jìn)行衡量。在React的學(xué)習(xí)道路上我們已經(jīng)走了這么遠(yuǎn),現(xiàn)在是時(shí)候來(lái)跨過(guò)終點(diǎn),獲得這個(gè)物超所值的全部成長(zhǎng)歷程。
第一部分:React Router
第二部分:Container Components
第三部分:Redux(你在這里)
Redux 是一個(gè)用來(lái)管理JavaScript應(yīng)用中 data-state(數(shù)據(jù)狀態(tài))和UI-state(UI狀態(tài))的工具,對(duì)于那些隨著時(shí)間推移狀態(tài)管理變得越來(lái)越復(fù)雜的單頁(yè)面應(yīng)用(SPAs)它是比較理想的,同時(shí),它又是和框架無(wú)關(guān)的,因此,盡管它是提供給React使用的,但它也可以結(jié)合Angular 或者 jQuery來(lái)使用。
另外,它的設(shè)想來(lái)自一個(gè)叫做“時(shí)間旅行”的實(shí)驗(yàn),這是真實(shí)的,我們后面會(huì)講到。
正如我們前面的教程所提到的,React 在組件之間流通數(shù)據(jù).更確切的說(shuō),這被叫做“單向數(shù)據(jù)流”——數(shù)據(jù)沿著一個(gè)方向從父組件流到子組件。由于這個(gè)特性,對(duì)于沒(méi)有父子關(guān)系的兩個(gè)組件之間的數(shù)據(jù)交流就變得不是那么顯而易見(jiàn)。
React 不推薦組件對(duì)組件直接交流的這種方式,盡管它確實(shí)有一些特征可以支持這個(gè)方法,但在許多組件之間進(jìn)行直接的組件對(duì)組件的交流被認(rèn)為是不好的做法,因?yàn)檫@樣會(huì)容易出錯(cuò),并且導(dǎo)致spaghetti code —— 過(guò)時(shí)的代碼, 很難維護(hù)。
React 提供了一個(gè)建議,但是他們希望你能自己來(lái)實(shí)現(xiàn)它。這里是React官方文檔里的一段話:
想讓兩個(gè)沒(méi)有父子關(guān)系的組件進(jìn)行交流,你可以通過(guò)設(shè)置你自己的全局事件機(jī)制…… Flux 模式就是其中一個(gè)可行的方案
這里 Redux 就排上用場(chǎng)了。Redux提供了一個(gè)解決方案,通過(guò)將應(yīng)用程序所有的狀態(tài)都存儲(chǔ)在一個(gè)地方,叫做“store”。然后組件就可以“dispatch”狀態(tài)的改變給這個(gè)store,而不是直接跟另外的組件交流。所有的組件都應(yīng)該意識(shí)到狀態(tài)的改變可以“subscribe”給store。
可以把store想象成是應(yīng)用程序中所有狀態(tài)改變的中介。隨著Redux的介入,所有的組件不再相互直接交流,而是所有的狀態(tài)改變必須通過(guò)store這個(gè)單一的真實(shí)來(lái)源。
這和那些應(yīng)用程序中不同的部分直接交流的策略有很大的不同。有時(shí),那些策略被認(rèn)為是容易出錯(cuò)和混亂的原因:
有了Redux,所有的組件都從store中來(lái)獲取他們的狀態(tài),變得非常清晰。同樣,組件狀態(tài)的改變都發(fā)送給了store,也很清晰。組件初始化狀態(tài)的改變只需要關(guān)心如何派發(fā)給store,而不用去關(guān)心一系列其它的組件是否需要狀態(tài)的改變。這就是Redux如何使數(shù)據(jù)流變得更簡(jiǎn)單的原因。
使用store來(lái)協(xié)調(diào)應(yīng)用之間狀態(tài)改變的概念就是Flux模式。它是一種傾向單向數(shù)據(jù)流(比如 React)的設(shè)計(jì)模式。Redux像Flux,但是他們又有多少關(guān)系呢?
Redux is "Flux-like"Flux 是一種模式,不像Redux那樣是可以下載的工具,Redux 是受Flux模式,此外,它比較像Elm。這里有許多有關(guān)于Redux和Flux之間比較的指南。它們中的大多數(shù)都會(huì)得出Redux就是Flux,或者Redux和Flux比較類似的結(jié)論,這取決于給Flux定義的規(guī)則到底有多嚴(yán)格。然而說(shuō)到底,這些都無(wú)關(guān)緊要。Facebook 非常喜歡并且支持Redux,這從它們雇傭了Redux的主要開(kāi)發(fā)者 Dan Abramov 就可以看出。
這篇文章假設(shè)你一點(diǎn)都不熟悉Flux的設(shè)計(jì)模式。不過(guò)如果你熟悉,你會(huì)注意到許多微小的不同,尤其考慮到Redux的三大指導(dǎo)原則:
1. 單一真實(shí)源Redux只使用一個(gè)store來(lái)處理應(yīng)用的狀態(tài)。因?yàn)樗械臓顟B(tài)都駐留在同一個(gè)地方,Redux稱這個(gè)為單一真實(shí)源。
store中數(shù)據(jù)的結(jié)構(gòu)完全取決于你,但通常都是針對(duì)應(yīng)用的一個(gè)深層嵌套的對(duì)象。
Redux的單一store方法是區(qū)分Flux多個(gè)store方法的最主要區(qū)別。
2. 狀態(tài)是只讀的Redux的文檔指出,唯一改變狀態(tài)的方法就是發(fā)出一個(gè)action,一個(gè)用來(lái)描述發(fā)生了什么的對(duì)象。
這意味著應(yīng)用不能直接改變狀態(tài),相反,“actions” 被派發(fā)給store,用來(lái)描述一個(gè)改變狀態(tài)的意圖。
store對(duì)象自己有幾個(gè)小型的API,對(duì)應(yīng)4個(gè)方法:
store.dispatch(action)
store.subscribe(listener)
store.getState()
replaceReducer(nextReducer)
所以你可以看到,這里沒(méi)有設(shè)置狀態(tài)的方法。因此,派發(fā)一個(gè)action是處理應(yīng)用狀態(tài)更改的唯一辦法。
var action = { type: "ADD_USER", user: {name: "Dan"} }; // Assuming a store object has been created already store.dispatch(action);
dispatch() 方法發(fā)送了一個(gè)對(duì)象給Redux,這個(gè)對(duì)象就被叫做action。這個(gè)action可以被描述成一個(gè)攜帶了一個(gè) type 屬性以及其它可以被用來(lái)更新?tīng)顟B(tài)的數(shù)據(jù)(在這個(gè)例子里就是user)的有效負(fù)載。記住,在 type 屬性之后,這個(gè)action對(duì)象的設(shè)計(jì)完全取決于你。
3. 所有的狀態(tài)改變使用的都是純函數(shù)就像剛才所描述的,Redux不允許應(yīng)用直接改變狀態(tài),而是用被分派的action來(lái)“描述”狀態(tài)改變或者改變狀態(tài)的意圖。而一個(gè)個(gè)Reducer就是你自己寫的函數(shù),用來(lái)處理分派的action,事實(shí)上是它真正改變了狀態(tài)。
一個(gè)reducer接受當(dāng)前的狀態(tài)(state)作為參數(shù),而且必須返回一個(gè)新的狀態(tài)才能改變之前的狀態(tài)。
// Reducer Function var someReducer = function(state, action) { ... return state; }
reducer 必須使用 “純”函數(shù) , 一個(gè)可以用以下這些特征來(lái)描述的術(shù)語(yǔ):
沒(méi)有任何的網(wǎng)絡(luò)或數(shù)據(jù)庫(kù)請(qǐng)求操作
返回的值僅依賴于參數(shù)
參數(shù)必須是“不可改變的”,以為著它們將不能被更改。
調(diào)用具有相同參數(shù)集的純函數(shù)將始終返回相同的值
它們被稱為“純”函數(shù)是因?yàn)樗鼈兪裁炊疾蛔鰞H僅返回一個(gè)基于參數(shù)的值。它們?cè)谙到y(tǒng)的任何其他部分都沒(méi)有副作用。
第一個(gè) Redux Store開(kāi)始之前,需要先用 Redux.createStore() 創(chuàng)建一個(gè)store,然后將所有的reducer作為參數(shù)傳遞進(jìn)去,讓我們看一下這個(gè)只傳遞了一個(gè)reducer的小例子:
// Note that using .push() in this way isn"t the // best approach. It"s just the easiest to show // for this example. We"ll explain why in the next section. // The Reducer Function var userReducer = function(state, action) { if (state === undefined) { state = []; } if (action.type === "ADD_USER") { state.push(action.user); } return state; } // Create a store by passing in the reducer var store = Redux.createStore(userReducer); // Dispatch our first action to express an intent to change the state store.dispatch({ type: "ADD_USER", user: {name: "Dan"} });
上面的程序干了些什么呢:
這個(gè)store只由一個(gè)reducer創(chuàng)建。
這個(gè)reducer 初始化狀態(tài)的時(shí)候使用了一個(gè)空數(shù)組 。*
在被分派的這個(gè)action里面使用了新的user對(duì)象。
這個(gè)reducer將這個(gè)新的user對(duì)象附加到state上,并將它返回,用來(lái)更新store。
*在這個(gè)例子里reducer實(shí)際上被調(diào)用了兩次 —— 一次是在創(chuàng)建store的時(shí)候,一次是在分派action之后。
當(dāng)store被創(chuàng)建之后,Redux立即調(diào)用了所有的reducer,并且將它們的返回值作為初始狀態(tài)。第一次調(diào)用reducer傳遞了一個(gè) undefined 給state。經(jīng)過(guò)reducer內(nèi)部的代碼處理之后返回了一個(gè)空數(shù)組給這個(gè)store的state作為開(kāi)始。
所有的reducer在每次action被分派之后都會(huì)被調(diào)用。因?yàn)閞educer返回的狀態(tài)將會(huì)成為新的狀態(tài)存儲(chǔ)在store中,所以 Redux總是希望所有的reducer都要返回一個(gè)狀態(tài)。
在這個(gè)例子中,reducer第二次的調(diào)用發(fā)生在分派之后。記住,一個(gè)被分派的action描述了一個(gè)改變狀態(tài)的意圖,而且通常攜帶有數(shù)據(jù)用來(lái)更新?tīng)顟B(tài)。這一次,Redux將當(dāng)前的狀態(tài)(仍舊是空數(shù)組)和action對(duì)象一起傳遞給了reducer。這個(gè)action對(duì)象,現(xiàn)在有了一個(gè)值為‘ADD_USER’的type屬性, 讓reducer知道怎樣改變狀態(tài)。
我們很容易就能將reducers和漏斗聯(lián)想起來(lái),允許狀態(tài)通過(guò)他們。這是因?yàn)閞educers總是接受和返回狀態(tài)用來(lái)更新store。
基于這個(gè)例子,我們的store將會(huì)變成一個(gè)只有一個(gè)user對(duì)象的數(shù)組:
store.getState(); // => [{name: "Dan"}]不要改變狀態(tài),復(fù)制它
在我們上面的例子中這個(gè)reducer從技術(shù)上來(lái)講是可行的,但是它改變了狀態(tài),這是一種不好的做法。盡管reducers 負(fù)責(zé)改變狀態(tài),但是不應(yīng)該直接改變“現(xiàn)有的狀態(tài)”。所以我們不應(yīng)該在reducer的state這個(gè)參數(shù)上使用.push()這個(gè)變異的方法。
傳遞給reducer的參數(shù)應(yīng)該被視為不可改變的。換句話說(shuō),他們不應(yīng)該被直接改變。我們可以使用不變異的方法比如.concat()來(lái)拷貝這個(gè)數(shù)組,然后我們將拷貝的數(shù)組返回。
var userReducer = function(state = [], action) { if (action.type === "ADD_USER") { var newState = state.concat([action.user]); return newState; } return state; }
在這個(gè)新的reducer中,我們添加了一個(gè)新的user對(duì)象作為state參數(shù)的副本被改變和返回。當(dāng)沒(méi)有添加新的用戶的時(shí)候,注意返回的是原始的state而不是它的拷貝。
有一大節(jié)關(guān)于不可變數(shù)據(jù)結(jié)構(gòu)的最佳嘗試,我們應(yīng)該更多的去了解
多個(gè)reducer你也許已經(jīng)注意到初始化參數(shù)使用了ES2015的默認(rèn)參數(shù)方法。到目前為止,在這一些列的文章中,我們一直避免使用ES2015來(lái)使你更專心于主題內(nèi)容。然而,Redux和ES2015結(jié)合使用會(huì)變得非常完美。因此,我們最終開(kāi)始在這篇文章中使用ES2015。然而不用擔(dān)心,每次采用新的ES2015的特性,我們都會(huì)指出來(lái)并且解釋
上一個(gè)例子是一個(gè)很好的入門,但是大多數(shù)的應(yīng)用都需要更復(fù)雜的state來(lái)滿足整個(gè)應(yīng)用。因?yàn)镽edux僅使用一個(gè)store,所以我們需要使用嵌套的對(duì)象來(lái)組織不同模塊的state。假設(shè)我們的想要我們的store類似于這種樣子:
{ userState: { ... }, widgetState: { ... } }
整個(gè)應(yīng)用對(duì)應(yīng)的還是 “一個(gè)store = 一個(gè)對(duì)象”,但是它嵌套了 userState 和 widgetState 對(duì)象,可以包含各種數(shù)據(jù)。這似乎過(guò)于簡(jiǎn)單了,但是實(shí)際上和一個(gè)真實(shí)的Redux store沒(méi)多少差別。
為了創(chuàng)建具有嵌套對(duì)象的store,我們需要定義每一塊的reducer:
import { createStore, combineReducers } from "redux"; // The User Reducer const userReducer = function(state = {}, action) { return state; } // The Widget Reducer const widgetReducer = function(state = {}, action) { return state; } // Combine Reducers const reducers = combineReducers({ userState: userReducer, widgetState: widgetReducer }); const store = createStore(reducers);
ES2015 提示! 在這個(gè)例子中四個(gè)主要的變量都不會(huì)被改變, 所以我們將它們定義成常量. 同時(shí)我們也使用了ES2015 modules and destructuring
combineReducers()允許我們用不同的邏輯塊來(lái)描述store,將reducer分配給每一個(gè)塊?,F(xiàn)在,每一個(gè)reducer返回的初始狀態(tài)會(huì)進(jìn)入到它們store中各自對(duì)應(yīng)的userState或者widgState塊。
有些非常重要的點(diǎn)需要注意,現(xiàn)在每一個(gè)reducer中所傳遞的只是全部狀態(tài)中各自的部分,不再像之前只有一個(gè)reducer時(shí)傳遞的是整個(gè)store的狀態(tài)。然后每個(gè)reducer返回的狀態(tài)應(yīng)用于它們各自的部分。
在分派之后調(diào)用的是哪一個(gè)Reducer?當(dāng)我們考慮每次action被分派的時(shí)候,把上面全部的reducer想想成一個(gè)個(gè)漏斗會(huì)變得更加明了,所有的reducer都會(huì)被調(diào)用,都將有機(jī)會(huì)來(lái)更新各自的狀態(tài):
我很小心地說(shuō)“它們的”狀態(tài)是因?yàn)閞educer的“當(dāng)前狀態(tài)”參數(shù)和它的返回“更新”狀態(tài)僅僅影響到store中reducer里面的部分。記住,像前面所說(shuō)的,每一個(gè)reducer只獲得它們各自的狀態(tài),而不是整個(gè)狀態(tài)。
Action 策略實(shí)際上有大量的關(guān)于創(chuàng)建和管理action及其類型的策略。雖然它們都很棒,但是它們不像本文中的其他一些信息那樣重要。為了減少文章的篇幅,我們整理了這些基本的action策略,你可以在 GitHub repo上獲得這一系列的策略。
不可變的數(shù)據(jù)結(jié)構(gòu)state的樣式由你自己決定: 它可以是原始值,數(shù)組,對(duì)象,或者一個(gè)Immutable.js的數(shù)據(jù)結(jié)構(gòu)。 唯一重要的部分就是你不能改變state對(duì)象,而且需要返回一個(gè)更改后的新對(duì)象 -- Redux 文檔
上面的陳訴說(shuō)了很多,我們已經(jīng)在本教程中提到了這一點(diǎn)。如果我們開(kāi)始討論什么是可變的什么是不可變的的來(lái)龍去脈和利弊,我們可以在 《blog article"s worth of information》找到更有價(jià)值的信息。所以事實(shí)上,我只是想突出一些要點(diǎn)。
開(kāi)始前:
JavaScript的原始數(shù)據(jù)類型(Number, String, Boolean, Undefined, and Null) 已經(jīng)是不可變得了。
對(duì)象、數(shù)組、函數(shù)是可變的。
有人說(shuō)數(shù)據(jù)結(jié)構(gòu)的可變性容易產(chǎn)生問(wèn)題。因?yàn)槲覀兊膕tore是有state對(duì)象和數(shù)組所組成,我們需要實(shí)施一種策略來(lái)保持狀態(tài)不可變。
讓我們假設(shè)需要改變一個(gè)state對(duì)象的屬性,這里有三種方式:
// Example One state.foo = "123"; // Example Two Object.assign(state, { foo: 123 }); // Example Three var newState = Object.assign({}, state, { foo: 123 });
第一個(gè)和第二個(gè)例子都改變了state對(duì)象。第二個(gè)例子是因?yàn)?b>Object.assign()把所有的參數(shù)都合并到了第一個(gè)參數(shù)里。但這也就是為什么第三個(gè)例子沒(méi)有改變state對(duì)象的原因。
第三個(gè)例子將state的內(nèi)容和{foo: 123}合并到了一個(gè)新的空對(duì)象中。這是一種常見(jiàn)的技巧,允許我們創(chuàng)建一個(gè)state對(duì)象的副本,在副本上進(jìn)行修改,本質(zhì)上不會(huì)影響原始的state。
對(duì)象的“擴(kuò)展運(yùn)算符”是保持state不可變的另一種方式:
const newState = { ...state, foo: 123 };
有關(guān)于上述代碼究竟發(fā)生了什么,為什么它對(duì)Redux是友好的詳細(xì)解釋,可以參考這個(gè)主題的文檔
Object.assign() 和擴(kuò)展運(yùn)算符都是ES2015的特性。
總結(jié)來(lái)說(shuō),有許多方法可以明確地保持對(duì)象和數(shù)組不可變。許多開(kāi)發(fā)者使用第三方庫(kù)比如 seamless-immutable、Mori 甚至Facebook自己的Immutable.js 來(lái)達(dá)到這個(gè)目的。
初始化狀態(tài) 和 時(shí)間旅行我非常小心的選擇了一些相關(guān)的博客和教程。如果你不是非常明白不變性,可以看一下上面給出的這些鏈接。這在Redux的學(xué)習(xí)中是一個(gè)非常重要的概念。
如果你讀過(guò)文檔,你也許會(huì)注意到createStore()這個(gè)方法里的第二個(gè)參數(shù)是用來(lái)“初始化狀態(tài)”的,這也許是對(duì)reducer創(chuàng)建初始化狀態(tài)方式的一種替代。然而,這個(gè)初始化的狀態(tài)只會(huì)被用來(lái)“state hydration”。
想象一下一個(gè)用戶刷新了你的單頁(yè)面應(yīng)用,store中的狀態(tài)被重置為reducer中的初始狀態(tài),這樣可能是不理想的。
相反,想象一個(gè)你可以使用一種策略來(lái)保持store,然后在刷新的時(shí)候重新將它化合到Redux中。這就是傳送一個(gè)初始化狀態(tài)到createStore()中的原因。
這帶來(lái)了一個(gè)有趣的概念,如果重新化合老的狀態(tài)變得這么容易,我們可以將app中的狀態(tài)想象成是時(shí)間旅行。這可以被用來(lái)進(jìn)行調(diào)試或者撤銷/重做某些特性。所以將所有的狀態(tài)存儲(chǔ)在一個(gè)store中變得很有意義。這就是為什么不可變的狀態(tài)能夠幫助我們的其中一個(gè)原因。
在一次面談中,Dan Abramov 被問(wèn)到“為什么你要開(kāi)發(fā)Redux?”
Redux with React我并不是有意要?jiǎng)?chuàng)建Flux框架。當(dāng)React第一次被宣布的時(shí)候,我提議來(lái)談一談‘熱加載和時(shí)間旅行’,但是老實(shí)說(shuō),我自己也不知道該怎么實(shí)施時(shí)間旅行
就像我們已經(jīng)討論過(guò)的,Redux與框架無(wú)關(guān)。在我們開(kāi)始考慮Redux跟React怎么結(jié)合之前,明白R(shí)edux的核心概念是非常重要的。但是現(xiàn)在我們已經(jīng)準(zhǔn)備好從上一篇文章中拿一個(gè)容器組件,然后將Redux應(yīng)用在它上面了。
首先,這是沒(méi)有使用Redux的原始組件代碼:
import React from "react"; import axios from "axios"; import UserList from "../views/list-user"; const UserListContainer = React.createClass({ getInitialState: function() { return { users: [] }; }, componentDidMount: function() { axios.get("/path/to/user-api").then(response => { this.setState({users: response.data}); }); }, render: function() { return; } }); export default UserListContainer;
ES2015 說(shuō)明!這個(gè)例子已經(jīng)在原始代碼的基礎(chǔ)上做了部分轉(zhuǎn)換,使用了ES2015的模塊功能和箭頭函數(shù)。
它所做的就是發(fā)送一個(gè)Ajax請(qǐng)求,然后更新它的本地狀態(tài)。但是,如果該應(yīng)用的其它區(qū)域也要根據(jù)這個(gè)新獲取到的用戶列表進(jìn)行改變呢,這個(gè)策略是不夠的。
有了Redux策略,我們可以在Ajax請(qǐng)求的時(shí)候分派一個(gè)action而不是進(jìn)行 this.setState(),然后這個(gè)組件和其它組件可以訂閱狀態(tài)的改變。但是事實(shí)上這帶給我們一個(gè)問(wèn)題,我們應(yīng)該怎么設(shè)置store.subscribe()來(lái)更新組件的狀態(tài)呢?
我想我可以提供幾個(gè)例子來(lái)手動(dòng)的連接一些組件到Redux store。你也可以想象一下用你的方法會(huì)怎么做。但是最終,在這些例子的最后我會(huì)解釋有一個(gè)更好的辦法,然后忘掉這些手動(dòng)的例子。然后我會(huì)介紹官方的連接React和Redux的模塊,叫做react-redux,所以還是直接跳到那一步吧。
使用 react-redux 進(jìn)行連接為了說(shuō)明白,react,redux和react-redux是npm上三個(gè)獨(dú)立的模塊。其中,react-redux模塊允許我們以更方便的方式“connect” React組件和Redux
下面給出例子:
import React from "react"; import { connect } from "react-redux"; import store from "../path/to/store"; import axios from "axios"; import UserList from "../views/list-user"; const UserListContainer = React.createClass({ componentDidMount: function() { axios.get("/path/to/user-api").then(response => { store.dispatch({ type: "USER_LIST_SUCCESS", users: response.data }); }); }, render: function() { return; } }); const mapStateToProps = function(store) { return { users: store.userState.users }; } export default connect(mapStateToProps)(UserListContainer);
這里面有許多的新東西:
1、我們從 react-redux 中引入了 connect 函數(shù)。
2、這段代碼可能從最底下的連接操作開(kāi)始往上看會(huì)更容易理解。connect()方法實(shí)際上接收兩個(gè)參數(shù),但是我們這里只顯示了一個(gè) mapStateToProps()
connect()() 多了一個(gè)括號(hào)看起來(lái)好像很奇怪,實(shí)際上這是兩個(gè)函數(shù)的調(diào)用。首先,connect()返回了另外一個(gè)函數(shù),我想我們可以把這個(gè)函數(shù)賦值給一個(gè)變量名,然后調(diào)用它,但是既然在后面多加一個(gè)括號(hào)就可以直接調(diào)用這個(gè)函數(shù),我們?yōu)槭裁催€要給它設(shè)置一個(gè)函數(shù)名呢?而且,在這個(gè)函數(shù)調(diào)用結(jié)束之后,我們根本不需要這個(gè)額外的函數(shù)名。這第二個(gè)函數(shù)需要你傳遞一個(gè)React組件。在這個(gè)例子中,傳遞的是我們的容器組件。我敢打賭你肯定正在思考“為什么要把它變得這么復(fù)雜?”,然而,這實(shí)際上是一種常見(jiàn)的“函數(shù)式編程”范式,所以,學(xué)習(xí)如何使用它是非常有好處的。
3、connect()第一個(gè)參數(shù)是需要返回一個(gè)對(duì)象的函數(shù)。這個(gè)對(duì)象的屬性會(huì)成為這個(gè)組件的“props”。你可以看到它們的狀態(tài)值?,F(xiàn)在,我希望“mapStateToProps”變得更有意義。同時(shí),我們也看到mapStateToProps()這個(gè)函數(shù)接收了一個(gè)參數(shù),這個(gè)參數(shù)就是整個(gè)Redux的store。mapStateToProps()函數(shù)的主體思想就是將這個(gè)組件需要用到的部分狀態(tài)從全部狀態(tài)中隔離出來(lái)作為它的props屬性。
4、根據(jù)第3點(diǎn)中所說(shuō)的,我們將不再需要getInitialState()的存在。同時(shí),我們也看到,自從users這個(gè)數(shù)組變成了props屬性而不是本地組件狀態(tài)之后,我們參考使用this.props.users而不是this.state.users。
5、Ajax的返回現(xiàn)在變成了一個(gè)action的分派,而不是本地狀態(tài)的更新。為了更簡(jiǎn)單明了的展示,我們沒(méi)有使用action構(gòu)造器和action type常量
下面的代碼提供了一種在用戶自定義的reducer沒(méi)有出現(xiàn)的時(shí)候也可以工作的假設(shè)。注意store的userState屬性,但是這個(gè)名字是哪里來(lái)的呢?
const mapStateToProps = function(store) { return { users: store.userState.users }; }
這個(gè)名字來(lái)自我們合并所有的reducer的時(shí)候:
const reducers = combineReducers({ userState: userReducer, widgetState: widgetReducer });
userState的.users屬性又是什么?它又來(lái)自哪里?
在這個(gè)例子中,我們并沒(méi)有展示一個(gè)實(shí)際的reducer(因?yàn)樗鼤?huì)出現(xiàn)在另一個(gè)文件中),reducer決定了它所負(fù)責(zé)狀態(tài)的子屬性。為了確保.users是userState的一個(gè)屬性,上述例子對(duì)應(yīng)的reducer可能看起來(lái)是這樣的:
const initialUserState = { users: [] } const userReducer = function(state = initialUserState, action) { switch(action.type) { case "USER_LIST_SUCCESS": return Object.assign({}, state, { users: action.users }); } return state; }在 Ajax 不同生命周期進(jìn)行分派
在我們Ajax的例子中,我們僅僅分派了一個(gè)action。它被特意叫做“USER_LIST_SUCCESS”,因?yàn)槲覀兺瑫r(shí)也希望在Ajax調(diào)用開(kāi)始的時(shí)候分派一個(gè)“USER_LIST_REQUEST”的action,在Ajax調(diào)用失敗的時(shí)候分派一個(gè)“USER_LIST_FAILED”的action。請(qǐng)確保讀取異步操作的文檔
分派事件在之前的文章中,我們看到事件應(yīng)該通過(guò)容器組件傳遞到表現(xiàn)組件。原來(lái) react-redux同時(shí)也可以處理這個(gè),一個(gè)事件只需要分派一個(gè)action:
... const mapDispatchToProps = function(dispatch, ownProps) { return { toggleActive: function() { dispatch({ ... }); } } } export default connect( mapStateToProps, mapDispatchToProps )(UserListContainer);
在表現(xiàn)組件中,就像我們之前做過(guò)的,可以通過(guò)onClick={this.props.toggleActive}來(lái)調(diào)用事件,不需要再編寫事件本身。
容器組件省略有時(shí),一個(gè)容器組件只需要訂閱store,不需要任何像componentDidMount()這樣的方法來(lái)開(kāi)始Ajax 請(qǐng)求。它只需要一個(gè)render()方法傳遞給表現(xiàn)組件。在這個(gè)例子中,我們可以像這樣構(gòu)造容器組件:
import React from "react"; import { connect } from "react-redux"; import UserList from "../views/list-user"; const mapStateToProps = function(store) { return { users: store.userState.users }; } export default connect(mapStateToProps)(UserList);
是的,父老鄉(xiāng)親們,這就是新的容器組件的整個(gè)文件。但是等一下,容器組件在哪里?為什么我們?cè)谶@里沒(méi)有用到任何的React.createClass()?
事實(shí)證明,connect()方法為我們構(gòu)造了一個(gè)容器組件。注意到這一次我們直接傳遞的是一個(gè)表現(xiàn)組件,而不是我們自己創(chuàng)建的容器組件。如果你真的在想容器組件干了什么,記住,它們的存在是為了表現(xiàn)組件專心于視圖,而不是狀態(tài)。它們也傳遞狀態(tài)給子視圖作為props。而這就是connect()實(shí)際所做的,它傳遞了狀態(tài)(作為props)給我們的表現(xiàn)組件,然后返回一個(gè)React組件來(lái)包裹這個(gè)表現(xiàn)組件。從本質(zhì)上來(lái)說(shuō),這個(gè)包裹,就是容器組件。
所以是不是意味著上面的例子中其實(shí)有兩個(gè)容器組件包裹著一個(gè)表現(xiàn)組件?當(dāng)然,你可以這樣子認(rèn)為。但這并沒(méi)有什么問(wèn)題,只有當(dāng)我們的容器組件需要除了render()方法之外的其它方法的時(shí)候它才是必須的。
想象這兩個(gè)容器組件是具有不同但是相關(guān)服務(wù)的角色:
嗯,也許這就是為什么React的logo看起來(lái)這么像原子的原因吧
Provider為了保證任何react-redux的代碼能正常工作,你需要使用一個(gè)
import React from "react"; import ReactDOM from "react-dom"; import { Provider } from "react-redux"; import store from "./store"; import router from "./router"; ReactDOM.render({router} , document.getElementById("root") );
通過(guò)react-redux真正“連接”React和Redux的東西是附加給Provider的store,這里有個(gè)例子,關(guān)于主要入口點(diǎn)大概是怎么樣
Redux with React Router這個(gè)不做要求,但是有另一個(gè)npm項(xiàng)目叫做 react-router-redux ,因?yàn)閺募夹g(shù)上來(lái)說(shuō),路由是UI-state的一部分,而且React Router不認(rèn)識(shí)Redux,所以這個(gè)項(xiàng)目幫助我們連接這兩個(gè)東西。
你看到我做了什么嗎?我們走了一圈,又回到了第一篇文章!
項(xiàng)目最后遵照這一系列教程,最終你可以實(shí)現(xiàn)一個(gè)叫做“用戶控件”的單頁(yè)面應(yīng)用。
與本系列其他文章一樣,每個(gè)都有相關(guān)指導(dǎo)文檔,在Github上也都有相關(guān)代碼指導(dǎo)你怎么做。
總結(jié)我真的希望你能喜歡我寫的這一系列文章,我意識(shí)到有許多關(guān)于React的主題我們都沒(méi)有覆蓋到,但我試圖在保持真實(shí)的前提下,給新用戶一種跨越React基礎(chǔ)知識(shí)的認(rèn)知,以及制作一個(gè)單頁(yè)面應(yīng)用所帶來(lái)的感受。
系列文章第一部分:React Router
第二部分:Container Components
第三部分:Redux(你在這里)
翻譯文獻(xiàn):Leveling Up with React: Redux By Brad Westfall On March 28, 2016
翻譯作者:coocier
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/82457.html
摘要:前言最近將公司項(xiàng)目的從版本升到了版本,跟完全不兼容,是一次徹底的重寫。升級(jí)過(guò)程中踩了不少的坑,也有一些值得分享的點(diǎn)。沒(méi)有就會(huì)匹配所有路由最后不得不說(shuō)升級(jí)很困難,坑也很多。 前言 最近將公司項(xiàng)目的 react-router 從 v3 版本升到了 v4 版本,react-router v4 跟 v3 完全不兼容,是一次徹底的重寫。這也給升級(jí)造成了極大的困難,與其說(shuō)升級(jí)不如說(shuō)是對(duì) route...
摘要:在幾天前發(fā)布了新版本,被合入。但是在版本迭代的背后很多有趣的設(shè)計(jì)值得了解。參數(shù)處理這項(xiàng)改動(dòng)由提出。對(duì)透明化處理中的,達(dá)到將包裹起來(lái)的目的。對(duì)的凍結(jié)認(rèn)為,在中使用和方法是一種反模式。尤其是這樣的新,某些開(kāi)發(fā)者認(rèn)為將逐漸取代。 showImg(https://segmentfault.com/img/remote/1460000014571148); Redux 在幾天前(2018.04....
摘要:在幾天前發(fā)布了新版本,被合入。但是在版本迭代的背后很多有趣的設(shè)計(jì)值得了解。參數(shù)處理這項(xiàng)改動(dòng)由提出。對(duì)透明化處理中的,達(dá)到將包裹起來(lái)的目的。對(duì)的凍結(jié)認(rèn)為,在中使用和方法是一種反模式。尤其是這樣的新,某些開(kāi)發(fā)者認(rèn)為將逐漸取代。 showImg(https://segmentfault.com/img/remote/1460000014571148); Redux 在幾天前(2018.04....
摘要:第一階段升級(jí)到升級(jí)到遇到的坑在中去除,導(dǎo)致需要替換成。中把內(nèi)置的去掉,添加了和平級(jí)的屬性,來(lái)區(qū)分環(huán)境。的有這中屬性,若要在系統(tǒng)中使用,則用變量來(lái)獲取,比較奇葩。的路徑原來(lái)的,應(yīng)替換為相對(duì)路徑的。 第一階段: react15+react-router2+redux3+webpack1 升級(jí)到 react16+react-router3+redux4+webpack4 1.reac...
閱讀 2785·2021-10-14 09:42
閱讀 3608·2021-10-11 10:59
閱讀 2941·2019-08-30 11:25
閱讀 3073·2019-08-29 16:25
閱讀 3223·2019-08-26 17:40
閱讀 1224·2019-08-26 13:30
閱讀 1143·2019-08-26 11:46
閱讀 1329·2019-08-23 15:22