先祭上本文的思惟導圖:html
在項目中用 Redux 的時候,有時候就以爲會用,可是不明白爲何這樣用。致使在 debug 的時候,沒法快速的 debug 出緣由。並且 Redux 的源碼也不復雜,暴露出來的只有 5 個 API,能夠做爲很好的閱讀源碼的開端,因此在這裏很開心能夠和你們一塊兒來探索 Redux。若是有些講的不許確的地方,歡迎你們提出來;也特別但願你們積極的討論,迸發出更多想法。react
要了解 Redux,就要從 Flux 提及。能夠認爲 Redux 是 Flux 思想的一種實現。那 Redux 是爲何被提出來呢?就要提一下 MVC 了。git
說到 Flux,咱們就不得不要提一下 MVC 框架。github
MVC 框架將應用分爲 3 個部分:redux
用戶請求先到達 Controller,而後 Controller 調用 Model 得到數據,再把數據交給 View。這個想法是很理想的想法。在實際的框架應用中,大部分都是容許 View 和 Model 直接通訊的。當項目變的愈來愈大的時候,這種不一樣模塊之間的依賴關係就變得「不可預測」了,因此就變成了下面這樣子。性能優化
雖然這張圖有誇大的嫌疑,可是也說明了 MVC 在大型項目下,容易形成數據混亂的問題。架構
因此,Flux 誕生了。在寫這篇文章以前,我查閱不少資料,有些說 Flux 思想替代了 MVC 框架,我則不這麼認爲。我的以爲,Flux 思想更嚴格的控制了 MVC 的數據流走向。下面我們來看看 Flux 是如何嚴格控制數據流的。框架
一個 Flux 應用包含四個部分:ide
經過上圖能夠看出來,Flux 的特色就是單向數據流:函數
因此在 Flux 體系下,若是想要驅動界面,只能派發一個 Store,別無他法。在這種規矩下,若是想要追溯一個應用的邏輯就變得很輕鬆了。並且這種思想解決了 MVC 中沒法杜絕 View 和 Model 之間的直接對話的問題。
這裏就不具體講關於 Flux 的例子了,若是想要更瞭解 Flux ,能夠看一下阮一峯老師的 Flux 架構入門教程。
Redux 是 Flux 的一種實現,意思就是除了「單向數據流」以外,Redux 還強調三個基本原則:
在 Flux 中,應用能夠擁有多個 Store,可是分紅多個 Store 容易形成數據冗餘,數據一致性不太好處理,並且 Store 之間可能還會有依賴,增長了應用的複雜度。因此 Redux 對這個問題的解決方法就是:整個應用只有一個 Store。
就是不能直接修改狀態。若是想要修改狀態,只能經過派發一個 Action 對象來完成。
這裏說的純函數就是 Reducer。按照 redux 做者 Dan 的說法:Redux = Reducer + Flux
。
下面我們根據例子來了解一下 Reudx 在 React 中的應用。
建立一個 Redux 應用須要下面幾部分:
他們分別是什麼意思呢?下面咱們來舉一個例子: 好比下面是商場某品牌鞋子的展現櫃:
店長來視察,發現鞋子2
放的過高了,並且這款鞋仍是店裏的主推款,放在這個位置不適合宣傳,就讓店員把鞋子 2 往下挪兩排
,放下去以後,店長看着舒服多了。
其實經過上面的例子,咱們如今就很好解釋 Redux 了:
因此整個過程能夠是下面這樣:
Store
決定了 View
,而後用戶的交互產生了 Action
,Reducer
根據接收到的 Action
執行任務,從而改變 Store
中的 state
,最後展現到 View
上。那麼,Reducer
如何接收到動做(Action
)信號的呢?伴隨着這個問題,我們來看一個例子。
瞭解了 Redux 中各個部分表明的意思,下面我們來經過一個計數器的例子進一步瞭解一下 Redux 的原理(具體代碼能夠看 GitHub)。咱們想要的最終效果以下:
根據上面的思路,能夠分別把 Action 和 Reducer 定義爲:
那麼咱們來建立 Action 和 Reducer 這兩個文件:
首先咱們建立一個 ActionTypes.js
和 Actions.js
這兩個文件。ActionType 表明的就是 Action 的類型,能夠看到它是一個常量。在 Actions.js
中,咱們定義了兩個 Action 構造函數,他們返回的都是一個簡單對象 (plain object
),並且每一個對象必須包含 type 屬性。
能夠看出來 Action 明確表達了咱們想要作的事情(加和減)。
可能有些同窗會問,在 Action 中,有時候也會 return 一個 function,不是簡單對象。其實這個時候,是中間件攔截了 Action,若是是 function,就執行中間件中的方法。可是我們此次不講中間件,因此就先忽略這種狀況。
能夠看到 Reducer 是一個純函數。它接收兩個參數 state 和 Action,根據接收到的 state 和 Action 來判斷本身須要對當前的 state 作哪些操做,而且返回新的 state。
在 Reducer 中咱們給了 state 一個默認的值,這就是咱們的初始 state。關於 Redux 是如何返回初始值的,繼續往下看。
Action 和 Reducer 都有了,那怎麼讓他們兩個聯繫起來呢?下面我們看一下 Redux 中的精華部分 - Store
。
首先咱們先建立 Store:
在 store.js
中,咱們把 reducer 傳給 createStore
方法而且執行了它,來建立 Store。這個方法是 Redux 的精髓所在。
下面看一下 createStore
的源碼部分:
createStore 接收三個參數:
reducer{Function}
state{any}
(可選參數)enhancer{Function}
(可選參數)返回一個對象,這個對象包含五個方法,我們目前先只關注前三個方法:
在整個 createStore
中,只執行了 dispatch({ type: ActionTypes.INIT })
這一句代碼。那 dispatch 作了什麼呢?
我省略了一些代碼,這是 dispatch 方法的核心代碼。它接收一個 action 對象,而且把 createStore
接收到的 state 參數和經過 dispatch 方法傳進來的 Action 參數,傳給了 Reducer 而且執行,而後把 reducer 返回的 state 賦值給 currentState
。最後執行訂閱隊列中的方法。
createStore 方法一上來就執行了 dispatch({ type: ActionTypes.INIT })
。這句話的意思我們如今也清楚了,它的主要目的就是初始化 state。
如今我們已經把 Action 和 Reducer 聯繫起來了。能夠看到,在 createStore
方法中,它維護一個變量 currentState
,經過 dispatch 方法來更新 currentState
變量。外部若是想要獲取 currentState
,只須要調用 createStore 暴露出來的 getState
方法便可:
getState 方法是獲取當前的 currentState
變量,若是想要實時獲取 state,那就須要註冊監聽事件,每次 dispatch 的時候,就都會執行一遍這個事件。
如今我們來梳理一下思路:
Action
:這次動做的目的Reducer
:根據接收到的 Action 命令來作具體的操做Store
:把 Action 傳給 Reducer,而且更新 state。而後執行訂閱隊列中的方法。Redux 和 React 是兩個獨立的產品,可是若是兩個結合使用,就不得不提 react-redux
這個庫了,能夠大大的簡化代碼的書寫,可是我們先不講這個庫,來本身實現一下。
你們都知道,在 React 中咱們都是使用 props 來傳遞數據的。整個 React 應用就是一個組件樹,一層一層的往下傳遞數據。
可是若是在一個多層嵌套的組件結構中,只有最裏層的組件才須要使用這個數據,致使中間的組件都須要幫忙傳遞這個數據,咱們就要寫不少次 props,這樣就很麻煩。
好在 React 提供了一個叫作 context
的功能,能夠很好的解決和這個問題。
所謂 context 就是「上下文環境」,讓一個樹狀組件上全部組件都能訪問一個共同的對象,爲了完成這個任務,須要上下級組件的配合。
首先是上級組件宣稱本身支持 context,而且提供給一個函數來返回表明 context 的對象。
而後,子組件只要宣稱本身須要這個 context,就能夠經過 this.context
來訪問這個共同的對象。
因此咱們能夠利用 React 的 context,把 Store 掛在它上面,就能夠實現全局共享 Store 了。
瞭解瞭如何在 React 中共享 Store,那我們就動手來實現一下吧~
Provider
,顧名思義,它是提供者,在這個例子中,它是 context 的提供者。
就像下面這樣來使用:
Provider
提供了一個函數 getChildContext
,這個函數返回的是就是表明 context 的對象。在調用 Store 的時候能夠從 context 中獲取:this.context.store
。
Provider
爲了聲明本身是 context 的提供者,還須要指定 Provider
的 childContextTypes
屬性(須要和 getChildContext
對其)。
只有具有上面兩個特色,Provider
纔有可能訪問到 context。
好了,Provider
組件我們已經完成了,下面我們就能夠把 context 掛到整個應用的頂層組件上了。
進入整個應用的入口文件 index.js:
咱們把 Store 做爲 props 傳遞給了 Provider
組件,Provider 組件把 Store 掛在了 context 上。因此下面咱們就要從 context 中來獲取 Store 了。
下面是咱們整個計數器應用的骨架部分。
咱們先把頁面渲染出來:
在上面的組件中,咱們作了兩件事情:
如何聲稱本身須要 context 呢?
contextTyp
e 賦值,值的類型和 Provider 中提供的 context 的類型同樣。this.context
來調用 context 了。getState
方法。上面咱們瞭解過,getState 方法返回的就是 createStore 方法中維護的那個變量。在 createStore
執行的時候,就已經初始化過了這個變量。
接下來咱們給「加號」加上具體動做。
咱們想要把數字加一,因此就有一個「加」的動做,這個動做就是一個 Action,這個 Action 就是 addAction
。若是想要觸發這個動做,就須要執行 dispatch 方法。
經過 dispatch 方法,把 Action 對象傳給了 Reducer,通過處理,Reducer 會返回一個加 1 的新 state。
其實如今 Store 中的數據已是最新的了,能夠咱們看到頁面上尚未更新。那咱們如何能獲取到最新的 state 呢?
就像關注公衆號同樣,我只須要在最開始的時候訂閱一下,以後每次有更新,我都會收到推送。
這個時候就要使用 Store 的 subscribe 方法了。顧名思義,就是我要訂閱 state 的變化。咱們先看一下代碼怎麼寫:
在組件的 componentDidMount
生命週期中,咱們調用了 store 的 subscribe
方法,每次 state 更新的時候,都會去調用 onChange
方法;在 onChange
方法中,咱們會取得最新的 state,而且賦值。在組件被卸載的時候,咱們取消訂閱。
上面這樣就完成了訂閱功能。這時候再運行程序,能夠發現頁面上就會顯示最新的數字了。
在這個例子中,能夠看出來咱們能夠抽象出來不少邏輯,好比 Provider
,還有訂閱 store 變化的功能。其實這些 react-redux 都已經幫咱們作好了。
在我們這個例子中,只是簡單的實現了一下 react-redux
部分功能。具體的你們能夠到官網上去看。
下面我們來總結一下 redux 和 react 結合使用的整個數據流:
good~ 咱們已經所有完成了整個應用。如今你們瞭解 Redux 的運行原理 了嗎?
具體代碼能夠到 GitHub 查看。
參考資料:
本文永久連接: