Redux 最佳實踐[譯]

摘要

Redux 是 其餘 flux 框架 推薦使用的 React 框架。當我開始寫這篇文章時,它仍是 1.0.0 版本,當這篇文章發佈時,它已是 3.0.0 了。javascript

它的做者,Dan Abramov 已經發布一些很棒的 文檔,可是他依然沒有徹底指明如何在大規模項目中使用 Redux,因此人們開始問了 「有哪些大型項目使用了 Redux」. 好吧,但願這篇文章能夠解決這些疑惑。html

咱們將會討論:java

  • Redux 的全部技術棧react

  • Redux 的各個模塊都作了什麼ios

  • 如何劃分 Redux 項目結構git

  • 如何處理 WebSocket 的異步數據github

正文

我應該有哪些知識儲備?

閱讀 Redux 官方文檔。npm

閱讀了 Dan 的文章 Smart & Dumb Componentsredux

開發 Redux 項目須要使用哪些工具?

Redux 不只僅是 Redux, 它是一堆相關東西的集合,其中有些已經發布到 v1.0.0,有些還在醞釀中。axios

你的工具包可能包含下面的絕大多數:

  • Webpack

使用它打包你的文件,而不是使用 Browserify、Require 或者任何你掙扎使用的工具。爲何?由於一部分 Redux 初始化示例 展現了它的熱加載能力,並且這些示例是使用 Webpack 構建的。

點擊保存就能夠直接看到樣式的更新是如此方便,以致於我都不想再使用保存->刷新頁面->跳轉到指定頁面這樣繁瑣重複的方式了。

  • Babel

一部分緣由是它讓你可使用 ES6/7 的語法糖, 另外一部份緣由是熱加載如今是做爲一個 Babel plugin 實現的。

  • React

雖然在這篇文章發佈時 0.13 版是穩定版本,但咱們依然期待 0.14 版, 由於它能修復一些上下文相關的問題。

由於 ES6 提供了 classes 機制,因此 React 也 棄用 Mixins了。如今應該使用 高階組件 來代替--把你的 React 組件包裹在一個提供 上下文 的父元素中。Redux 充分利用了這一點。

  • Redux

這個沒什麼好說的。

  • React-Redux

嚴格來講,它與 Redux 無關,它是爲 React 編寫的,提供了把 React 組件和 Redux Store 鏈接在一塊兒的高階組件。

  • Middleware

你有兩個選擇:thunks 或者別的 promise 庫。不管哪一個選擇,它都能讓你在 action creatores 中運行異步代碼成爲可能。

  • Request Library

這正是上述異步代碼的緣由。我使用 Axios,它基於 promise,所以能夠和 promise 中間件很好的兼容。

  • React-Router(-redux)

表面上看,使用路由就是更新導航欄並顯示對應的應用頁面。然而更底層的緣由是它提供了一邏輯機制去拆分你的代碼。

路由帶來的問題是,它給了你更多的 state,而這些 state 卻不屬於你的 store。Redux-Router 能夠確保你的 state 被 Redux 管理。

如何使用 Redux 的不一樣部分?

咱們都知道 Flux 是一個單向數據流框架,但即便這樣,咱們如何使用它?

在應用中你須要:

  • 獲取應用的初始狀態

  • 根據狀態繪製內容

  • 處理 UI 交互

  • 處理 request 而且保持 state 與 store的同步

  • 更新和重繪內容

在一個不太規範的框架中,你能夠隨意放置內容,可能在一個活着兩個不一樣的地方作了上述全部事情。

我按照如下的標準組織個人代碼:

使用路由來確保你的組件擁有正確的數據

這是一個很好的方式,由於它劃分了數據集合。使用 Route 中的 onEnter 方法 來指定須要渲染的東西。你沒必要讓這個方法等到數據集合加載完畢,由於。。。

使用智能組件來確保你的木偶組件能夠渲染

你的智能組件應該是配置在 Route 中的組件,你的智能組件的 render 方法控制子組件的渲染數據:

render () {
  if (this.hasData()) {
    return this.renderComponents();
  } else {
    return this.renderLoadingScreen();
  }
}

智能組件儘量的作數據預處理,以使你的木偶組件足夠 「木偶」

好比說,當你傳遞一個處理句柄給木偶組件時,帶上它須要的 id,這樣
木偶組件就不須要本身獲取 id 了:

renderComponents () {
  return <DumbComponent 
    onSelect={this.itemSelected.bind(this, this.props.item.id)}
  >;
}

使用木偶組件去渲染全部東西

不要放哪怕一個 <div> 到你的智能組件中,任什麼時候候,智能組件都應該僅僅是木偶組件的組合。拆分你的關注點,不要在這裏寫一點東西。

使用智能組件調用 actions creators

當一個木偶組件和用戶有交互時,它本身不該該處理任何邏輯--它應該僅僅調用從智能組件中傳過來的處理函數,而後由這個函數去處理。

而後智能組件採集必要的數據傳遞給 action creator。

在 ActionCreators 中轉換應用數據結構到 API 數據結構

你的 ActionCreators 負責在應用數據結構和 API 數據結構間轉換。這個操做是雙向的--發起請求,處理返回值。

由於 action 的輸出會被 reducer 處理,而 reducer 並不知道本身是被怎樣調用的,你可能發現有時候你不能僅僅返回 API 的調用結果--你須要補充它的附加字段,好比:若是你的 action 是 PROJECT_UPDATE,你須要返回新的項目名和 id,而 API 僅僅返回 {savedAt: "<some date>"},你就須要這樣傳遞參數:

function updateProject(projectId, projectName) {
  request.put(`/project/${projectId}`, {projectName}).then(
    response => Object.assign(
      {projectId, projectName}, 
      response.data
    )
  );
}

使用 reducers 同步你的 state

有趣的是,一個 reducer 能夠處理任何的 action。一個數據清理的場景是,當用戶註銷時,清理 store 中的全部數據:

switch (action.type) {
   ...
   case USER_LOGOUT: 
     return {}
}

文件結構

如何組織文件結構是件複雜的事,由於它比處理一成不變的東西多了不少藝術性和我的風格。

我找到了 Redux 應用中的兩個分離點,而後我圍繞這兩個分離點組織文件結構。

一個分離點是 數據。你的 actions 能夠在任何地方被調用(雖然一般都是被智能組件調用)。你的 reducers 和 actions 是綁定的。actions 能夠組合在一塊兒,根據模塊構建你的應用:可能一部分是處理用戶登陸和權限,另外一部分是用戶管理的項目。全部這些都有建立、查詢、更新和刪除,而這些都應該放在一塊兒。

另外一個分離點是 視圖。根據視圖你就能夠佈局你的應用--不一樣頁面的路由,聚合數據和交互的智能組件,渲染數據的木偶組件。

多個視圖能夠調用同一個 action。好比,項目列表頁面可讓你簡單的編輯項目名,而項目詳情頁面能夠提供一個編輯項目名的表單。而這二者都有不一樣分離的路由,不一樣的智能組件,不一樣的木偶組件和不一樣的數據集。

因此,我這樣組織個人項目文件:

public/
  index.html
  client/
    index.js
    modules/
      reducers.js
      users/
        constants.js
        actions/
          user_fetch.js
          user_login.js
          permissions_fetch.js
        reducers/
          index.js
          user.js
          permission.js
      projects/
    routes/
      login/
        index.js
        containers/
          login.js
        components/
          login.js
      logged_in/
      project_list/
      project_view/

modules 目錄負責處理和數據相關的文件,不一樣模塊的數據處理經過子目錄的方式劃分。這使得您將來能夠把這些模塊單獨打包到你的 npm 倉庫,它們之間沒有依賴。

每一個 action 和 reducer 都有本身單獨的文件。有的項目 試圖把一個模塊中的全部內容都放倒一個文件中。我我的反對在中大型項目中採用這種作法,當項目愈來愈大時,應該把東西拆成儘量小的塊。

爲了使不一樣的模塊的 reducers 保持類似的結構,增長了 index.js 文件,它導出了該目錄中的全部 reducer,而後頂層的 reducers.js 引入全部模塊的 reducers。這些單獨的 reducers 文件都會用於生成 Redux store。

routes 目錄負責管理全部視圖相關的文件,按不一樣的路由劃分子目錄。每一個 route 目錄包涵三個部分:

  • 在 containers 目錄中的智能組件

  • 在 components 目錄中的木偶組件

  • 包含 Route 的 index.js 文件

一樣的,隨着路徑層級變深,會分解成更多的小組件。我推薦這種方式,由於它容許你僅僅在須要的時候實例化這些路由。並且意味着你的路由僅僅包含子其子目錄中的文件,這樣感受很好而且解偶了。

經過使用 onEnter 和 onLeave 方法,你的路由文件一樣能夠做爲數據的關卡。在這裏你能夠觸發 fetch action 來獲取組件須要的數據。這在你使用深層路由嵌套的時候頗有用,好比,給定路由 /app/project/10/permission,你能夠:

  • /app 中獲取當前用戶的登陸信息

  • /project 中獲取該用戶可見的項目

  • /10 中獲取項目 10 的詳細信息

  • /permission 中獲取該用戶的權限列表

當切換到另一個路由 /app/project/11,你僅僅須要獲取更改的數據(/11 對應的數據),這時你就只須要一次對項目 11 的請求了:

import Projects from "./containers/projects";
import ProjectDetailRoute from "routes/project_detail";
export default class ProjectList {
  constructor () {
    this.path = "project";
    this.projectDetailRoute = new ProjectDetailRoute();
  }
  getChildRoutes (state, cb) {
    cb(null, [this.projectDetailRoute]);
  }
  getComponents (cb) {
    cb(null, ProjectTasks);
  }
  onEnter () {
    this.fetchProjects();
  }
  fetchProjects () {
    ...
  }
}

如何命名

Actions: <名詞>-<動詞>,好比 Project-Create,User-Login。依據是按照對象類型而不是動做類型分組。

Reducers: <名詞>。

如何處理第三方異步數據

很明顯的這裏有條正確的流程(Action->Reducer->SmartContainer->DumbComponent)。但如何讓你的更改符合這個流程?

第三方異步數據一般來自於 WebSocket。你可能僅僅想在應用的某些部分監聽它,好比登陸時,或者某些頁面。並且,從 UI 到 actions 的處理流程是,用戶觸發了一個事件,木偶組件把事件傳播到智能組件,而後觸發一個 action。

但在這種狀況下,沒有木偶組件渲染內容,而由路由決定你什麼時候接收數據,action 把數據注入到 redux。這個智能組件不須要任何木偶組件,也應該獨立於其餘智能組件。

React-Route 很好的處理了這個問題:
Route 能夠有多個組件:

getComponents () {
  cb(null, {view: ViewContainer, data: DataContainer};
}

該智能組件能夠這樣渲染:

render () {
  return <div>{this.props.view}{this.props.data}</div>
}

DataContainer 能夠經過 componentDidUpdate 對 props 的更改做出響應,或者根據 componentWillUnmount 關閉鏈接。

總結

我已經連續兩週在寫這篇文章了,由於我總以爲還有些東西須要加進去。故事沒有結束,但我把它發佈出來以使 Redux 新手能夠看到我對 Reactiflux 的探索。請評論和註釋這篇文章,我將在接下來的幾周內持續關注它。

做者信息

原文做者: Will Becker
原文連接: https://medium.com/lexical-labs-engineering/redux-best-practices-64d59775802e#.1b8hgoju1
翻譯自MaxLeap團隊_UX成員:Henry Bai
力譜宿雲團隊首發:https://blog.maxleap.cn/archives/930

歡迎關注微信訂閱號:從移動到雲端歡迎加入咱們的MaxLeap活動QQ羣:555973817,咱們將不按期作技術分享活動。如有轉載須要,請轉發時注意自帶做者信息一欄並本自媒體公號:力譜宿雲,尊重原創做者及譯者的勞動成果~ 謝謝配合~

相關文章
相關標籤/搜索