深刻redux技術棧

這一篇是接上一篇「react進階漫談」的第二篇,這一篇主要分析redux的思想和應用,一樣參考了網絡上的大量資料,但代碼一樣都是本身嘗試實踐所得,在這裏分享出來,僅供一塊兒學習(上一篇地址:我的博客segmentFault)html

注:本文中的全部示例代碼,已經合成一個小的demo放在了這裏,若是你認爲這個demo對你的學習起到了一點幫助,請給star以支持。前端

redux 簡介

本文默認你們掌握一些react和flux架構的相關知識,也用過或者瞭解過redux,因此並不會從最基礎的講起,而是直接對redux進行總結。若是沒有用過redux,最好能夠先看這裏node

想要理解redux,咱們首先要總結redux的一些設計原則:react

  • 單一數據源git

Redux中只有用單一個對象大樹結構來的存儲整個應用的狀態,也就是整個應用中會用到的數據,稱之爲store(存儲)。store除了存儲的數據,還能夠存儲整個應用的狀態(包括router狀態,後文有介紹),因此,經過store,實現一個對整個應用的即時保存功能(創建快照)變爲可能,另外這種設計也爲服務端渲染提供了可能。es6

  • 狀態是隻讀的github

這一點符合flux的設計理念,咱們並不能在components裏面更改store的狀態(實際上redux會根據reducer生成store),而是隻能經過dispatch,觸發action對當前狀態進行迭代,這裏咱們也並無直接修改應用的狀態,而是返回了一份全新的狀態。redux

  • 狀態修改均由純函數構成segmentfault

Redux中的reducer的原型會長得像下面這樣,你能夠把它看成就是 以前的狀態 + 動做 = 新的狀態 的公式:瀏覽器

(previousState, action) => newState

每個reducer都是純函數,這意味着它沒有任何反作用,這種設計的好處不只在於用reducer對狀態修改變的簡單,純粹能夠測試,另外,redux能夠保存各個返回狀態從而方便地生成時間旅行,跟蹤每一次由於出發action而致使變動的結果。

咱們若是在react中使用redux,同時須要react-redux 和 redux。

redux 架構與源碼分析

這一部分主要談一點本身的理解,可能有些抽象,也可能不徹底正確,可直接跳過。

createStore

redux中核心的方法是createStore,react的核心功能全都覆蓋在createStore和其最終生成的store中,createStore方法自己支持傳入reducer、initialState、enhancer三參數,enhancer能夠做爲加強的包裝函數,這個咱們並非十分經常使用。

這個函數內部維護了一個currentState,而且這個currentState能夠經過getState函數(內置)返回,另外自己其實是實現了一個發佈-訂閱模式,經過store.subscribe來訂閱事件,這個工做由react-redux來幫助咱們隱式完成,這是爲了在有dispatch的時候觸發全部監遵從而更新整個狀態樹。另外,內置的dispatch函數在通過一系列校驗後,觸發reducer,以後state被更改,以後依次調用監聽,完成整個狀態樹的更新。

middleWare

用過redux的朋友實際上都對於redux-thunk等中間件並不陌生,實際上不少時候這是不可缺乏的,redux對middleWare也有很好的支持,這種理念我認爲和nodejs的中間件機制有些相似:action依次通過各個middleWare而後傳給下一個,每個middleWare也能夠進行另外的操做好比中斷或者改變action,知道最終的處理函數交給reducer。

redux的applyMiddleware函數很是精煉:

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    var store = createStore(reducer, preloadedState, enhancer)
    var dispatch = store.dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
      //注意這裏的dispatch並非一開始的store.dispatch,其實是變化了的
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

核心是dispatch = compose(...chain)(store.dispatch),這句話是對於各個中間件的鏈式調用,其中compose的源代碼:

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
}

調用上一個函數的執行結果給下一個函數。

實際上咱們要寫一個middleware的過程也很是簡單,好比redux-trunk實際上就這點內容:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

redux 與路由

固然,咱們首先聲明react工具集的react-router並不必定必須搭配redux使用,只是redux另外有一個react-router-redux能夠搭配react-router以及redux使用,效果很是好。

由於咱們這部分並非介紹react-router怎麼使用的,關於react-router的用法請參考中文文檔

react-router的特性

  • 容許開發者經過JSX標籤來聲明路由,這一點讓咱們路由寫起來十分友好,而且聲明式路由的表述能力比較強。

  • 嵌套路由以及路由匹配:能夠在指定的path中傳遞參數:

<Route path="/mail/:mailId" component={Mail} />

另外若是參數是可選的,咱們經過括號包起來便可(:可選參數)。

  • 支持多種路由切換方式:咱們知道如今的路由切換方式無外乎使用hashchange和pushState,前者有比較好的瀏覽器兼容性,可是卻並不像一個真正的url,然後者給咱們提供優雅的url體驗,可是卻須要服務端解決任意路徑刷新的問題(服務端要自動重定向到首頁)。

爲何須要react-router-redux

簡單的說,react-router-redux讓咱們能夠把路由也看成狀態的一部分,而且可使用redux的方式改變路由:直接調用dispatch:this.props.push(「/detail/」);,這樣把路由也看成一個全局狀態,路由狀態也是應用狀態的一部分,這樣可能更有利於前端狀態管理。

react-router-redux是須要配合react-router來使用的,並不能單獨使用,在本來的項目中添加上react-router-redux也不復雜:

import { createStore, combineReducers, compose, applyMiddleware } from 'redux';
import { routerReducer, routerMiddleware } from 'react-router-redux';
import { hashHistory } from 'react-router';

import ThunkMiddleware from 'redux-thunk';
import rootReducer from './reducers';
import DevTools from './DevTools';

const finalCreateStore = compose(
  applyMiddleware(ThunkMiddleware,routerMiddleware(hashHistory)),
  DevTools.instrument()
)(createStore);

console.log("rootReducer",rootReducer);

const reducer = combineReducers({
  rootReducer,
  routing: routerReducer,
});

export default function configureStore(initialState) {
  const store = finalCreateStore(reducer, initialState);
  return store;
}

另外,上文提到的demoreact-router-redux-demo用了react-routerreact-router-redux,固然也用到了redux的一些別的比較好的工做,好比redux-devtools,有興趣的朋友能夠點擊這裏

redux 與組件

這一部分講述的是一種組件書寫規範,並非一些庫或者架構,這些規範有利於咱們在複雜的項目中組織頁面,不至於混亂。

從佈局的角度看,redux強調了三種不一樣的佈局組件,Layouts,Views,Components:

  • Layouts: 指的是頁面佈局組件,描述了頁面的基本結構,能夠是無狀態函數,通常就直接設置在最外層router的component參數中,並不承擔和redux直接交互的功能。好比我項目中的Layouts組件:

const Frame = (props) =>
       <div className="frame">
           <div className="header">
               <Nav />
           </div>
           <div className="container">
               {props.children}
           </div>
       </div>;
  • Views組件,我認爲這個組件是Components的高階組件或者Components group,這一層是能夠和redux進行交互而且處理數據的,咱們能夠將一個總體性功能的組件組放在一個Views下面(注:因爲我給出的demo十分簡單,所以Views層和Components層分的不是那麼開)

  • Components組件,這是末級渲染組件,通常來講,這一層級的組件的數據經過props傳入,不直接和redux單向數據流產生交互,能夠是木偶般的無狀態組件,或者是包含自身少許交互和狀態的組件,這一層級的組件能夠被大量複用。

總而言之,遵照這套規範並非強制性的,可是項目一旦稍微複雜一些,這樣作的好處就能夠充分彰顯出來。

redux 與表單

redux的單向數據流相對於雙向數據綁定,在處理表單等問題上的確有點力不從心,可是幸運的是已經開源了有幾個比較不錯的插件:

  • redux-form-utils,好吧,這個插件的star數目很是少,可是他比較簡單,源代碼也比較短,只有200多行,因此這是一個值得咱們看源碼學習的插件(它的源碼結構也很是簡單,就是先定一個一個高階組件,這個高階組件能夠給咱們本身定義的表單組件傳入新的props,定製組件,後一部分就是定義了一些action和reducer,負責在內容變化的時候通知改變狀態樹),可是缺憾就是這個插件沒有對錶單驗證作工做,因此若是咱們須要表單驗證,仍是須要本身作一些工做的。

    • 另外還有一地方,這個插件源代碼寫法中用到了::這種ES6的語法,這實際上是一種在es6中class內部,使用babel-preset-stage-0便可使用的語法糖:::this.[functionName] 等價於 this.[functionName].bind(this, args?)

  • redux-form,這個插件功能複雜,代碼完善,體量也很是龐大,能夠參考文檔進行使用,可是讀懂源代碼就是比較麻煩的事情了。不過這個插件須要在redux的應用的state下掛載一個節點,這個節點是不須要開發者本身來操控的,他惟一須要作的事情就是寫一個submit函數便可。我在本身的demo中也把一個例子稍加改動搬了過來,感受用起來比較舒服。

redux 性能優化

想要作到redux性能優化,咱們首先就要知道redux的性能可能會在哪些地方受到影響,不然沒有目標是沒有辦法性能優化的。

由於我也不是使用redux的老手,因此也並不能覆蓋全部性能優化的點,我總結兩點:

  • 有的時候,咱們須要的數據格式會自帶冗餘,能夠抽取出一些公共的部分從而縮減大小,好比咱們須要的數據格式多是這樣的:

[
    {
        name:"Nike",
        title:"國家一級運動員","國家一級裁判員"
    }
    {
        name:"Jenny",
        title:"國家一級裁判員"
    }
    {
        name:"Mark",
        title:"國家一級運動員"
    }
]

這個時候實際上咱們能夠優化成這樣:

[
    {
    "國家一級運動員":"Nike","Mark"
    "國家一級裁判員":"Jenny","Nike"
    }
]

這個時候,咱們能夠直接把後者看成store的格式,而咱們用reselect這個庫再轉變成咱們所要的格式,關於reselect怎麼用上述連接有更詳細的例子,在這裏我就不過多介紹了。

  • 事實上,對於redux來講,每當store發生改變的時候,全部的connect都會從新計算,在一個大型應用中,浪費的時間可想而知,爲了減小性能浪費,咱們能夠對connect中的selector作緩存。

上文提到的reselect庫自帶了緩存特性,咱們能夠經過比較參數來肯定是否使用緩存,這裏用了純函數的特性。

reselect的緩存函數能夠用戶自定義,具體能夠參考上文github連接的readme。

相關文章
相關標籤/搜索