經過了解 Redux 簡單源碼,掌握 Redux 數據流原理

先祭上本文的思惟導圖:html

1、爲何講 Redux

在項目中用 Redux 的時候,有時候就以爲會用,可是不明白爲何這樣用。致使在 debug 的時候,沒法快速的 debug 出緣由。並且 Redux 的源碼也不復雜,暴露出來的只有 5 個 API,能夠做爲很好的閱讀源碼的開端,因此在這裏很開心能夠和你們一塊兒來探索 Redux。若是有些講的不許確的地方,歡迎你們提出來;也特別但願你們積極的討論,迸發出更多想法。react

2、Redux 爲何會出現

要了解 Redux,就要從 Flux 提及。能夠認爲 Redux 是 Flux 思想的一種實現。那 Redux 是爲何被提出來呢?就要提一下 MVC 了。git

一、MVC

說到 Flux,咱們就不得不要提一下 MVC 框架。github

MVC 框架將應用分爲 3 個部分:redux

  • View:視圖,展現用戶界面
  • Controller:管理應用的行爲和數據,響應用戶的輸入(常常來自 View)和更新狀態的指令(常常來自 Model)
  • Model:管理數據,大部分業務邏輯也在 Model 中

用戶請求先到達 Controller,而後 Controller 調用 Model 得到數據,再把數據交給 View。這個想法是很理想的想法。在實際的框架應用中,大部分都是容許 View 和 Model 直接通訊的。當項目變的愈來愈大的時候,這種不一樣模塊之間的依賴關係就變得「不可預測」了,因此就變成了下面這樣子。性能優化

雖然這張圖有誇大的嫌疑,可是也說明了 MVC 在大型項目下,容易形成數據混亂的問題。架構

因此,Flux 誕生了。在寫這篇文章以前,我查閱不少資料,有些說 Flux 思想替代了 MVC 框架,我則不這麼認爲。我的以爲,Flux 思想更嚴格的控制了 MVC 的數據流走向。下面我們來看看 Flux 是如何嚴格控制數據流的。框架

二、Flux

一個 Flux 應用包含四個部分:ide

  • Dispatcher,處理動做分發,維持 Store 之間的依賴關係
  • Store,負責存儲數據和處理數據相關邏輯
  • Action,觸發 Dispatcher
  • View,視圖,負責顯示用戶界面

經過上圖能夠看出來,Flux 的特色就是單向數據流函數

  • 用戶在 View 層發起一個 Action 對象給 Dispatcher
  • Dispatcher 接收到 Action 並要求 Store 作相應的更新
  • Store 作出相對應更新,而後發出一個 change 事件
  • View 接收到 change 事件後,更新頁面

因此在 Flux 體系下,若是想要驅動界面,只能派發一個 Store,別無他法。在這種規矩下,若是想要追溯一個應用的邏輯就變得很輕鬆了。並且這種思想解決了 MVC 中沒法杜絕 View 和 Model 之間的直接對話的問題。

這裏就不具體講關於 Flux 的例子了,若是想要更瞭解 Flux ,能夠看一下阮一峯老師的 Flux 架構入門教程

四、Redux 誕生

Redux 是 Flux 的一種實現,意思就是除了「單向數據流」以外,Redux 還強調三個基本原則:

  • 惟一的 store(Single Source of Truth)
  • 保持狀態只讀(State is read-only)
  • 數據改變只能經過純函數完成(Changes are made with pure functions)

a. 惟一的 store

在 Flux 中,應用能夠擁有多個 Store,可是分紅多個 Store 容易形成數據冗餘,數據一致性不太好處理,並且 Store 之間可能還會有依賴,增長了應用的複雜度。因此 Redux 對這個問題的解決方法就是:整個應用只有一個 Store。

b. 保持狀態只讀

就是不能直接修改狀態。若是想要修改狀態,只能經過派發一個 Action 對象來完成。

c. 數據改變只能經過純函數完成

這裏說的純函數就是 Reducer。按照 redux 做者 Dan 的說法:Redux = Reducer + Flux

3、在 React 中應用 Redux

下面我們根據例子來了解一下 Reudx 在 React 中的應用。

一、Redux 中的數據流動

建立一個 Redux 應用須要下面幾部分:

  • Actions
  • Reducers
  • Store

他們分別是什麼意思呢?下面咱們來舉一個例子: 好比下面是商場某品牌鞋子的展現櫃:

鞋子展現櫃

店長來視察,發現鞋子2放的過高了,並且這款鞋仍是店裏的主推款,放在這個位置不適合宣傳,就讓店員把鞋子 2 往下挪兩排,放下去以後,店長看着舒服多了。

鞋子展現櫃

其實經過上面的例子,咱們如今就很好解釋 Redux 了:

  • View: 鞋子擺放在鞋架上的總體效果
  • Action: 店長給店員分配的任務(往下挪鞋子)
  • Reducers: 具體任務的實施者(把鞋子往下挪兩排)
  • Store: 鞋子在鞋架上的具體位置

因此整個過程能夠是下面這樣:

Store 決定了 View,而後用戶的交互產生了 ActionReducer 根據接收到的 Action 執行任務,從而改變 Store 中的 state,最後展現到 View 上。那麼,Reducer 如何接收到動做(Action)信號的呢?伴隨着這個問題,我們來看一個例子。

二、Redux 實踐

瞭解了 Redux 中各個部分表明的意思,下面我們來經過一個計數器的例子進一步瞭解一下 Redux 的原理(具體代碼能夠看 GitHub)。咱們想要的最終效果以下:

根據上面的思路,能夠分別把 Action 和 Reducer 定義爲:

  • 動做(Action): 加
  • 執行者(Reducer): 加 1

那麼咱們來建立 Action 和 Reducer 這兩個文件:

Actions

首先咱們建立一個 ActionTypes.jsActions.js 這兩個文件。ActionType 表明的就是 Action 的類型,能夠看到它是一個常量。在 Actions.js 中,咱們定義了兩個 Action 構造函數,他們返回的都是一個簡單對象 (plain object),並且每一個對象必須包含 type 屬性。

能夠看出來 Action 明確表達了咱們想要作的事情(加和減)。

可能有些同窗會問,在 Action 中,有時候也會 return 一個 function,不是簡單對象。其實這個時候,是中間件攔截了 Action,若是是 function,就執行中間件中的方法。可是我們此次不講中間件,因此就先忽略這種狀況。

Reducer

能夠看到 Reducer 是一個純函數。它接收兩個參數 state 和 Action,根據接收到的 state 和 Action 來判斷本身須要對當前的 state 作哪些操做,而且返回新的 state。

在 Reducer 中咱們給了 state 一個默認的值,這就是咱們的初始 state。關於 Redux 是如何返回初始值的,繼續往下看。

Action 和 Reducer 都有了,那怎麼讓他們兩個聯繫起來呢?下面我們看一下 Redux 中的精華部分 - Store

createStore

首先咱們先建立 Store:

store.js 中,咱們把 reducer 傳給 createStore 方法而且執行了它,來建立 Store。這個方法是 Redux 的精髓所在。

下面看一下 createStore 的源碼部分:

createStore 接收三個參數:

  • reducer{Function}
  • state{any}(可選參數)
  • enhancer{Function}(可選參數)

返回一個對象,這個對象包含五個方法,我們目前先只關注前三個方法:

  • dispatch
  • subscribe
  • getState

在整個 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 這個庫了,能夠大大的簡化代碼的書寫,可是我們先不講這個庫,來本身實現一下。

二、store 和 context 結合

你們都知道,在 React 中咱們都是使用 props 來傳遞數據的。整個 React 應用就是一個組件樹,一層一層的往下傳遞數據。

可是若是在一個多層嵌套的組件結構中,只有最裏層的組件才須要使用這個數據,致使中間的組件都須要幫忙傳遞這個數據,咱們就要寫不少次 props,這樣就很麻煩。

好在 React 提供了一個叫作 context 的功能,能夠很好的解決和這個問題。

所謂 context 就是「上下文環境」,讓一個樹狀組件上全部組件都能訪問一個共同的對象,爲了完成這個任務,須要上下級組件的配合。

首先是上級組件宣稱本身支持 context,而且提供給一個函數來返回表明 context 的對象。

而後,子組件只要宣稱本身須要這個 context,就能夠經過 this.context 來訪問這個共同的對象。

因此咱們能夠利用 React 的 context,把 Store 掛在它上面,就能夠實現全局共享 Store 了。

瞭解瞭如何在 React 中共享 Store,那我們就動手來實現一下吧~

Provider

Provider,顧名思義,它是提供者,在這個例子中,它是 context 的提供者。

就像下面這樣來使用:

Provider 提供了一個函數 getChildContext,這個函數返回的是就是表明 context 的對象。在調用 Store 的時候能夠從 context 中獲取:this.context.store

Provider 爲了聲明本身是 context 的提供者,還須要指定 ProviderchildContextTypes 屬性(須要和 getChildContext 對其)。

只有具有上面兩個特色,Provider 纔有可能訪問到 context。

好了,Provider 組件我們已經完成了,下面我們就能夠把 context 掛到整個應用的頂層組件上了。

進入整個應用的入口文件 index.js

咱們把 Store 做爲 props 傳遞給了 Provider 組件,Provider 組件把 Store 掛在了 context 上。因此下面咱們就要從 context 中來獲取 Store 了。

消費者

下面是咱們整個計數器應用的骨架部分。

咱們先把頁面渲染出來:

在上面的組件中,咱們作了兩件事情:

  • 第一件事情是:聲稱本身須要 context
  • 第二件事情是:初始化 state。

如何聲稱本身須要 context 呢?

  • 首先是須要給 App 組件的 contextType 賦值,值的類型和 Provider 中提供的 context 的類型同樣。
  • 而後在構造函數中加上 context,這樣組件的其餘部分就能夠經過 this.context 來調用 context 了。
  • 而後是初始化 state。看代碼能夠知道,咱們調用了掛在 context 上的 Store 的 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,而且賦值。在組件被卸載的時候,咱們取消訂閱。

上面這樣就完成了訂閱功能。這時候再運行程序,能夠發現頁面上就會顯示最新的數字了。

react-redux

在這個例子中,能夠看出來咱們能夠抽象出來不少邏輯,好比 Provider,還有訂閱 store 變化的功能。其實這些 react-redux 都已經幫咱們作好了。

  • Provider: 提供包含 store 的 context
  • connect: 把 state 轉化爲內層組件的 props,監聽 state 的變化,組件性能優化

在我們這個例子中,只是簡單的實現了一下 react-redux 部分功能。具體的你們能夠到官網上去看。

總結

下面我們來總結一下 redux 和 react 結合使用的整個數據流:

good~ 咱們已經所有完成了整個應用。如今你們瞭解 Redux 的運行原理 了嗎?

具體代碼能夠到 GitHub 查看。

參考資料:

本文永久連接

相關文章
相關標籤/搜索