1. 程式人生 > >React+Redux技術棧核心要點解析(中篇)

React+Redux技術棧核心要點解析(中篇)

感謝作者郭永峰的授權釋出。
作者:郭永峰,前端架構師,現用友網路 FED團隊負責人,目前主要負責企業級應用前端技術平臺建設工作,在前端工程化實現、Node 應用開發、React技術、移動開發等方向有豐富實踐經驗。Github地址:https://github.com/GuoYongfeng
責編:陳秋歌,關注前端開發等領域,尋求報道或者投稿請發郵件至chenqg#csdn.net。

使用 React + Redux 這個技術棧開發應用已經有很長一段時間了,我的一些使用經驗也許會有些主觀,但我覺得寫出來也許對你開始學習或是進階使用 React + Redux 會有些幫助。Redux 並不是只和 React 結合使用的,它也可以和其他的很多類庫結合起來一起使用,即使你還未開始深入使用,你也可以閱讀文中的部分內容。同時,如果你有一些建議或是疑惑,可以在 Github 給我提交 Issue,很樂意與你一起交流。

本次分享共有三篇文章,本文為第二篇。

對於學習 Redux 的一些建議

React 和 Redux 經常結合在一起使用,Redux 是 flux 架構模式的一種優秀實現,並且在 React 社群被廣泛使用,但也不是完全和 React 耦合在一起的。

全域性 state

並不是所有的全域性state都需要被儲存起來,一些元件可以使用 setState 來管理元件的內部狀態,這也是為什麼在學習 Redux 前要掌握 React 中的 setState ,否則你將習慣式的把所有的global state都儲存在store裡面。所以思考一下,在大型開發團隊裡面開發的複雜應用,你更不能將應用的所有 state 都切換成全域性狀態。

專案目錄如何組織

第一種方式是按功能劃分

React + Redux 的一些教程經常給我們展示按功能劃分的目錄,這也是一種很好的 React + Redux 學習方式,不過,將應用的所有 reducers 和 actions 都放在專門的資料夾維護的方案,並不是所有人都能贊同。

src/
--actions/
--reducers/
--components/

經常聽到的有建設性的想法是,目錄劃分應該以元件為核心,每個目錄應該有元件本身以及它所對應的 reducers、actions,那麼一個示例的目錄結構應該是這樣的:

message/
--components
--reducer.js
--actions.js

一個包含 container component 、presenter component以及測試相關的詳細的元件目錄會是這樣的:

message/
--components/
----messageItem/
------presenter.js
------spec.js
----messageList/
------container.js
------presenter.js
------spec.js
--reducer/
----index.js
----spec.js
--actions/
----index.js
----spec.js

當然了,也並不是大家都會喜歡這種方式。(其實,我個人是很贊同這樣的就近維護元件的原則的,因為將各個功能性的reducer和action都丟到對應的目錄,這以後維護起來會更加困難,檔案也不好找,這可不像是MVC那樣的分層結構。)尤其是將reducer隱藏在各個功能目錄中,這也不利於全域性性的來理解使用 redux 的架構意圖。所以建議是適當的在最初就抽取一些 reducers 來共享他們所包含的功能。

但在現實場景中,尤其是多個團隊在同一個應用專案中協作的時候,在開發進度的壓力之下,並沒有那麼多機會來正確的抽象出一些 reducers。反而通常是一口氣的封裝所有的功能模組,只為了感覺把活給幹完了,讓需求按時上線。

第二種方式是對功能模組劃分清晰的界限

給每個模組都設定一個 index.js 檔案作為入口,這個檔案只是用於匯出一些API給其他的模組使用。在基於 React + Redux 的應用中,index.js 檔案可以用於匯出一個 container components ,或是一個presenter components、action creators、能用於其他地方的 reducer(但不是最終的reducer)。那麼,基於這樣的思考,我們的目錄就可以變成這樣了:

message/
--index.js
--components/
----messageItem/
------index.js
------presenter.js
------spec.js
----messageList/
------index.js
------container.js
------presenter.js
------spec.js
--reducer/
----index.js
----spec.js
--actions/
----index.js
----spec.js

那麼,在當前功能模組下的 index.js 檔案應該包含這些程式碼:

import MessageList from './messageList';

export default MessageList;

export MessageItem from './messageItem';
export reducer from './reducer';
export actions from './actions';

好了,這樣外部的其他模組就可以這樣在他的 index.js 檔案中呼叫 message 模組了。

// bad
import { reducer } from ./message/reducer;

// good
import { reducer } from ./message;

收穫:按功能模組以及清晰的界限可以幫助我們很好的組織程式碼和目錄。

命名約定

在軟體程式設計中命名可真是一件令人頭疼的事情,這跟給孩子取名一樣費勁,哈哈。合適的命名是實現可維護性、易於理解的程式碼的最好實踐,React + Redux 的應用中提供了大量的約束來幫助我們組織程式碼,而且不會在命名上固執己見。無論你的函式封裝在 reducer 還是 component 中,在action creator 或是 selector 中,你都應該有一個命名約束,並且在擴充套件應用之前就確定如何命名,否則經常會讓我們陷入難以捉摸的回撥和重構當中。

而我習慣為每個型別的函式都加上一個字首。

  • 在元件的callback中,為每個函式都加上 on 作為字首,比如 onCreateRplay
  • 在改變 state 的 reducer 中加上 applay 作為字首,比如 applyCreateReply
  • 在 selector 中 加上 get 作為字首,比如 getReply
  • 在 action creator 中加上 do 作為字首,比如 doCreateReply

也許你不一定習慣這種加上字首的方式,不過我還是推薦給你,同時也建議找到自己喜歡的命名約束規則。

追蹤狀態的改變

在持續迭代中的應用免不了定義大量的 action,而且還需要追溯 state 是如何改變的,redux-logger 可以幫助你看到所有的 state change。每條日誌都會顯示出 previous state、執行的 action、next state。

不過你得確保 actions 是可被裝置的,因此我建議為不同型別的 action 都加上一個字首,比如這樣:

const MESSAGE_CREATE_REPLY = 'message/CREATE_REPLY';

這樣的話,無論你在何時觸發了資訊回覆這個動作,你都能看到 message/CREATE_REPLY 這一條日誌,如果出現 state 異常,便能迅速查到是那條錯誤的 state 改變而導致的。

儘可能讓 state tree 扁平化

在 Redux 中,扁平化的 state tree可以讓你的 reducers 更加的簡單,這樣你就不需要在整個 store 的狀態樹中深層的查詢到某個 state 後再將其修改,而是可以很輕鬆的就能實現。不過,在 Redux 中卻不能做這麼做,因為 state 是不可變的。

如果你正在開發一個部落格應用,需要維護一個類似這樣的列表物件,列表中包含 author 和 comment 欄位:

{
  post: {
    author: {},
    comments: [],
  }
}

不過實際情況是每個物件都需要有對應的 id 來進行維護:

{
  post: {
    id: '1',
    author: {
      id: 'a',
      ...
    },
    comments: [
      {
        id: 'z',
        ...
      },
      ...
    ],
  }
}

這個時候,我們將資料序列化之後將會變得更有意義,資料解構變得更加扁平化了。序列化之後的資料通過 id 關聯其他欄位,之後,你就可以通過實體物件來將其報酬,通過 id 來進行關聯資料的查詢。

{
  posts: {
    1: {
      authorId: 'a',
      commentIds: ['z', ...]
    }
  },
  authors: {
    a: {
      ...
    }
  },
  comments: {
    z: {
      ...
    }
  },
}

這樣,資料結構看起來就不在那麼深層嵌套了,當你需要改變資料的時候,就可以輕鬆的實現資料的不可變性了。

normalizr 是個強大的 library,可以幫助我們進行資料格式化,噢耶~!

單一資料來源原則

格式化之後的資料可以幫助你按同步的方式來管理 state ,而假如請求後端介面後返回的是深層巢狀的 blog 的 posts 資料結構呢,是不是欲哭無淚啊?! post 欄位依然包含 author 和 comments欄位,不過這次,comments 是一個數組,陣列中的每個物件都有 author 欄位:

{
  post: {
    author: { id: 'a' },
    comments: [
      {
        author: { id: 'b' },
        reply: {},
      },
      {
        author: { id: 'a' },
        reply: {},
      },
    ],
  }
}

我們可以看到資料結構中 author 欄位在 post 和 comments 中都有維護,這就導致巢狀的資料結構中出現了兩次,這就不是單一資料來源,當你改變了author 欄位的時候就會變得很困難了。

這個時候當你將資料格式化之後, author 這個欄位就只有一個了。

{
  authors: {
    a: {},
    b: {},
  }
}

當你想 follow 一個 author 的時候,就可以輕鬆的更新一個欄位了 — 資料來源是單一的:

{
  authors: {
    a: { isFollowed: true },
    b: {},
  }
}

應用中所有依賴了 author 這個欄位的地方都能得到更新。

Selectors

你還沒使用 selectors 嗎?沒關係,在 redux 中依然可以通過 mapStateToProps 來計算 props:

function mapStateToProps(state) {
  return {
    isShown: state.list.length > 0,
  };
};

而如何你一旦使用了 selectors 之後的話,你就可以將這部分計算的工作放到 selectors,從而讓 mapStateToProps 更加的簡潔:

function mapStateToProps(state) {
  return {
    isShown: getIsShown(state),
  };
};

你可以使用 reselect 來幫助你完成這些事情,它可以幫助你從 state 中計算得到衍生的資料,並且讓你的應用的效能得到提升:

  • Selectors 可以推匯出衍生資料,並傳遞所需資料的最小集,不用一次把所有資料都給元件,解決效能問題;
  • Selectors是可組合的,它可以作為其他 Selectors 的輸入;
  • Reselect所提供的 selector 是非常高效,除非它的引數改變了,否則 selector 不會重新計算,這在複雜應用中對效能提升是非常有幫助的。

不斷的重構

隨著時間得推移,你會想要重構你的程式碼,無論是你在應用中使用了 React 、React + Redux 或者其他前端框架,你總會不斷的掌握更加高效的程式碼組織方式,或者是一些很好的設計模式。

如果你的應用中的元件非常的多,你可以找到一個更好的方式來分離和組織木偶元件和容器元件,你會發現他們之間的關係並做一些公共的抽取;如果你還沒有使用合適的命名約束,你也可以在重構的時候去做這些事情。

Generators, Sagas, Observables, Epics, …

Redux 是一個非常優秀的 library,讓我們可以體驗不同的程式設計正規化和技術。而大家又常常需要不構建不同的類庫來實現 async action,這裡有幾種不同的方式來處理這些 side effects:

新手的話建議使用 redux thunk 來處理一些非同步操作;等你慢慢的熟悉整個生態及其相關的應用的時候,可以看看其他的相關類庫。Redux Saga 是目前被廣泛採用的一種實現方式。不過,Redux Observables 目前也被越來越多的人所接受,這可是需要掌握不少關於 rxjs 及其響應式程式設計的概念及其使用方式。

其實,整體看來,redux 生態圈的本身就產生了非常多的前端類庫,真是讓人應接不暇啊。但也別煩惱,那些你不需要用到的東西,自然也不需要都去掌握,對吧。

多閱讀一下 Redux 的實現原始碼

Redux 本身的原始碼並不多,總共也才五六個關鍵檔案,不超千行程式碼。如果你想對 Redux 更加熟悉,那麼強烈建議你要抽些時間多分析一下他的原始碼。

在開始學習的時候,也推薦部分學習視訊給你:

這些視訊內容不僅可以教你快速掌握如何使用 Redux,還可以讓你理解 Redux 的實現原理。最後,你就可以啃一啃 Redux 的原始碼了,可以學習到很多有意思的程式設計思想和函式式的運用。

相關閱讀:

歡迎加入“CSDN前端開發者”群,與更多專家、技術同行進行熱點、難點技術交流。請掃描以下二維碼加群主微信,申請入群,務必註明「姓名+公司+職位」
圖片描述