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