看漫畫,學 Redux

Flux 架構已然讓人以爲有些迷惑,而比 Flux 更讓人摸不着頭腦的是 Flux 與 Redux 的區別。Redux 是一個基於 Flux 思想的新架構方式,本文將探討它們的區別。react

若是你尚未看過這篇關於 Flux 的文章(譯者注:也能夠參考這篇),你應該先閱讀一下。redux

爲何要改變 Flux?

Redux 解決的問題和 Flux 同樣,但 Redux 能作的還有更多。數組

和 Flux 同樣,Redux 讓應用的狀態變化變得更加可預測。若是你想改變應用的狀態,就必須 dispatch 一個 action。你沒有辦法直接改變應用的狀態,由於保存這些狀態的東西(稱爲 store)只有 getter 而沒有 setter。對於 Flux 和 Redux 來講,這些概念都是類似的。網絡

那麼爲何要新設計一種架構呢?Redux 的創造者 Dan Abramov 發現了改進 Flux 架構的可能。他想要一個更好的開發者工具來調試 Flux 應用。他發現若是稍微對 Flux 架構進行一些調整,就能夠開發出一款更好用的開發者工具,同時依然能享受 Flux 架構帶給你的可預測性。架構

確切的說,他想要的開發者工具包含了代碼熱替換(hot reload)和時間旅行(time travel)功能。然而要想在 Flux 架構上實現這些功能,確實有些麻煩。ide

問題1:store 的代碼沒法被熱替換,除非清空當前的狀態

在 Flux 中,store 包含了兩樣東西:函數

  1. 改變狀態的邏輯
  2. 當前的狀態

在一個 store 中同時保存這兩樣東西將會致使代碼熱替換功能出現問題。當你熱替換掉 store 的代碼想要看看新的狀態改變邏輯是否生效時,你就丟失了 store 中保存的當前狀態。此外,你還把 store 與 Flux 架構中其它組件產生關係的事件系統搞亂了。工具

解決方案ui

將這兩樣東西分開處理。讓一個對象來保存狀態,這個對象在熱替換代碼的時候不會受到影響。讓另外一個對象包含全部改變狀態的邏輯,這個對象能夠被熱替換由於它不用關心任何保存狀態相關的事情。spa

問題2:每次觸發 action 時狀態對象都被直接改寫了

時間旅行調試法的特性是:你能掌握狀態對象的每一次變化,這樣的話,你就能輕鬆的跳回到這個對象以前的某個狀態(想象一個撤銷功能)。

要實現這樣的功能,每次狀態改變以後,你都須要把舊的狀態保存在一個數組中。可是因爲 JavaScript 的對象引用特性,簡單的把一個對象放進數組中並不能實現咱們須要的功能。這樣作不能建立一個快照(snapshot),而只是建立了一個新的指針指向同一個對象。

因此要想實現時間旅行特性,每個狀態改變的版本都須要保存在不一樣的 JavaScript 對象中,這樣你纔不會不當心改變了某個歷史版本的狀態。

解決方案

當一個 action 須要 store 響應時,不要直接修改 store 中的狀態,而是將狀態拷貝一份並在這份拷貝的狀態上作出修改。

問題3:沒有合適的位置引入第三方插件

當你在寫一些調試性工具時,你但願它們可以更加通用。一個使用該工具的用戶應該能夠直接引入這個工具而不須要作額外的包裝或橋接。

要實現這樣的特性,Flux 架構須要一個擴展點。

一個簡單的例子就是日誌。好比說你但願 console.log() 每個觸發的 action 同時 console.log() 這個 action 被響應完成後的狀態。在 Flux 中,你只能訂閱(subscribe) dispatcher 的更新和每個 store 的變更。可是這樣就侵入了業務代碼,這樣的日誌功能不是一個第三方插件可以輕易實現的。

解決方案

將這個架構的部分功能包裝進其餘的對象中將使得咱們的需求變得更容易實現。這些「其餘對象」在架構原有的功能基礎之上添加了本身的功能。你能夠把這 種擴展點看作是一個加強器(enhancers)或者高階對象(higher order objects),亦或者中間件(middleware)。

此外,使用一個樹形結構來組織全部改變狀態的邏輯,這樣當狀態發生改變的時候 store 只會觸發一個事件來通知視圖層(view),而這一個事件會被整棵樹中的全部邏輯處理(譯者注:「處理」不表明必定會改變狀態,這些改變狀態的邏輯本質上 是函數,函數內部會根據 action 的類型等來肯定是否對狀態進行改變)。

*注意:就上述這些問題和解決方案來講,我主要在關注開發者工具這一使用場景。實際上,對 Flux 作出的這些改變在其餘場景中也很是有幫助。在上述三點以外,Flux 和 Redux 還有更多的不一樣點。好比,相比於 Flux,Redux 精簡了整個架構的冗餘代碼,而且複用 store 的邏輯變得更加簡單。這裏有一個 Redux 優勢的列表可供參考。

那麼讓咱們來看看 Redux 是怎麼讓這些特性變爲現實的。

新的角色

從 Flux 演進到 Redux,整個架構中的角色發生了些許的變化。

Action creators

Redux 保留了 Flux 中 action creator 的概念。每當你想要改變應用中的狀態時,你就要 dispatch 一個 action,這也是惟一改變狀態的方法。

就像我在這篇關於 Flux 的文章中 提到的同樣,我把 action creator 看作是一個報務員(負責發電報的人,telegraph operator),你找到 action creator 告訴他你大體上想要傳達什麼信息,action creator 則會把這些信息格式化爲一種標準的格式,以便系統中的其餘部分可以理解。

與 Flux 不一樣的是,Redux 中的 action creator 不會直接把 action 發送給 dispatcher,而是返回一個格式化好的 JavaScript 對象。

The store

我把 Flux 中 store 的那一套機制描述爲一種控制過分的官僚體系。你不能簡單直接的修改狀態,而是要求全部的狀態改變都必須由 store 親自產生,還必需要經歷 action 分發那種套路。在 Redux 中,store 依然是這麼的充滿控制慾和官僚主義,可是又有些不同。

在 Flux 中,你能夠擁有多個 store,每個 store 都有本身的統治權。每一個 store 都保存着本身對應的那部分狀態,以及全部修改這些狀態的邏輯。

而 Redux 中的 store 更喜歡將權力下放,事實上不得不這麼作。由於在 Redux 中,你只能有一個 store……因此若是你打算像 Flux 那樣讓 store 徹底獨立處理本身的事情,那麼在 Redux 中,store 裏的工做量將變得很是大。

所以,Redux 中的 store 首先會保存整個應用的全部狀態,而後將「判斷哪一部分狀態須要改變」的任務分配下去。而以根 reducer(root reducer)爲首的 reducer 們將會承擔這個任務。

你可能發現這裏好像沒有 dispatcher 什麼事。是的,雖然看起來有點兒越權,但 Redux 裏的 store 已經徹底接管了 dispatcher 相關的工做。

The reducers

當 store 須要知道一個 action 觸發後狀態須要怎麼改變時,他會去詢問 reducer。根 reducer 會根據狀態對象的鍵(key)將整個狀態樹進行拆分,而後將拆分後的每一塊子狀態傳到知道該怎麼對這塊狀態進行響應的子 reducer 那裏處理。

我把 reducers 看作是對複印情有獨鍾的白領們。他們不但願把任何事搞砸,所以他們不會修改任何傳遞給他們的文件。取而代之的是,他們會對這些文件進行復印,而後在複印件上進行修改。(譯者注:固然,當這些修改後的複印件定稿後,他們也不會再去修改這些複印件。)

這是 Redux 的核心思想之一。不直接修改整個應用的狀態樹,而是將狀態樹的每一部分進行拷貝並修改拷貝後的部分,而後將這些部分從新組合成一顆新的狀態樹。

子 reducers 會把他們建立的副本傳回給根 reducer,而根 reducer 會把這些副本組合起來造成一顆新的狀態樹。最後根 reducer 將新的狀態樹傳回給 store,store 再將新的狀態樹設爲最終的狀態。

若是你有一個小型應用,你可能只有一個 reducer 對整個狀態樹進行拷貝並做出修改。又或者你有一個超大的應用,你可能會有若干個 reducers 對整個狀態樹進行修改。這也是 Flux 和 Redux 的另外一處區別。在 Flux 中,store 並不須要與其餘 store 產生關聯,並且 store 的結構是扁平的。而在 Redux 中,reducers 是有層級結構的。這種層級結構能夠有若干層,就像組件的層級結構那樣。

The views: 智能組件(smart components)和木偶組件(dumb components)

Flux 擁有控制型視圖(controller views) 和常規型視圖(regular views)。控制型視圖就像是一個經理同樣,管理着 store 和子視圖(child views)之間的通訊。

在 Redux 中,也有一個相似的概念:智能組件和木偶組件。(譯者注:在最新的 Redux 文檔中,它們分別叫作容器型組件 Container component 和展現型組件 Presentational component)智能組件的職責就像經理同樣,可是比起 Flux 中的角色,Redux 對經理的職責有了更多的定義:

  • 智能組件負責全部的 action 相關的工做。若是智能組件裏包含的一個木偶組件須要觸發一個 action,智能組件會經過 props 傳一個 function 給木偶組件,而木偶組件能夠在須要觸發 action 時調用這個 function。
  • 智能組件不定義 CSS 樣式。
  • 智能組件幾乎不會產生本身的 DOM 節點,他的工做是組織若干的木偶組件,由木偶組件來生成最終的 DOM 節點。

木偶組件不會直接依賴 action(譯者注:即不會在木偶組件裏 require action 相關的文件),由於全部的 action 都會當作 props 傳下來。這意味着木偶組件能夠被任何一個邏輯不一樣的 App 拿去使用。同時木偶組件也須要有必定的樣式來讓本身變得好看一些(固然你可讓木偶組件接受某些 props 做爲設置樣式的變量)。

視圖層綁定

要把 store 綁定到視圖上,Redux 還須要一點幫助。若是你在使用 React,那麼你須要使用 react-redux。

視圖綁定工做有點像爲組件樹服務的 IT 部門。IT 部門確保全部的組件都正確的綁定到 store 上,並處理各類技術上的細節,以確保餘下層級的組件對綁定相關的操做毫無感知。

視圖層綁定引入了三個概念:

  1. <Provider> 組件: 這個組件須要包裹在整個組件樹的最外層。這個組件讓根組件的全部子孫組件可以輕鬆的使用 connect() 方法綁定 store。
  2. connect():這是 react-redux 提供的一個方法。若是一個組件想要響應狀態的變化,就把本身做爲參數傳給 connect() 的結果(譯者注:connect() 返回的依然是一個函數),connect() 方法會處理與 store 綁定的細節,並經過 selector 肯定該綁定 store 中哪一部分的數據。
  3. selector:這是你本身編寫的一個函數。這個函數聲明瞭你的組件須要整個 store 中的哪一部分數據做爲本身的 props。

根組件

全部的 React 應用都存在一個根組件(root component)。他其實就是整個組件樹最外層的那個組件,可是在 Redux 中,根組件還要承擔額外的任務。

根組件承擔的角色有點像企業中的高管,他將整個團隊整合到一塊兒來完成某項任務。他會建立 store,並告訴 store 使用哪些 reducers,並最終完成視圖層的綁定。

當完成整個應用的初始化工做後,根組件的就再也不插手整個應用的運行過程了。每一次從新渲染(re-render)都沒有根組件什麼事,這些活兒都由根組件下面的子組件完成,固然也少不了視圖層綁定的功勞。

Redux 完成的運行流程

讓咱們看看上述各個部分是怎樣組合成一個能夠運行的應用的。

配置環節

應用中的不一樣部分須要在配置環節中整合到一塊兒。

(1) 準備好 store。根組件會建立 store,並經過 createStore(reducer) 方法告訴 store 該使用哪一個根 reducer。與此同時,根 reducer 也經過 combineReducers() 方法組建了一隻向本身彙報的 reducer 團隊。

(2) 設置 store 和組件之間的通訊。根組件將它全部的子組件包裹在 <Provider> 組件中,並創建了 Provider 與 store 之間的聯繫。

Provider 本質上建立了一個用於更新視圖組件的網絡。那些智能組件經過 connect() 方法連入這個網絡,以此確保他們可以獲取到狀態的更新。

(3) 準備好 action callback。爲了讓木偶組件更好的處理 action,智能組件能夠用 bindActionCreators() 方法來建立 action callback。這樣作以後,智能組件就能給木偶組件傳入一個回調(callback)。對應的 action 會在木偶組件調用這個回調時被自動 dispatch。(譯者注:使用 bindActionCreators() 使得木偶組件無需關心 action 的 type 等信息,只用調用 props 中的某個方法傳入須要的參數做爲 action 的 payload 便可)

數據流

如今咱們的應用已經配置完成,用戶能夠開始操做了。讓咱們觸發一個 action,看看數據是怎樣流動的。

(1) 視圖發出了一個 action,action creator 將這個 action 格式化並返回。

(2) 這個 action 要麼被自動 dispatch(若是咱們在配置階段使用了 bindActionCreators()),要麼由視圖層手動 dispatch。

(3) store 接受到這個 action 後,將當前的狀態樹和這個 action 傳給根 reducer。

(4) 根 reducer 將整個狀態樹切分紅一個個小塊,而後將某一個小塊分發給知道怎麼處理這部份內容的子 reducer。

(5) 子 reducer 將傳入的一小塊狀態樹進行拷貝,而後在副本上進行修改,並最終將修改後的副本返回根 reducer。

(6) 當全部的子 reducer 返回他們修改的副本以後,根 reducer 將這些部分再次組合起來造成一顆新的狀態樹。而後根 reducer 將這個新的狀態樹交還給 store,store 再把本身的狀態置爲這個最新的狀態樹。

(7) store 告訴視圖層綁定:「狀態更新啦」

(8) 視圖層綁定讓 store 把更新的狀態傳過來

(9) 視圖層綁定觸發了一個從新渲染的操做(re-render)

這就是我所理解的 Redux,但願對你有所幫助。

相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息