乾貨 | React技術棧耕耘 —— Redux

做者:小boy (滬江web前端開發工程師)
本文爲原創文章,有不當之處歡迎指出。轉載請註明出處。
文章示例代碼:https://github.com/ikcamp/rea...前端

Redux 是近年來提出的 Flux 思想的一種實踐方案,在它以前也有 reflux 、 fluxxor 等高質量的做品,但短短几個月就在 GitHub 上獲近萬 star 的成績讓這個後起之秀逐漸成爲 Flux 的主流實踐方案。react

正如 Redux 官方所稱,React 禁止在視圖層直接操做 DOM 和異步行爲 ( removing both asynchrony and direct DOM manipulation ),來拆開異步和變化這一對冤家。但它依然把狀態的管理交到了咱們手中。Redux 就是咱們的狀態管理小管家。git

安利的話先暫時說到這,本期的技術週刊將爲你帶來 React-Redux 在滬江前端團隊中的實踐。github

0. 放棄

你沒有看錯,在開始以前咱們首先談論一下什麼狀況下不該該用 Redux。
所謂殺雞焉用宰牛刀,任何技術方案都有其適用場景。做爲一個思想的實踐方案,Redux 必然會爲實現思想立規矩、鋪基礎,放在複雜的 React 應用裏,它會是「金科玉律」,而放在結構不算複雜的應用中,它只會是「繁文縟節」。web

若是咱們將要構建的應用無需多層組件嵌套,狀態變化簡單,數據單一,那麼就應放棄 Redux ,選用單純的 React 庫 或其餘 MV* 庫。畢竟,沒有人願意僱傭一個收費比本身收入還高的財務顧問。編程

1. 思路

首先,咱們回顧一下 Redux 的基本思路redux

clipboard.png

當用戶與界面交互時,交互事件的回調函數會觸發 ActionCreators ,它是一個函數,返回一個對象,該對象攜帶了用戶的動做類型和修改 Model 必需的數據,這個對象也被咱們稱做 Action 。數組

以 TodoList 爲例,添加一個 Todo 項的 ActionCreator 函數以下所示:promise

clipboard.png

在上例中,addTodo 就是 ActionCreator 函數,該函數返回的對象就是 Action 。數據結構

其中 type 爲 Redux 中約定的必填屬性,它的做用稍後咱們會講到。而 text 則是執行 「添加 Todo 項「 這個動做必需的數據。

固然,不一樣動做所須要的數據也不盡相同,如 「刪除Todo」 動做,咱們就須要知道 todo 項的 id,「拉取已有的Todo項」 動做,咱們就須要傳入一個元素爲 Todo 項對象的數組( todos )。形如 text 、 id 、 todos 這類屬性,咱們習慣稱呼其爲 「 payload 」 。

如今,咱們獲得了一個 「栩栩如生」 的動做。它足夠簡潔,但擔任 Model 的 store 暫時還不知道如何感知這個動做從而改變數據結構。

爲了處理這個關鍵問題,Reducer 巧然登場。它仍然是一個函數,並且是沒有反作用的純函數。它只接收兩個參數:state 和 action ,返回一個 newState 。

沒錯,state 就是你在 React 中熟知的 state,但根據 Redux 三原則 之一的 「單一數據源」 原則,Reducer 幽幽地說:「你的 state 被我承包了。」

因而,單一數據源規則實施起來,是規定用 React 的頂層容器組件( Container Components )的 state 來存儲單一對象樹,同時交給 Redux store 來管理。

這裏區分一下 state 和 Redux store:state 是真正儲存數據的對象樹,而 Redux store 是協調 Reducer、state、Action 三者的調度中心。

而如此前所說,Reducer 此時手握兩個關鍵信息:舊的數據結構(state),還有改變它所須要的信息 (action),而後聰明的 Reducer 算盤一敲,就能給出一個新的 state ,從而更新數據,響應用戶。下面依然拿 TodoList 舉例:

clipboard.png

當接收到一個 action 時,Reducer 從 action.type 識別出該動做是要添加 Todo 項,而後路由到相應的處理方案,接着根據 action.text 完成了處理,返回一個 newState 。過程之間,整個應用的 state 就從 state => newState 完成了狀態的變動。

這個過程讓咱們很天然地聯想到去銀行存取錢的經歷,顯然咱們應該告訴櫃檯操做員要存取錢,而不是遙望着銀行的金庫自言自語。

Reducer 爲咱們梳理了全部變動 state 的方式,那麼 Redux store 從無到有,從有到變都應該與 Reducer 強關聯。

所以,Redux 提供了 createStore 函數,他的第一個參數就是 Reducer ,用以描繪 state 的更改方式。第二個是可選參數 initialState ,此前咱們知道,這個 initialState 參數也能夠傳給 Reducer 函數。放在這裏作可選參數的緣由是爲同構應用提供便捷。

clipboard.png

createStore 函數最終返回一個對象,也就是咱們所說的 store 對象。主要提供三個方法: getStatedispatch subscribe。 其中 getState() 得到 state 對象樹。dispatch(actionCreator) 用以執行 actionCreators,建起從 action 到 store 的橋樑。

僅僅完成狀態的變動可不算完,咱們還得讓視圖層跟上 store 的變化,因而 Redux 還爲 store 設計了 subscribe 方法。顧名思義,當 store 更新時,store.subscribe() 的回調函數會更新視圖層,以達到 「訂閱」 的效果。

在 React 中,有 react-redux 這樣的橋接庫爲 Redux 的融入鋪平道路。因此,咱們只需爲頂層容器組件外包一層 Provider 組件、再配合 connect 函數處理從 store 變動到 view 渲染的相關過程。

clipboard.png

而頂層容器組件往下的子組件只需憑藉 props 就能一層層地拿到 store 數據結構的數據了。就像這樣:

clipboard.png

至此,咱們走了一圈完整的數據流。然而,在實際項目中,咱們面臨的需求更爲複雜,與此同時,redux 和 react 又是具備強大擴展性的庫,接下來咱們將結合以上的主體思路,談談咱們在實際開發中會遇到的一些細節問題。

2. 細節

應用目錄

清晰的思路須輔以分工明確的文件模塊,才能讓咱們的應用達到更佳的實踐效果,同時,統一的結構也便於腳手架生成模板,提升開發效率。

如下的目錄結構爲團隊夥伴屢次探討和改進而來(限於篇幅,這裏只關注 React 應用的目錄。):

clipboard.png

入口文件 app.js 與頂層組件 react/container.js

這塊咱們基本上保持和以前思路上的一致,用 react-redux 橋接庫提供的 Provider 與函數 connect 完成 Redux store 到 React state 的轉變。

細心的你會在 Provider 的源碼中發現,它最終返回的仍是子組件(本例中就是頂層容器組件 「Container「 )。星星仍是那個星星,Container 仍是那個 Container,只是多了一個 Redux store 對象。

而 Contaier 做爲 業務組件 Wrapper 的 高階組件 ,負責把 Provider 賦予它的 store 經過 store.getState() 獲取數據,轉而賦值給 state 。而後又根據咱們定義的 mapStateToProps 函數按必定的結構將 state 對接到 props 上。 mapStateToProps 函數咱們稍後詳說。以下所見,這一步主要是 connect 函數乾的活兒。

clipboard.png

clipboard.png

業務組件 component/Wrapper.js 與 mapStateToProps

這兩個模塊是整個應用很重要的業務模塊。做爲一個複雜應用,將 state 上的數據和 actionCreator 合理地分發到各個業務組件中,同時要易於維護,是開發的關鍵。

首先,咱們設計 mapStateToProps 函數。須要謹記一點: 拿到的參數是 connect 函數交給咱們的根 state,返回的對象是最終 this.props 的結構。

和 Redux 官方示例不一樣的是,咱們爲了可讀性,將分發 action 的函數也囊括進這個結構中。這也是得益於 bindActions 模塊,稍後咱們會講到。

clipboard.png

這樣,咱們這個函數就準備好履行它分發數據和組件行爲的職責了。那麼,它又該如何 「服役」 呢?

敏銳的你必定察覺到剛纔咱們設計的結構中,以 「 params 」 開頭的屬性既沒起到給組件展現數據的做用,又沒有爲組件發送 action 的功能。它們即是咱們分發以上兩種功能屬性的關鍵。

咱們先來看看業務組件 Wrapper :

clipboard.png

如今,param 屬性們爲咱們展現了它扮演的角色:在組件中實際分發數據和方法的快遞小哥。這樣,即便項目越變越大,組件嵌套愈來愈多,咱們也能在 param.js 模塊中,清晰地看到咱們的組件結構。需求更改的時候,咱們也能快速地定位和修改,而不用對着堆積如山的組件模塊梳理父子關係。

相信你應該能猜到剩下的子組件們怎麼取到數據了,這裏限於篇幅就不貼出它們的代碼了。

Action 模塊: react/action.js、react/actionType.js 和 react/bindActions.js

在前面的介紹中,咱們提到:一個 ActionCreator 長這樣:

clipboard.png

而在 Redux 中,真正讓其分發一個 action ,並讓 store 響應該 action,依靠的是 dispatch 方法,即:

clipboard.png

交互動做一多,就會變成:

clipboard.png

而容易想到:抽象出一個公用函數來分發 action (這裏粗略寫一下個人思路,簡化方式並不惟一)

clipboard.png

而細心的 Redux 已經爲咱們提供了這個方法 —— bindActionCreator
因此,咱們的 bindActions.js 模塊就借用了 bindActionCreator 來簡化 action 的分發:

clipboard.png

不難想象,action 模塊裏就是一個個 actionCreator :

clipboard.png

爲了更好地合做,咱們單獨爲 action 的 type 劃分了一個模塊

clipboard.png

react/reducers/ 和 react/store.js

前面咱們說到,reducer 的做用就是區別 action type 而後更新 state ,這裏再也不贅述。可上手實際項目的時候,你會發現 action 類型和對應處理方式多起來會讓單個 reducer 迅速龐大。

爲此,咱們就得千方百計將其按業務邏輯拆分,以避免難以維護。可是如何把拆分後的 Reducer 組合起來呢 Redux 再次爲咱們提供便捷 —— combineReducers 。

只有單一 Reducer 時,想必代碼結構你也瞭然:

clipboard.png

咱們最終獲得的 state 結構是:

  • state

    • demoAPP

當有多個 reducer 時:

clipboard.png

咱們最終獲得的 state 結構是:

  • state

    • demoAPP
    • reducerB

想必你已經想到更進一步,把這些 Reducer 拆分到相應的文件模塊下:

clipboard.png

接着,咱們來看 store 模塊:

clipboard.png

怎麼和想象的不同?不該該是這樣嗎:

clipboard.png

這裏引入 redux 中間件的概念,你只需知道 redux 中間件的做用就是 在 action 發出之後,給咱們一個再加工 action 的機會 就能夠了。

爲何要引入 redux-thunk 這個中間件呢?

要知道,咱們此前所討論的都是同步過程。實際項目中,只要遇到請求接口的場景(固然不僅有這種場景)就要去處理異步過程。

前面咱們知道,dispatch 一個 ActionCreator 會當即返回一個 action 對象,用以更新數據,而中間件賦予咱們再處理 action 的機會。

試想一下,若是咱們在這個過程當中,發現 ActionCreator 返回的並非一個 action 對象,而是一個函數,而後經過這個函數請求接口,響應就緒後,咱們再 dispatch 一個 ActionCreator ,此次咱們真的返回一個 action ,而後攜帶接口返回的數據去更新 state 。 這樣一來不就解決了咱們的問題嗎?

固然,這只是基本思路,關於 redux 的中間件設計,又是一個有趣的話題,有興趣咱們能夠再開一篇專門討論,這裏點到爲止。

回到咱們的話題,通過

clipboard.png

這樣包裝一遍 store 後,咱們就能夠愉快地使用異步 action 了:

clipboard.png

這裏咱們用 promise 方式來處理請求,model.js 模塊如你所想是一些接口請求 promise,就像這樣:

clipboard.png

你也能夠參閱咱們往期介紹的其餘方式。

最後,咱們再來完善一下以前的流程:

clipboard.png

3.結語

Redux 的 API 一隻手都能數得完,源碼更是精煉,加起來不超過500行。但它給咱們帶來的,不啻是一套複雜應用解決方案,更是 Flux 思想的精簡表達。此外,你還能夠從中體會到函數式編程的樂趣。

一千個觀衆心中有一千個哈姆萊特,你腦海裏的又是哪個呢?

本文示例代碼GitHub地址:https://github.com/ikcamp/rea...

參考

《Redux 官方文檔》
《深刻 React 技術棧》

clipboard.png
圖片描述

iKcamp原創新書《移動Web前端高效開發實戰》已在亞馬遜、京東、噹噹開售。

相關文章
相關標籤/搜索