摘要:另外,內置的函數(shù)在經(jīng)過一系列校驗后,觸發(fā),之后被更改,之后依次調用監(jiān)聽,完成整個狀態(tài)樹的更新。總而言之,遵守這套規(guī)范并不是強制性的,但是項目一旦稍微復雜一些,這樣做的好處就可以充分彰顯出來。
這一篇是接上一篇“react進階漫談”的第二篇,這一篇主要分析redux的思想和應用,同樣參考了網(wǎng)絡上的大量資料,但代碼同樣都是自己嘗試實踐所得,在這里分享出來,僅供一起學習(上一篇地址:個人博客/segmentFault)
注:本文中的所有示例代碼,已經(jīng)合成一個小的demo放在了這里,如果你認為這個demo對你的學習起到了一點幫助,請給star以支持。
redux 簡介本文默認大家掌握一些react和flux架構的相關知識,也用過或者了解過redux,所以并不會從最基礎的講起,而是直接對redux進行總結。如果沒有用過redux,最好可以先看這里
想要理解redux,我們首先要總結redux的一些設計原則:
單一數(shù)據(jù)源
Redux中只有用單一個對象大樹結構來的存儲整個應用的狀態(tài),也就是整個應用中會用到的數(shù)據(jù),稱之為store(存儲)。store除了存儲的數(shù)據(jù),還可以存儲整個應用的狀態(tài)(包括router狀態(tài),后文有介紹),所以,通過store,實現(xiàn)一個對整個應用的即時保存功能(建立快照)變?yōu)榭赡?,另外這種設計也為服務端渲染提供了可能。
狀態(tài)是只讀的
這一點符合flux的設計理念,我們并不能在components里面更改store的狀態(tài)(實際上redux會根據(jù)reducer生成store),而是只能通過dispatch,觸發(fā)action對當前狀態(tài)進行迭代,這里我們也并沒有直接修改應用的狀態(tài),而是返回了一份全新的狀態(tài)。
狀態(tài)修改均由純函數(shù)構成
Redux中的reducer的原型會長得像下面這樣,你可以把它當作就是 之前的狀態(tài) + 動作 = 新的狀態(tài) 的公式:
(previousState, action) => newState
每一個reducer都是純函數(shù),這意味著它沒有任何副作用,這種設計的好處不僅在于用reducer對狀態(tài)修改變的簡單,純粹可以測試,另外,redux可以保存各個返回狀態(tài)從而方便地生成時間旅行,跟蹤每一次因為出發(fā)action而導致變更的結果。
我們如果在react中使用redux,同時需要react-redux 和 redux。
redux 架構與源碼分析這一部分主要談一點自己的理解,可能有些抽象,也可能不完全正確,可直接跳過。
createStoreredux中核心的方法是createStore,react的核心功能全都覆蓋在createStore和其最終生成的store中,createStore方法本身支持傳入reducer、initialState、enhancer三參數(shù),enhancer可以作為增強的包裝函數(shù),這個我們并不是十分常用。
這個函數(shù)內部維護了一個currentState,并且這個currentState可以通過getState函數(shù)(內置)返回,另外本身實際上是實現(xiàn)了一個發(fā)布-訂閱模式,通過store.subscribe來訂閱事件,這個工作由react-redux來幫助我們隱式完成,這是為了在有dispatch的時候觸發(fā)所有監(jiān)聽從而更新整個狀態(tài)樹。另外,內置的dispatch函數(shù)在經(jīng)過一系列校驗后,觸發(fā)reducer,之后state被更改,之后依次調用監(jiān)聽,完成整個狀態(tài)樹的更新。
middleWare用過redux的朋友實際上都對于redux-thunk等中間件并不陌生,實際上很多時候這是不可缺少的,redux對middleWare也有很好的支持,這種理念我認為和nodejs的中間件機制有些類似:action依次經(jīng)過各個middleWare然后傳給下一個,每一個middleWare也可以進行另外的操作比如中斷或者改變action,知道最終的處理函數(shù)交給reducer。
redux的applyMiddleware函數(shù)非常精煉:
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)) }
調用上一個函數(shù)的執(zhí)行結果給下一個函數(shù)。
實際上我們要寫一個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的特性允許開發(fā)者通過JSX標簽來聲明路由,這一點讓我們路由寫起來十分友好,并且聲明式路由的表述能力比較強。
嵌套路由以及路由匹配:可以在指定的path中傳遞參數(shù):
另外如果參數(shù)是可選的,我們通過括號包起來即可(:可選參數(shù))。
支持多種路由切換方式:我們知道現(xiàn)在的路由切換方式無外乎使用hashchange和pushState,前者有比較好的瀏覽器兼容性,但是卻并不像一個真正的url,而后者給我們提供優(yōu)雅的url體驗,但是卻需要服務端解決任意路徑刷新的問題(服務端要自動重定向到首頁)。
為什么需要react-router-redux簡單的說,react-router-redux讓我們可以把路由也當作狀態(tài)的一部分,并且可以使用redux的方式改變路由:直接調用dispatch:this.props.push(“/detail/”);,這樣把路由也當作一個全局狀態(tài),路由狀態(tài)也是應用狀態(tài)的一部分,這樣可能更有利于前端狀態(tài)管理。
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 與組件這一部分講述的是一種組件書寫規(guī)范,并不是一些庫或者架構,這些規(guī)范有利于我們在復雜的項目中組織頁面,不至于混亂。
從布局的角度看,redux強調了三種不同的布局組件,Layouts,Views,Components:
Layouts: 指的是頁面布局組件,描述了頁面的基本結構,可以是無狀態(tài)函數(shù),一般就直接設置在最外層router的component參數(shù)中,并不承擔和redux直接交互的功能。比如我項目中的Layouts組件:
const Frame = (props) =>;{props.children}
Views組件,我認為這個組件是Components的高階組件或者Components group,這一層是可以和redux進行交互并且處理數(shù)據(jù)的,我們可以將一個整體性功能的組件組放在一個Views下面(注:由于我給出的demo十分簡單,因此Views層和Components層分的不是那么開)
Components組件,這是末級渲染組件,一般來說,這一層級的組件的數(shù)據(jù)通過props傳入,不直接和redux單向數(shù)據(jù)流產(chǎn)生交互,可以是木偶般的無狀態(tài)組件,或者是包含自身少量交互和狀態(tài)的組件,這一層級的組件可以被大量復用。
總而言之,遵守這套規(guī)范并不是強制性的,但是項目一旦稍微復雜一些,這樣做的好處就可以充分彰顯出來。
redux 與表單redux的單向數(shù)據(jù)流相對于雙向數(shù)據(jù)綁定,在處理表單等問題上的確有點力不從心,但是幸運的是已經(jīng)開源了有幾個比較不錯的插件:
redux-form-utils,好吧,這個插件的star數(shù)目非常少,但是他比較簡單,源代碼也比較短,只有200多行,所以這是一個值得我們看源碼學習的插件(它的源碼結構也非常簡單,就是先定一個一個高階組件,這個高階組件可以給我們自己定義的表單組件傳入新的props,定制組件,后一部分就是定義了一些action和reducer,負責在內容變化的時候通知改變狀態(tài)樹),但是缺憾就是這個插件沒有對表單驗證做工作,所以如果我們需要表單驗證,還是需要自己做一些工作的。
另外還有一地方,這個插件源代碼寫法中用到了::這種ES6的語法,這其實是一種在es6中class內部,使用babel-preset-stage-0即可使用的語法糖:::this.[functionName] 等價于 this.[functionName].bind(this, args?)
redux-form,這個插件功能復雜,代碼完善,體量也非常龐大,可以參考文檔進行使用,但是讀懂源代碼就是比較麻煩的事情了。不過這個插件需要在redux的應用的state下掛載一個節(jié)點,這個節(jié)點是不需要開發(fā)者自己來操控的,他唯一需要做的事情就是寫一個submit函數(shù)即可。我在自己的demo中也把一個例子稍加改動搬了過來,感覺用起來比較舒服。
redux 性能優(yōu)化想要做到redux性能優(yōu)化,我們首先就要知道redux的性能可能會在哪些地方受到影響,否則沒有目標是沒有辦法性能優(yōu)化的。
因為我也不是使用redux的老手,所以也并不能覆蓋所有性能優(yōu)化的點,我總結兩點:
有的時候,我們需要的數(shù)據(jù)格式會自帶冗余,可以抽取出一些公共的部分從而縮減大小,比如我們需要的數(shù)據(jù)格式可能是這樣的:
[ { name:"Nike", title:"國家一級運動員","國家一級裁判員" } { name:"Jenny", title:"國家一級裁判員" } { name:"Mark", title:"國家一級運動員" } ]
這個時候實際上我們可以優(yōu)化成這樣:
[ { "國家一級運動員":"Nike","Mark" "國家一級裁判員":"Jenny","Nike" } ]
這個時候,我們可以直接把后者當作store的格式,而我們用reselect這個庫再轉變成我們所要的格式,關于reselect怎么用上述鏈接有更詳細的例子,在這里我就不過多介紹了。
事實上,對于redux來說,每當store發(fā)生改變的時候,所有的connect都會重新計算,在一個大型應用中,浪費的時間可想而知,為了減少性能浪費,我們可以對connect中的selector做緩存。
上文提到的reselect庫自帶了緩存特性,我們可以通過比較參數(shù)來確定是否使用緩存,這里用了純函數(shù)的特性。
reselect的緩存函數(shù)可以用戶自定義,具體可以參考上文github鏈接的readme。
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://www.ezyhdfw.cn/yun/50436.html
摘要:另外,內置的函數(shù)在經(jīng)過一系列校驗后,觸發(fā),之后被更改,之后依次調用監(jiān)聽,完成整個狀態(tài)樹的更新。總而言之,遵守這套規(guī)范并不是強制性的,但是項目一旦稍微復雜一些,這樣做的好處就可以充分彰顯出來。 這一篇是接上一篇react進階漫談的第二篇,這一篇主要分析redux的思想和應用,同樣參考了網(wǎng)絡上的大量資料,但代碼同樣都是自己嘗試實踐所得,在這里分享出來,僅供一起學習(上一篇地址:個人博客/s...
摘要:前端每周清單半年盤點之與篇前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點分為新聞熱點開發(fā)教程工程實踐深度閱讀開源項目巔峰人生等欄目。與求同存異近日,宣布將的構建工具由遷移到,引發(fā)了很多開發(fā)者的討論。 前端每周清單半年盤點之 React 與 ReactNative 篇 前端每周清單專注前端領域內容,以對外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點;分為...
摘要:總結本文分析了在采用架構下的數(shù)據(jù)設計結構,在一個復雜的場景下,希望引起讀者對能有一個更深入的認識。 前幾天刷Twitter,發(fā)現(xiàn)Nicolas(Engineering at @twitter. Technical Lead for Twitter Lite)發(fā)布了這么一條推文: showImg(https://segmentfault.com/img/remote/1460000009...
摘要:作者小滬江前端開發(fā)工程師本文為原創(chuàng)文章,有不當之處歡迎指出。于是,單一數(shù)據(jù)源規(guī)則實施起來,是規(guī)定用的頂層容器組件的來存儲單一對象樹,同時交給來管理。顧名思義,當更新時,的回調函數(shù)會更新視圖層,以達到訂閱的效果。 作者:小boy (滬江web前端開發(fā)工程師)本文為原創(chuàng)文章,有不當之處歡迎指出。轉載請注明出處。文章示例代碼:https://github.com/ikcamp/rea... ...
閱讀 3998·2021-09-23 11:51
閱讀 3134·2021-09-22 15:59
閱讀 1007·2021-09-09 11:37
閱讀 2158·2021-09-08 09:45
閱讀 1341·2019-08-30 15:54
閱讀 2152·2019-08-30 15:53
閱讀 557·2019-08-29 12:12
閱讀 3362·2019-08-29 11:15