摘要:另外,內置的函數在經過一系列校驗后,觸發,之后被更改,之后依次調用監聽,完成整個狀態樹的更新。總而言之,遵守這套規范并不是強制性的,但是項目一旦稍微復雜一些,這樣做的好處就可以充分彰顯出來。
這一篇是接上一篇“react進階漫談”的第二篇,這一篇主要分析redux的思想和應用,同樣參考了網絡上的大量資料,但代碼同樣都是自己嘗試實踐所得,在這里分享出來,僅供一起學習(上一篇地址:個人博客/segmentFault)
注:本文中的所有示例代碼,已經合成一個小的demo放在了這里,如果你認為這個demo對你的學習起到了一點幫助,請給star以支持。
redux 簡介本文默認大家掌握一些react和flux架構的相關知識,也用過或者了解過redux,所以并不會從最基礎的講起,而是直接對redux進行總結。如果沒有用過redux,最好可以先看這里
想要理解redux,我們首先要總結redux的一些設計原則:
單一數據源
Redux中只有用單一個對象大樹結構來的存儲整個應用的狀態,也就是整個應用中會用到的數據,稱之為store(存儲)。store除了存儲的數據,還可以存儲整個應用的狀態(包括router狀態,后文有介紹),所以,通過store,實現一個對整個應用的即時保存功能(建立快照)變為可能,另外這種設計也為服務端渲染提供了可能。
狀態是只讀的
這一點符合flux的設計理念,我們并不能在components里面更改store的狀態(實際上redux會根據reducer生成store),而是只能通過dispatch,觸發action對當前狀態進行迭代,這里我們也并沒有直接修改應用的狀態,而是返回了一份全新的狀態。
狀態修改均由純函數構成
Redux中的reducer的原型會長得像下面這樣,你可以把它當作就是 之前的狀態 + 動作 = 新的狀態 的公式:
(previousState, action) => newState
每一個reducer都是純函數,這意味著它沒有任何副作用,這種設計的好處不僅在于用reducer對狀態修改變的簡單,純粹可以測試,另外,redux可以保存各個返回狀態從而方便地生成時間旅行,跟蹤每一次因為出發action而導致變更的結果。
我們如果在react中使用redux,同時需要react-redux 和 redux。
redux 架構與源碼分析這一部分主要談一點自己的理解,可能有些抽象,也可能不完全正確,可直接跳過。
createStoreredux中核心的方法是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中傳遞參數:
另外如果參數是可選的,我們通過括號包起來即可(:可選參數)。
支持多種路由切換方式:我們知道現在的路由切換方式無外乎使用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-router和react-router-redux,當然也用到了redux的一些別的比較好的工作,比如redux-devtools,有興趣的朋友可以點擊這里
redux 與組件這一部分講述的是一種組件書寫規范,并不是一些庫或者架構,這些規范有利于我們在復雜的項目中組織頁面,不至于混亂。
從布局的角度看,redux強調了三種不同的布局組件,Layouts,Views,Components:
Layouts: 指的是頁面布局組件,描述了頁面的基本結構,可以是無狀態函數,一般就直接設置在最外層router的component參數中,并不承擔和redux直接交互的功能。比如我項目中的Layouts組件:
const Frame = (props) =>;{props.children}
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。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/81580.html
摘要:另外,內置的函數在經過一系列校驗后,觸發,之后被更改,之后依次調用監聽,完成整個狀態樹的更新。總而言之,遵守這套規范并不是強制性的,但是項目一旦稍微復雜一些,這樣做的好處就可以充分彰顯出來。 這一篇是接上一篇react進階漫談的第二篇,這一篇主要分析redux的思想和應用,同樣參考了網絡上的大量資料,但代碼同樣都是自己嘗試實踐所得,在這里分享出來,僅供一起學習(上一篇地址:個人博客/s...
摘要:前端每周清單半年盤點之與篇前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點分為新聞熱點開發教程工程實踐深度閱讀開源項目巔峰人生等欄目。與求同存異近日,宣布將的構建工具由遷移到,引發了很多開發者的討論。 前端每周清單半年盤點之 React 與 ReactNative 篇 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發者了解一周前端熱點;分為...
摘要:總結本文分析了在采用架構下的數據設計結構,在一個復雜的場景下,希望引起讀者對能有一個更深入的認識。 前幾天刷Twitter,發現Nicolas(Engineering at @twitter. Technical Lead for Twitter Lite)發布了這么一條推文: showImg(https://segmentfault.com/img/remote/1460000009...
摘要:作者小滬江前端開發工程師本文為原創文章,有不當之處歡迎指出。于是,單一數據源規則實施起來,是規定用的頂層容器組件的來存儲單一對象樹,同時交給來管理。顧名思義,當更新時,的回調函數會更新視圖層,以達到訂閱的效果。 作者:小boy (滬江web前端開發工程師)本文為原創文章,有不當之處歡迎指出。轉載請注明出處。文章示例代碼:https://github.com/ikcamp/rea... ...
閱讀 725·2021-11-17 09:33
閱讀 3757·2021-09-01 10:46
閱讀 1751·2019-08-30 11:02
閱讀 3280·2019-08-29 15:05
閱讀 1396·2019-08-26 11:39
閱讀 2272·2019-08-23 17:04
閱讀 1973·2019-08-23 15:43
閱讀 1371·2019-08-23 14:12