這一篇是接上一篇「react進階漫談」的第二篇,這一篇主要分析redux的思想和應用,一樣參考了網絡上的大量資料,但代碼一樣都是本身嘗試實踐所得,在這裏分享出來,僅供一塊兒學習(上一篇地址:我的博客/segmentFault)html
注:本文中的全部示例代碼,已經合成一個小的demo放在了這裏,若是你認爲這個demo對你的學習起到了一點幫助,請給star以支持。前端
本文默認你們掌握一些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,react的核心功能全都覆蓋在createStore和其最終生成的store中,createStore方法自己支持傳入reducer、initialState、enhancer三參數,enhancer能夠做爲加強的包裝函數,這個咱們並非十分經常使用。
這個函數內部維護了一個currentState,而且這個currentState能夠經過getState函數(內置)返回,另外自己其實是實現了一個發佈-訂閱模式,經過store.subscribe來訂閱事件,這個工做由react-redux
來幫助咱們隱式完成,這是爲了在有dispatch的時候觸發全部監遵從而更新整個狀態樹。另外,內置的dispatch函數在通過一系列校驗後,觸發reducer,以後state被更改,以後依次調用監聽,完成整個狀態樹的更新。
用過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;
固然,咱們首先聲明react工具集的react-router
並不必定必須搭配redux使用,只是redux另外有一個react-router-redux
能夠搭配react-router
以及redux使用,效果很是好。
由於咱們這部分並非介紹react-router
怎麼使用的,關於react-router
的用法請參考中文文檔。
容許開發者經過JSX標籤來聲明路由,這一點讓咱們路由寫起來十分友好,而且聲明式路由的表述能力比較強。
嵌套路由以及路由匹配:能夠在指定的path中傳遞參數:
<Route path="/mail/:mailId" component={Mail} />
另外若是參數是可選的,咱們經過括號包起來便可(:可選參數)。
支持多種路由切換方式:咱們知道如今的路由切換方式無外乎使用hashchange和pushState,前者有比較好的瀏覽器兼容性,可是卻並不像一個真正的url,然後者給咱們提供優雅的url體驗,可是卻須要服務端解決任意路徑刷新的問題(服務端要自動重定向到首頁)。
簡單的說,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-router
和react-router-redux
,固然也用到了redux的一些別的比較好的工做,好比redux-devtools
,有興趣的朋友能夠點擊這裏
這一部分講述的是一種組件書寫規範,並非一些庫或者架構,這些規範有利於咱們在複雜的項目中組織頁面,不至於混亂。
從佈局的角度看,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-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的老手,因此也並不能覆蓋全部性能優化的點,我總結兩點:
有的時候,咱們須要的數據格式會自帶冗餘,能夠抽取出一些公共的部分從而縮減大小,好比咱們須要的數據格式多是這樣的:
[ { 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。