最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

【D1N910】一線大廠React實踐寶典 使用Redux (進度 6/9)

2020-05-17 06:46 作者:愛交作業(yè)的D1N910  | 我要投稿

正常操作,正常分析,大家好,我是 D1n910。

在大約兩年前,我購買了?騰訊課堂【NEXT】一線大廠 React 實踐寶典 這門課。?

因為我一直基本上是使用Vue來進行前端頁面的開發(fā),但是一直沒有時間去實踐看完。 兩年沒去看了,真的很慚愧,時間,唉,過得真快啊。

為了在這門課過期之前看完,所以我要抓緊時間學(xué)完了。

本系列的專欄記錄了我學(xué)習(xí)?一線大廠React實踐寶典? 的筆記。?

下面的內(nèi)容版權(quán)歸屬是??騰訊課堂【NEXT】一線大廠 React 實踐寶典 這門課。

當(dāng)然,如果我微不足道的筆記也算是我自己的版權(quán),那么也是有我的小小一份。

你可以用來學(xué)習(xí) React。

但是如果你用來商業(yè)化盈利,請先獲得??騰訊課堂【NEXT】一線大廠 React 實踐寶典 制作方的許可。

BTW,作為挖坑狂魔,如果這篇專欄沒有更新,那么當(dāng)我沒說過。

這是一個系列的文章,如果你沒有看過前面的內(nèi)容,那么建議你先看一看


加油加油


Redux 基本概念

數(shù)據(jù)太復(fù)雜眼看就要失去控制(小劇場)

A:誒,你這個頁面怎么這么多狀態(tài)呀?

B:是的,頁面不斷更新,所以狀態(tài)越來越多了。

A:那你是怎么管理這些狀態(tài)的呢?

B:這還真的是個問題,狀態(tài)引用復(fù)雜,想要知道怎么變換的,只能靠推斷了。

A:你為什么不用 Redux 進行管理呢? 這樣你就可以知道是什么時候因為什么原因進行的改變了。


Redux 簡介

一般來說,學(xué) React 都會有學(xué) Redux。

首先回顧:Flux的特點

  • 一種架構(gòu)模式,而不是一種框架。

  • 數(shù)據(jù)單向流動

那么 Redux 是什么呢?

  • Redux 是 Flux 架構(gòu)的一個實現(xiàn)。

  • 一個可預(yù)測的狀態(tài)容器。

  • Flux 架構(gòu)與函數(shù)式編程思想的結(jié)合。

那么 Redux 是如何實現(xiàn) Flux 的架構(gòu)呢?

先來看看 Redux 的數(shù)據(jù)流

1、只有唯一一個 Store(Flux 的 Dispatcher 唯一,但是 store 可以有多個);

2、因為 Dispathcer 和 Store 在 Redux 是唯一的,所以它們綁定在一起了。平時想用到?Dispathcer ,要使用?Store.dispatch;

3、Store 里的 reduce 被提出為 Reducers;

整個數(shù)據(jù)處理流程是這樣的:

展示層 -> 進行操作 ->?ActionCoerator > Dispathcer -> Reducers -> State ->?Reducers -> State? -> View


Redux? 的基本原則

1、唯一數(shù)據(jù)源:比如,只有唯一的 Store;

2、保持狀態(tài)只讀:Store 中的 state 只能讀取,不能改變;我們是在 actions 中,根據(jù)數(shù)據(jù)的不同,而返回新的不同的 state,不會改變原來的 state;

3、數(shù)據(jù)改變只能通過純函數(shù)完成。


什么時候使用 Redux

我們使用 Redux 是真正需要 Redux 的時候才用。功能:

(1)想要獲得存在本地存儲或從服務(wù)器帶來的初始數(shù)據(jù)。

(2)需要記錄用戶操作、實現(xiàn)撤銷功能或不同狀態(tài)跳轉(zhuǎn)。比如想知道我們的數(shù)據(jù)是做了什么才變成這樣的,什么時候 + 1,什么時候 -1。

(3)組件之間只狀態(tài)相互依賴嚴重,組件數(shù)據(jù)源復(fù)雜。用 Redux?可以抽象得比較好。

本小節(jié)完畢


純函數(shù)的定義

純函數(shù)是函數(shù)式編程中的非常重要的概念。

Redux 是 Flux 和純函數(shù)的結(jié)合。在我們的 Redux 中要使用純函數(shù)的形式。

1、給定相同的輸入,總能獲得相同的輸出。

- 輸出的結(jié)果,只能和入?yún)⑾嚓P(guān)聯(lián)。

2、函數(shù)過程中不會帶有副作用

- 不會改變外部的東西

3、不依賴外部狀態(tài)

- 不依賴外部狀態(tài)(全局變量等)


類似數(shù)學(xué)上的提供一個固定的 x,給出一個固定的 y。


下面來看一些非純函數(shù)的例子。

給定相同的輸入,不能獲得相同的輸出,違反第一條。


給定相同的輸入,不能獲得相同的輸出;有副作用(pop 修改了原數(shù)組)。違反第一條,第二條。

給定相同的輸入,不能獲得相同的輸出;依賴外部狀態(tài)。違反第一條,第三條。


Redux 與函數(shù)式編程

什么是函數(shù)式編程?

函數(shù)式編程(Functional programming)是指一種編程典范,它避免使用程序狀態(tài)和易變對象,倡導(dǎo)利用若干簡單的執(zhí)行單元讓計算結(jié)果不斷漸進,逐層推到復(fù)雜的運算,而不是設(shè)計一個復(fù)雜的執(zhí)行過程。

函數(shù)式編程的特點

1、函數(shù)是第一等公民

在 Javascript 中,函數(shù)本就是第一等公民。JavaScript 中的函數(shù)可以作為參數(shù)傳遞,可以賦值給變量,可以作為函數(shù)返回值,還可以把它們存儲到數(shù)組中,它與其他數(shù)據(jù)類型一樣,處于平等地位。

2、只使用表達式,不使用語句

函數(shù)式編程的每一步都是一個有返回值的表達式(expression),而不是多個語句(statemanet)。它強調(diào)將復(fù)雜的計算過程拆分多個可復(fù)用的函數(shù)。

3、沒有純作用的純函數(shù)

函數(shù)式編程要求我們編寫的每個函數(shù)都是獨立而無副作用的,不會修改系統(tǒng)中的狀態(tài)值,且只會返回一個新的值。同時,函數(shù)在相同輸入值時,需產(chǎn)生相同的輸出。

函數(shù)式編程的基本運算

1、柯里化(curry)

函數(shù)的柯里化,即對于一個多參數(shù)的函數(shù)進行一次轉(zhuǎn)化,轉(zhuǎn)化后的函數(shù)只傳遞給函數(shù)一部門參數(shù)來調(diào)用它,讓它返回一個函數(shù)去處理剩下的參數(shù)。

這里的 curry 我舉兩個很常見的例子。

例子一、


例子二、


2、數(shù)據(jù)的合成(compose)

數(shù)據(jù)需要經(jīng)過多個函數(shù)才可以變成另一個值,把這多個函數(shù)像一系列管道拼接起來,這個過程就叫函數(shù)合成(compose)。

這就是函數(shù)合成:

var compose = function(f,g) {

? ? return function(x) {

????????return f(g(x))

????}

}

例如,我們要是實現(xiàn)一個函數(shù),它接收一個字符串,然后需要將該字符串轉(zhuǎn)換成大寫,并在尾部添加一個感嘆號。我們可以把這兩個職責(zé)分派給 toUpperCase 和 exclaim 函數(shù):


函數(shù)嵌套的寫法


相比在函數(shù)中嵌套一大堆函數(shù)的調(diào)用,借用 compose 方法,我們可以寫出更為靈活易讀的函數(shù)組合。


Redux 中函數(shù)式編程的應(yīng)用:中間件

中間件的組合: applyMiddleware 函數(shù)解析

我們先來看看?applyMiddleware 函數(shù)的用法


如上,applyMiddleware 函數(shù)接收多個中間件函數(shù),并將其串聯(lián)起來。這種引入中間件的方式就很適合使用函數(shù)的組合(compose),事實上該函數(shù)也是這么實現(xiàn)的。

applyMiddleware 源碼:

如上,applyMiddleware 函數(shù)通過組合函數(shù) compose,對新生成的中間函數(shù)實現(xiàn)鏈式調(diào)用。相比嵌套函數(shù)調(diào)用的寫法,這種寫法十分直觀,可維護性也極佳。
除了compose 的使用,還可以看到,我們傳入的中間件函數(shù)并不會只執(zhí)行一次: middlewares 作為原始中間件的存儲數(shù)組,其包含的中間件函數(shù)會被遍歷執(zhí)行,接收一個中間件 Api middlewareAPI, 并返回一個新的中間件函數(shù)。事實上中間件函數(shù)在此處執(zhí)行了部分運算,得到一個更為強大的新函數(shù),讓新函數(shù)去執(zhí)行后面的操作。這正是一種柯里化的寫法,不需要一次性傳遞所有的參數(shù),而是將多個參數(shù)分多次傳入。事實上中間件函數(shù)總共會被執(zhí)行三次之多,下文我們會對其每一次的執(zhí)行展開詳解。


Redux 中的 compose

compose 源碼的實現(xiàn)也十分簡潔:

compose 函數(shù)巧妙的利用 reduce 函數(shù)對中間件函數(shù)進行累加,這種方式一開始看起來可能會比較繞,我們可以看下 compose 所生成的函數(shù)是什么樣的:


測試一波

中間件函數(shù)的組合也正是如此,例如當(dāng)我們引入 thunk 和 logger 這兩個中間件時,compose 方式組合的函數(shù)如下:

_dispath = compose.apply(undefined, [newTunk, newLogger])(store.dispatch)

非 compose 組合的函數(shù)

_dispath = (function(args) {

????newTunk(newLogger(args))

})(store.dispatch)

當(dāng)中間件的數(shù)量不斷增多的情況下,中間件嵌套調(diào)用的方式可讀性十分糟糕。compose 組合的方式賦予了中間件更大的靈活性:可以像管道一樣隨意且無限制的組合,而且不用擔(dān)心代碼的可讀性。


中間件實現(xiàn)

我們先來實現(xiàn)一個簡單的日志中間件 actionLogger,用于記錄我們分發(fā)的 action 類型:

它的整個調(diào)用過程可以寫成

actionLogger(store)(next)(action)

可以看到,中間件函數(shù)把參數(shù)分為三次傳入調(diào)用,很典型的柯里化寫法。我們來分析一下這個中間件函數(shù) actionLogger 每一次的部分運算是如何執(zhí)行的。

第一次調(diào)用:actionLogger(store)


actionLogger 第一次執(zhí)行所接收的 store 參數(shù)其實就是 applyMiddleware 函數(shù)為中間件函數(shù)封裝的一個簡潔版的 Store,包含了 Store 中的 getState 方法和 一個被中間件加強的 dispatch 方法,我們在上面介紹 applyMiddleware 函數(shù)的時候也有提到,代碼如下:

chain = middlewares.map(function (middleware) {

????// 為中間件函數(shù)傳遞 Store 的 Api, 返回一個新的中間件函數(shù)

????return middleware(middlewareAPI);

});

第一次執(zhí)行后返回的新函數(shù)形態(tài)如下:

通過第一次的部分運算,我們可以得到一個能夠訪問中間件 Api 的新函數(shù),而這可以幫助我們實現(xiàn)更強大的中間件。

第二次調(diào)用:actionLogger(store)(next)

我們上面有提到,_dispatch 在不使用 compose 組合的時候如下:

可以看到,最末端的中間件 actionLogger 接收的正是 Store 原始的 dispatch 方法。

我們再來看中間件第二次執(zhí)行后返回的函數(shù)形態(tài):

很顯然,中間件此時返回的正是一個類 dispatch 函數(shù),而它也將作為下一級的中間件函數(shù)的 next 參數(shù)。簡單來說,只有最末端的中間件函數(shù)的 next 參數(shù)是不經(jīng)過加工的 store.dispatch,其余中間件的 next 參數(shù)都是其后一個中間件函數(shù)加強過的 dispatch 方法。這是一種鏈式的關(guān)系,dispatch 的控制權(quán)在中間件中由內(nèi)而外進行傳遞,對于 compose 組合的順序來說則是從右至左。


第三次調(diào)用:actionLogger(store)(next)(action)

最后一次的調(diào)用正是用于 action 的分發(fā),和第二次從右至左傳遞 next 參數(shù)順序相反,此時的調(diào)用順序是從左往右。此時會先執(zhí)行排最前面的中間件函數(shù),通過對 next 參數(shù)的調(diào)用才一步步執(zhí)行到最末端的中間件函數(shù)的 next 方法,也就是 store.dispatch。

Redux 中更多的函數(shù)式編程

Redux 中間件系統(tǒng)十分靈活強大,得益于代碼中函數(shù)式編程的思想。不僅僅是中間件,Redux 中的Reducer 也是一個很典型的函數(shù)式編程,它要求我們保證函數(shù)的‘純’,不允許 Reducer 函數(shù)帶有任何的副作用,而這種函數(shù)式的編程模式也可以幫助我們杜絕多數(shù)意想不到的問題。

關(guān)于 Redux 中的函數(shù)式編程,建議大家通過閱讀它的源碼來細細體會。

( 這些內(nèi)容,我覺得在看完了 Redux 中間件以后再回顧會好一些?)

本小節(jié)完畢,部分代碼如下:

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-1-pure-function

Redux 基本元素

怎么在React中使用Redux(小劇場)

A:Redux 的確是一個不錯的狀態(tài)管理框架,但是發(fā)現(xiàn)直接在 React 中使用不是很方便。子組件使用 Redux 的屬性和方法,往往需要層層傳遞,或者組件把一整個 state 引入進來。

B:React-Redux 這個包可以幫我們解決這個問題。使用了它之后,我們只需要在最頂層的父組件傳入 store 就可以了。在子組件中,如果我們想要使用 Redux 的一些功能,只需要使用 context,把子組件做一個包裝就行。

Redux 是 Flux 的實現(xiàn),我們可以用 Redux 替換 Flux。

上一節(jié)我們用 Flux 實現(xiàn)了 todoList,我們可以直接用 Redux 來替換。


Redux元素詳解 - Action

  • store的唯一信息源,我們要想修改 stroe,只能通過發(fā)起 action 的方式

  • store.dispatch(action): 通過傳入 aciton,就可以發(fā)起調(diào)用。

在 Redux中,Dispatcher 已經(jīng)被繼承在 Store 當(dāng)中了,所以不用引入 Dispatcher,直接返回 action 對象即可。


使用的時候,通過 Store 對象的 dispatch 方法進行派發(fā)(感覺也和原來的差不多,只不過 dispatch 集中在了 store 上面)

Redux元素詳解?-?Reducer

  • 作用處理 state 變化的純函數(shù)

  • 一般結(jié)構(gòu) (state, action) => new State。是修改 state,但是不是直接改 state,而是返回一個新的 state。

  • immutable。不可變。


首先導(dǎo)出 reducer 的純函數(shù),根據(jù) action 分發(fā),通過?switch 實現(xiàn)。(你也可以用對象屬性來實現(xiàn)這種 switch 的效果。)

能觀察到,我們是不會改變 state 的。

使用 concat 、 assign 的方法,可以讓我們構(gòu)建新的對象、新的數(shù)組。

當(dāng)然,要注意到使用類似 default 的方法。

和 Flux 的差不多,這里我們專門提取為 reducer.js 文件來使用。

Redux元素詳解?- Store

保存現(xiàn)在狀態(tài)的數(shù)據(jù)。

和 flux 不一樣的是,引入了 redux 的 createStore 來創(chuàng)建了一個 store。

createStore 傳遞三個參數(shù)。

第一個參數(shù) reducer,相應(yīng)事件處理 state 方法;

第二個參數(shù) initialState,初始化 state 參數(shù);

第三個參數(shù)這里沒寫,是 store?的增強器。


事件的派發(fā):通過 store 的 dispatch 方法進行

e.g. TodoStore.dispatch(Actions.addTodo(text));


獲取當(dāng)前狀態(tài):

e.g. TodoStore.getSate().list


訂閱變化:

e.g. TodoStore.subscribe(this.onChange)


View 層的使用不同

(1)綁定 action?方法

e.g. TodoStore.dispatch(Action.addTodo(value))

(2)監(jiān)聽 store 變化辦法

e.g. TodoStore.subscribe(this.onStoreChange)

(3)取消監(jiān)聽?store?變化辦法

e.g.?TodoStore.unsubscribe(this.onStoreChange)

(4)綁定初始值

this.state = {

? ? ?// 獲取初始值

? ? ?todoList: TodoStore.getState().list

? ?}

Redux 的運作方式【資料】

核心模塊

Redux 作為一個狀態(tài)管理框架,包含三大核心模塊:

  • Action:視圖層發(fā)出的通知,通知 Store 執(zhí)行數(shù)據(jù)數(shù)據(jù)更新,同時它作為 Store 唯一的數(shù)據(jù)來源。

  • Store:Redux 中的數(shù)據(jù)容器,且 Store 在 Redux 中是唯一的。

  • Reducer: Reducer 指定了 Sotre 應(yīng)當(dāng)如何響應(yīng) Action 的更新。

除了以上三個模塊,還有一個 View 層,在 Redux 中一般指 React 視圖層。Redux 的數(shù)據(jù)在這四個模塊之間的流轉(zhuǎn)嚴格遵循單向數(shù)據(jù)流的原則。


Action

在 Redux 中,state 是不可變的,數(shù)據(jù)的更新需要通過分發(fā) Action 來實現(xiàn)。Action 本質(zhì)是一個 JavaScript 對象,type 是其必要的一個屬性,代表 Action 的名稱,該屬性的缺失將導(dǎo)致 Action 不能被識別而無法更新數(shù)據(jù)。

我們一般通過 action creator 函數(shù)來生成 Action:

const editTitle = function(title) {

????????return {

????????????????type: 'EDIT_TITLE',

????????????????title

????????}

}

// 使用一個 editTitle 方法生成一個 action

const action = editTitle('hello Redux')


Reducer

Reducer 是 Redux 中唯一能識別 Action 的模塊。它接收 Store 當(dāng)前的狀態(tài)和分發(fā)的 Action,并返回一個新的 State 給到 Store。需要注意,Reducer 是一個純函數(shù),函數(shù)中不能修改 State,而是創(chuàng)建一個新的 State 對象,而修改的狀態(tài)也將被合并到該對象。

Reducer.js

可以看到,這里可以根據(jù) Action 的 type 做不同的數(shù)據(jù)更新。如果無法識別,會返回原來的 state。

這里還多設(shè)置了一個 initialState,作為 state 傳入 undefined 的時候的默認值。每一個 Reducer都需要設(shè)置一個初始狀態(tài)。


Store

Store 存儲了 Redux 應(yīng)用中的所有狀態(tài),并提供了相關(guān)訪問 Store 當(dāng)前狀態(tài)的方法,同時 Store 也作為 Action 和 Reducer 之間的紐帶,將兩者聯(lián)結(jié)在一起。

1、創(chuàng)建 Store:createStore 方法

Redux 提供了一個 createStore 方法,用于創(chuàng)建 Store。

該方法接收三個參數(shù):

  • 第一個參數(shù)接收一個 Reducer。

  • 第二個參數(shù)是一個對象,它為 Store 指定了應(yīng)用的初始狀態(tài),當(dāng)項目中涉及服務(wù)器端的 Redux 時,這個參數(shù)可以幫助我們同步服務(wù)器端和客戶端的數(shù)據(jù)結(jié)構(gòu),

  • 第三個參數(shù)是一個函數(shù),用于指定 Redux 中間件。

需要注意,當(dāng)需要指定中間件且不需要指定初始狀態(tài)的時候,第三個參數(shù)可提到第二個參數(shù)的位置,createStore 方法會根據(jù)第二個參數(shù)的類型將其識別為初始狀態(tài)或者中間件函數(shù)。

創(chuàng)建一個 Store 時,我們一般只需要傳遞第一個參數(shù)(如果 Reducer 指定了默認 state 的話):

數(shù)據(jù)的初始化

Store 狀態(tài)的初始化可分為兩個階段。第一階段正是 createStore 方法所接收的第二個參數(shù)實現(xiàn)的初始化。這個階段的初始化常見于服務(wù)端的 Redux 渲染時,服務(wù)端的渲染會把請求到的狀態(tài)注入到 window.__PRELOADED_STATE__,客戶端可以通過該全局變量得到初始的狀態(tài)對象,并將其作為 createStore 的第二個參數(shù)傳入,進而完成初次狀態(tài)的初始化。

這個場景下初始化的狀態(tài)一般都會包含首屏所需的數(shù)據(jù),這也是使用服務(wù)端渲染的原因:用于加快首屏的展示。但是在 Reducer 被切分成多個模塊的時候,參數(shù)實現(xiàn)的初始化難免有遺漏,這個時候就需要我們進行第二次的數(shù)據(jù)初始化。

我們來看一段 createStore 方法的實現(xiàn)源碼:

通過 createStore 的實現(xiàn)源碼中可以看到,它在最后一步調(diào)用內(nèi)置的 dispatch 方法,分發(fā)了一個 type 為 ActionTypes.INIT 的 action 對象。

我們知道,Reducer 根據(jù) action 的 type 做不同的數(shù)據(jù)處理。如果 action 中的 type 與其不相匹配,則需要執(zhí)行 ‘default’ 操作: 返回原先的 State。

createStore 方法正是利用了 Reducer 的這個特性,通過分發(fā)一個不會被 Reducer 所識別的 action,讓 Reducer 返回各自的初始化狀態(tài),進而完成了 Store 數(shù)據(jù)的第二次初始化。

我們總結(jié)一下 Store 的初始化流程:

開始創(chuàng)建 Store

??

執(zhí)行 createStore

??

preloadedState 是否是對象

(是 ???初次初始化完成)

??

(否)

??

執(zhí)行初始化的 dispatch

??

Reducer 返回配置的默認 State

??

第二次初始化完成

數(shù)據(jù)的更新:dispatch 方法

Store 內(nèi)置的 dispatch?方法,充當(dāng)了 Action 與 Reducer 之間的橋梁。通過 dispatch 方法,實現(xiàn)了 Action 從 Store 層到 Reducer 層的轉(zhuǎn)發(fā)。我們可以在 View 層通過該方法分發(fā) Action,進而實現(xiàn) Store 層數(shù)據(jù)的更新。

我們看?dispatch 的源碼是如何實現(xiàn) Action 的分發(fā):

? 源碼中的 currentReducer 正是我們調(diào)用 createStore 方法時傳入的 Reducer,currentState 對應(yīng) Store 當(dāng)前的狀態(tài)。也就是說,dispatch 方法直接調(diào)用了傳入的 Reducer 函數(shù),并向其轉(zhuǎn)發(fā)了 Store 當(dāng)前狀態(tài)和被分發(fā)的 action。同時的,Reducer 函數(shù)處理返回的結(jié)果也將直接被保存到 currentState 中。

需要注意,Redux 的數(shù)據(jù)更新只支持同步處理,如果需要執(zhí)行異步處理,則需要引入 react-thunk 中間件。

數(shù)據(jù)的監(jiān)聽:subscribe

數(shù)據(jù)從 Store 層到 View 層的流向,正是依賴于 Store 中的內(nèi)置 subscribe 方法。該方法接收一個回調(diào)函數(shù),并返回一個取消監(jiān)聽的函數(shù)。

添加 Store 的數(shù)據(jù)監(jiān)聽:

import store form './store';

const unSubscribe = store.subscribe(() => {

????????let state = store.getState()

})

每一次數(shù)據(jù)的更新,都會執(zhí)行 subscribe 中傳入的回調(diào)函數(shù)。我們可以在 View 層添加這個回調(diào)函數(shù),回調(diào)函數(shù)中則通過 store.getState() 獲取Store當(dāng)前的數(shù)據(jù),進而執(zhí)行視圖層的更新。

數(shù)據(jù)監(jiān)聽原理解析

Store 中執(zhí)行監(jiān)聽回調(diào)的方法在 dispatch 方法內(nèi)部。也就是說,在執(zhí)行 dispatch 方法的時候,我們添加的監(jiān)聽回調(diào)都會被執(zhí)行一遍。這一點在 dispath 方法的源碼中也有體現(xiàn):

可以看到,Store 內(nèi)部有一個存儲監(jiān)聽函數(shù)的數(shù)組 nextListener,每次執(zhí)行 dispatch 方法都會遍歷調(diào)用該數(shù)組中所有的監(jiān)聽函數(shù)。

我們新增的監(jiān)聽回調(diào)是作為 subscribe 函數(shù)的參數(shù)傳入的,所以我們可以推斷,subscribe函數(shù)的作用正是幫助我們把新增的監(jiān)聽回調(diào)添加到 nextListeners 數(shù)組總。

我們的推斷可以在 subscribe 源碼中得到驗證:

function sbscribe(listener) {
????if (typeif listener !=== 'function') {

????????????throw new Error('這個要被監(jiān)聽的玩意兒得是函數(shù)啊兄臺')

????}

????let isSubscribed = true

????ensureCanMutateNextListeners()

????nextListeners.push(listener)

????return function unsubscribe() {

????????if (!isSubscribed) {

????????????return

????????}

????????isSubscribed = false

????????ensuerCanMutateNextListeners()

????????const index = nextListeners.indexOf(listener)

????????nextListeners.splice(index, 1)

????}

}

nextListeners 是一個數(shù)組,所以我們可以在多個視圖層添加 Store 的監(jiān)聽回調(diào)。同時,subscribe 方法會返回一個取消監(jiān)聽的函數(shù)。有的時候我們希望不在接收第一次的數(shù)據(jù)更新,這個時候我們可以使用該方法移除監(jiān)聽。


本小節(jié)結(jié)束,相關(guān)代碼如下:

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-2-redux

更加 Redux 的方式

容器組件和傻瓜組件

上面主要是了解了 Redux 的基本內(nèi)容。

了解一下 React 組件的架構(gòu),容器組件和傻瓜組件。這將對我們的組件進行區(qū)分。

首先看看我們的組件做了什么

  • 和 Store 保持同步:監(jiān)聽 Store 的變化,從 Store 中獲取數(shù)據(jù)。

  • 渲染界面:根據(jù) props 和 state 來渲染界面

通過這兩個內(nèi)容,我們可以進行拆分。

容器組件 ContainerComponent:與 Store 保持同步,負責(zé)管理數(shù)據(jù),存放業(yè)務(wù)邏輯。

傻瓜組件 DumbComponent:與 UI 保持同步,只負責(zé)渲染 UI,存放所有 UI 渲染方法;

這兩個組件可以通過 props 的方式進行溝通。

ContainerComponent ??props ?? DumbComponent

通過這樣的方式,將功能和界面解綁。

處理更復(fù)雜的數(shù)據(jù)

了解了上面的內(nèi)容后,可以看看我們現(xiàn)在的 React?應(yīng)用(TodoList)的問題。

  • 容器組件和傻瓜組件強耦合,代碼冗余

  • Redux 應(yīng)用只有一個 Store,處處都要 import

通過 props 傳遞什么

根據(jù)功能來看

傻瓜組件:渲染界面;反饋用戶行為。

所以主要傳遞的是數(shù)據(jù)和方法

  • Store 中的 state,容器向核心組件傳遞數(shù)據(jù)

  • Store 中的 dispatch 方法,核心組件向容器發(fā)起行為

我們可以利用高階組件來重用上面抽象出來的功能。


首先我們需要提供兩個參數(shù)。

mapStateToProps:映射到props,傳入到內(nèi)部的傻瓜組件中的數(shù)據(jù)。

它可能是這樣的

mapDispatchToProps:映射到props,即導(dǎo)入到內(nèi)部的傻瓜組件中的綁定方法

它可能是這樣的:

最后我們得到的

<Component {...this.props} {...this.state} />

實際上就是一個容器組件。傳入了一個傻瓜組件,得到了一個容器組件。


我們可以像下面這樣進行使用

當(dāng)然別忘了,傻瓜組件中要對傳入的 props 進行驗證,這樣能夠規(guī)定我們 props 傳入的是我們需要的內(nèi)容

驗證無誤 ??


使用容器組件-傻瓜組件模式的代碼如下

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-3-more-redux-cf


設(shè)置全局可訪問的 Store

在我們上面說到的高階組件李,要使用 store,需要專門引入,感覺很麻煩,而且如果在不同的組件中使用,還需要再次引入。

雖然我本人不覺得麻煩,但是視頻的作者覺得是麻煩事。他想要設(shè)置全局組件都可以訪問 store。那我們之前學(xué)過 React 組件傳遞參數(shù)的辦法,其中 Context 非常適合全局傳遞 Store。

這相當(dāng)于是一個全局的屬性。只需要在父級聲明Context,就可以在子組件中使用了。

設(shè)置一個可全局訪問的 Context

首先注意:

1、React 在 16.3 版本之后提供了新的 Context API,我們這里使用的是舊的 API

2、Context 需要在最頂層的組件日共,這樣所有下層組件才能訪問

3、在最外層包裹一層組件,傳入 Store 作為 props,并將其賦值給 Context

4、需要使用 Store 的子組件通過 Context 訪問


首先提供一個組件,這個組件是所有我們需要使用 Redux 組件最頂層的應(yīng)用 —— Provider

通過這個 Provider 組件作為頂級組件,就可以讓子組件通過 context 獲得這里存儲的值。


想要引入 store,需要在 constructor 中聲明繼承 context,聲明 Connext.contextTypes類型,然后就可以用 this.context 使用

本小節(jié)代碼如下

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-3-more-redux-context


react-redux

到這里,我們說完了使用做 Redux 的時候,可以優(yōu)化的兩個方式

(1)使用高階組件抽離成容器組件、傻瓜組件

(2)使用 context 實現(xiàn)全局組件使用 store

這兩個其實就是 react-redux 這個庫的內(nèi)容。

react-redux 將 React 和 Redux 鏈接到了一起,結(jié)構(gòu)如下。


我們?nèi)绾问褂媚兀?/p>

首先安裝 react-redux。

在引入 connect 的高階函數(shù),使用? import { connect } from 'react-redux' 引入 connect,其他不變。


在頂級組件,引入 Provider 然后可以直接使用。

(搞了一堆心智負擔(dān)以后,現(xiàn)在發(fā)現(xiàn)用了 react-redux 就不用了……感覺自己變蠢了)

使用 react-redux 的代碼如下

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-3-more-redux-react_redux


使用 React-Redux

上面有實際使用 react-redux 來完成簡易的 todoList 的功能了,下面將使用 react-redux 完成具有更復(fù)雜功能的 todoList 來輔助理解使用場景。

新增 - 能夠點擊切換完成狀態(tài);

新增?-?能夠篩選展示列表(有全部、已完成、未完成 三種展示情況)。

效果如下

按照視頻,我把文件結(jié)構(gòu)修改成如下的樣子。

把組件拆分成容器組件(containers)、傻瓜組件(components)


使用 app 文件來包裹傻瓜組件。

這里說一下,store 的第二個參數(shù)傳入的是 store 的 __REDUX_DEVTOOLS_EXTENSION__。


通過這個可以在谷歌瀏覽器上調(diào)用 react-redux 的谷歌插件 Redux DevTools。這個工具可以監(jiān)控我們 store 的變化,主要是發(fā)生了什么行為。同時可以選擇任意一個行為,然后跳轉(zhuǎn)進入。

上面的相當(dāng)于小的實踐項目,這里附帶上工程代碼

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-3-more-redux-z-more_react_redux



Redux 的高階應(yīng)用

異步操作如何使用 Action

目前 redux 里的 action 都是同步執(zhí)行。異步操作的時候使用 Action,可以通過使用 Redux?中間件的方式增強 React 的能力。

Redux 中間件

通過查看 Redux 數(shù)據(jù)流來查看哪里最適合拓展。

View:不適合,因為這些都是在展示層實現(xiàn)的,和 React 有關(guān),和 Redux 關(guān)系不大。

在 State -> View 也不適合,因為這里主要是和 action 傳遞數(shù)據(jù)有關(guān),這時候數(shù)據(jù)已經(jīng)處理完畢了。

?Store? 內(nèi)部:不太適合,我們不能直接改變 state,我們只能返回一個新的 state,而不改變原有的 state

Reducer 也不適合,已經(jīng)是發(fā)出數(shù)據(jù)了。

最后發(fā)現(xiàn)最好在 ActionCreator 到 Dispatcher 中做處理。在發(fā)出和接收的這段時間里進行增強處理。

通過 applyMiddleware(中間件1, 中間件2, 中間件3)? 的方法來實現(xiàn)加入中間件。

action creatir ->? action ->?中間件1 -> 中間件2?-> 中間件3 -> dispatcher?-> reducers?


加入中間件以后,我們的流程可以發(fā)現(xiàn)在 dispatcher 上變成了套娃模式。

中間件的實現(xiàn)是高級中的高級,使用了柯里化的辦法完成。(上面的資料有描述過這個中間件的實現(xiàn))

({getState, dispatch}) => next => action => next(action)

中間件模版整理如下

function middleware({getState, dispatch}) {

???return function (next) {

????? ?return function (action) {

????????????// do something ...

? ? ? ? ? ???return next(action)

????????}

????}

}

一定要返回 next(action),才能夠把我們進行的操作進行返回。

查看 Redux 源碼中 applyMiddle ware 的實現(xiàn)

這里拿到了中間件之后,他會使用 middlewareAPI 進行賦于獲得 state 還有 action 的能力。

這里用 map?實現(xiàn)一個對中間件的調(diào)用。

使用 compose 方法可以把里面所有的參數(shù)按照從左到右的方式,將右邊的參數(shù)當(dāng)作左邊參數(shù)的傳入值。這里將中間件串聯(lián)起來了。也就是說,next 指向的是右邊最近的中間件方法。最后完成循環(huán)的調(diào)用。

而我們的 dispath 也就經(jīng)過了多層的調(diào)用。

這里列舉一個栗子。

下面兩個中間件,

authorMiddleware 實現(xiàn)了為每個 action 方法打上作者;

loggerMiddleware 實現(xiàn)打印舊 state、action 以及 處理后的數(shù)據(jù)。

中間件有執(zhí)行數(shù)據(jù),所以要注意?loggerMiddleware 要放到后面。

執(zhí)行下我們上一節(jié)的方法, 發(fā)現(xiàn)我們的中間件能夠正確在每個 action 觸發(fā)后被調(diào)用了。


分解 ajax 操作

了解上面的中間件知識以后,回顧我們剛剛的提問,如果在 Action 中使用 AJAX 這樣的異步操作,我們需要分解 AJAX。

要分解 AJAX原因如下:

  • Redux 中的 Action 是同步的,收到 Action 之后數(shù)據(jù)就會改變

  • AJAX 是異步的,等待服務(wù)器返回后數(shù)據(jù)才會改變

  • 所以,將異步的 AJAX 拆解為同步的狀態(tài)變化過程

分析一下。

init 初始化以后,發(fā)起了 REQUEST (請求),一段 loading (等待期)后。

會有兩種情況,分別是 REQUEST_SUCCESS (請求成功),那么就要去設(shè)置新的 data值。REQUEST_FAILURE (請求失?。?,拋出 error。

在 React 中我們應(yīng)該如何拆解呢?

常見的會拆解成下面的寫法。

這里有一個問題,就是從架構(gòu)上來說,我們的數(shù)據(jù)處理動作是放到 ActionCreator 中處理的,React 中主要處理數(shù)據(jù)渲染、視圖相關(guān)的內(nèi)容。

回憶下我們剛剛學(xué)習(xí)的中間件,我們可以進行改造。

改造方案:

  • 在 ActionCreator 中不再返回簡單的 Action 對象,而是返回一個函數(shù)或 Promise

  • 使用中間件來解析特殊類型的 Action 對象,自動拆解為同步的簡單 Action

解決方案有兩種:

可以使用 Redux-thunk 中間件,它允許我們的 ActionCreator 返回函數(shù)?;

可以使用 Redux-promise 中間件,它允許我們的?ActionCreator 返回 Promise 對象;

視頻課程主要講述的是使用 tunk 的方式。

使用例子:

這里我們創(chuàng)建了三個普通的 action。

但是我們實際返回的 action creator 不是前面的 action 一樣返回一個 對象,而是返回一個函數(shù)。

這個函數(shù)體的內(nèi)部,先 dispatch(requestStart()),然后發(fā)起 ajax 請求,在請求成功和請求失敗的時候分別做 dispatch 請求。


查看thunk 的源碼,我們可以看到它很簡單。

獲得了傳入的 dispath,如果我們傳入的是 function類型的話,我們可以傳入 dispatch、getState 等讓他得到對應(yīng)的能力。這樣我們可以在 actionCreator 中使用 dispath 的能力。

接下來可以在我們的源碼中看看如何使用。

這里我們的 store 改造如下。

需要注意的是,這里我們用上了 window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__,這樣能夠在使用到 redux 開發(fā)者工具的時候使用 compose。

reducer.js 針對開始請求、請求完畢做處理。

造了 app 的容器組件去 connect 了 api 加載


根據(jù)之前的課程,我們的 ajax 加載要放到 componentDidMount 里


因為是本地服務(wù)器,所以我們可以利用 webpack-dev-server 開啟一個簡單的服務(wù)器

最后效果正常,在控制器里調(diào)到 'GET_CONFIG/REQUEST_START',能夠展示“加載中”

本節(jié)代碼如下

https://github.com/D1N910/learn-react/tree/master/Chapter5/demo-4-redux-applyMiddleware



這兩周因為頭腦不清醒,所以沒有很好地進行學(xué)習(xí),目前剩余進度 3

有點暈乎乎得

我有點高估我自己了

這個 Redux 的心智負擔(dān)有點高的

繼續(xù)加油吧

from 蛋糕

【D1N910】一線大廠React實踐寶典 使用Redux (進度 6/9)的評論 (共 條)

分享到微博請遵守國家法律
阿瓦提县| 开平市| 永春县| 德安县| 明光市| 沙洋县| 新平| 军事| 错那县| 城口县| 洞口县| 城固县| 孝义市| 吐鲁番市| 济南市| 桦南县| 大洼县| 深泽县| 迭部县| 华容县| 沂南县| 黄山市| 治县。| 南靖县| 江西省| 买车| 嵩明县| 文成县| 策勒县| 巨野县| 保德县| 凯里市| 盖州市| 廉江市| 东兰县| 西乌珠穆沁旗| 台南市| 新蔡县| 达日县| 仪征市| 阳朔县|