摘要:本文轉(zhuǎn)載至今日頭條技術(shù)博客眾所周知,的單向數(shù)據(jù)流模式導(dǎo)致狀態(tài)只能一級(jí)一級(jí)的由父組件傳遞到子組件,在大中型應(yīng)用中較為繁瑣不好管理,通常我們需要使用來幫助我們進(jìn)行管理,然而隨著的發(fā)布,新成為了新的選擇。
本文轉(zhuǎn)載至:今日頭條技術(shù)博客
眾所周知,React的單向數(shù)據(jù)流模式導(dǎo)致狀態(tài)只能一級(jí)一級(jí)的由父組件傳遞到子組件,在大中型應(yīng)用中較為繁瑣不好管理,通常我們需要使用Redux來幫助我們進(jìn)行管理,然而隨著React 16.3的發(fā)布,新context api成為了新的選擇。
一、Redux的簡介以及缺陷
Redux來源于Flux并借鑒了Elm的思想,主要原理如下圖所示:
可以看到,Redux的數(shù)據(jù)流其實(shí)非常簡單,外部事件通過actionCreator函數(shù)調(diào)用dipsatch發(fā)布action到reducers中,然后各自的reducer根據(jù)action的類型(action.type) 來按需更新整個(gè)應(yīng)用的state。
redux設(shè)計(jì)有以下幾個(gè)要點(diǎn):
1.state是單例模式且不可變的,單例模式避免了不同store之間的數(shù)據(jù)交換的復(fù)雜性,而不可變數(shù)據(jù)提供了十分快捷的撤銷重做、“時(shí)光旅行”等功能。
2.state只能通過reducer來更新,不可以直接修改
3.reducer必須是純函數(shù),形如(state,action) => newState
redux本身是個(gè)非常純粹的狀態(tài)管理庫,需要通過react-redux這個(gè)庫的幫助來管理react的狀態(tài)。react-redux主要包含兩個(gè)部分。
1.Provider組件:可以將store注入到子組件的cotext中,所以一般放在應(yīng)用的最頂層。
2.connect函數(shù): 返回一個(gè)高階函數(shù),把context中由Provider注入的store取出來然后通過props傳遞到子組件中,這樣子組件就能順利獲取到store了。
雖然redux在React項(xiàng)目中得到了普遍的認(rèn)可與使用率,然而在現(xiàn)實(shí)項(xiàng)目中redux還是存在著很多缺點(diǎn):
1.樣板代碼過多:增加一個(gè)action往往需要同時(shí)定義相應(yīng)的actionType然后再寫N個(gè)相關(guān)的reducer。例如當(dāng)添加一個(gè)異步加載事件時(shí),需要同時(shí)定義加載中、加載失敗以及加載完成三個(gè)actionType,需要一個(gè)相對(duì)應(yīng)的reducer通過switch分支來處理對(duì)應(yīng)的actionType,冗余代碼過多。
2.更新效率問題:由于使用不可變數(shù)據(jù)模式,每次更新state都需要拷貝一份完整的state造成了內(nèi)存的浪費(fèi)以及性能的損耗。
3.數(shù)據(jù)傳遞效率問題:由于react-redux采用的舊版context API,context的傳遞存在著效率問題。
其中,第一個(gè)問題目前已經(jīng)存在著非常多的解決方案,諸如dva、rematch以及mirror等等,筆者也造過一個(gè)類似的輪子restated這里不做過多闡述。
第二個(gè)問題首先redux以及react-redux中已經(jīng)做了非常詳盡的優(yōu)化了,其次擅用shouldComponentUpdate方法也可以避免很多不必要的更新,最后,也可以使用一些不可變數(shù)據(jù)結(jié)構(gòu)如immutable、Immr等來從根本上解決拷貝開銷問題。
第三個(gè)問題屬于React自身API的局限,從第三方庫的角度上來說,能做的很有限。
二、Context API
context API主要用來解決跨組件傳參泛濫的問題(prop drilling),舊的context API的語法形式如下:
// 傳遞者,生成數(shù)據(jù)并放入context中class DeliverComponent extends Component { getChildContext() { return { color: "purple" }; render() { return} } DeliverComponent.childContextTypes = { color: PropTypes.string };// 中間與context無關(guān)的組件 const MidComponent = (props) => ;// 接收者,需要用到context中的數(shù)據(jù) const ReceiverComponent = (props, context) => Hello, this is receiver.; ReceiverComponent.contextTypes = { color: PropTypes.string }; ReactDOM.render(, document.getElementById("root"));
可以看到,使用context api可以把DeliverComponent中的參數(shù)color直接跨越MidComponent傳遞到ReceiverComponent中,不需要冗余的使用props參數(shù)傳遞,特別是ReceiverComponent層級(jí)特別深的時(shí)候,使用context api能夠很大程度上節(jié)省重復(fù)代碼避免bug。
舊Context API的缺陷
舊的context api主要存在如下的缺陷:
1.代碼冗余:提供context的組件要定義childContextTypes與getChildContext才能把context傳下去。同時(shí)接收context的也要先定義contextTypes才能正確拿到數(shù)據(jù)。
2.傳遞效率:雖然功能上context可以跨層級(jí)傳遞,但是本質(zhì)上context也是同props一樣一層一層的往下傳遞的,當(dāng)層級(jí)過深的時(shí)候還是會(huì)出現(xiàn)效率問題。
3.shouldComponentUpdate:由于context的傳遞也是一層一層傳遞,因此它也會(huì)受到shouldComponent的阻斷。換句話說,當(dāng)傳遞組件的context變化時(shí),如果其下面某一個(gè)中間組件的shouldComponentUpdate方法返回false,那么之后的接收組件將不會(huì)受到任何context變化。
為了解決舊版本的shouldComponentUpdate問題,保證所有的組件都能收到store的變化,react-redux只能傳遞一個(gè)getState方法給各個(gè)組件用于獲取最新的state(直接傳遞state可能會(huì)被阻斷,后面的組件將接收不到state的變化),然后每個(gè)connect組件都需要直接或間接監(jiān)聽state的變化,當(dāng)state發(fā)生改變時(shí),通過內(nèi)部notifyNestedSubs方法從上往下依次觸發(fā)各個(gè)子組件通過getState方法獲取最新的state更新視圖。這種方式效率較低而且比較hack。
三、新Context API
React自16.3開始提供了一個(gè)新的context api,徹底解決了舊Context API存在的種種問題。
下面是新context api(右)與使用舊context api的react-redux(左)數(shù)據(jù)流的比較:
可以看到,新的context api可以直接將context數(shù)據(jù)傳遞到傳遞到子組件中而不需要像舊context api那樣級(jí)聯(lián)傳遞。因此也可以突破shouldComponentUpdate的限制。新版的context api的定義如下:
type Context= { Provider: Provider , Consumer: Consumer , }; interface React { createContext (defaultValue: T): Context ; } type Provider = React.Component<{ value: T, children?: React.Node, }>; type Consumer = React.Component<{ children: (value: T) => React.Node, }>;
下面是一個(gè)比較簡單的應(yīng)用示例:
import React, { Component, createContext } from "react";const DEFAULT_STATE = {color: "red"}; const { Provider, Consumer } = createContext(DEFAULT_STATE);// 傳遞者,生成數(shù)據(jù)并放入context中class DeliverComponent extends Component { state = { color: "purple" }; render() { return () } }// 中間與context無關(guān)的組件const MidComponent = (props) => ; // 接收者,需要用到context中的數(shù)據(jù) const ReceiverComponent = (props) => ( {context => ( ); ReactDOM.render(Hello, this is receiver.)}, document.getElementById("root"));
可以看到新的context api主要包含一個(gè)Provider和Consumer對(duì),在Provider輸入的數(shù)據(jù)可以在Consumer中獲得。 新context api的要點(diǎn)如下:
1.Provider和 Consumer必須來自同一次 React.createContext調(diào)用。也就是說 NameContext.Provider和 AgeContext.Consumer是無法搭配使用的。
2.React.createContext方法接收一個(gè)默認(rèn)值作為參數(shù)。當(dāng) Consumer外層沒有對(duì)應(yīng)的 Provider時(shí)就會(huì)使用該默認(rèn)值。
3.Provider 組件的 valueprop 值發(fā)生變更時(shí),其內(nèi)部組件樹中對(duì)應(yīng)的 Consumer組件會(huì)接收到新值并重新執(zhí)行 children函數(shù)。此過程不受 shouldComponentUpdete 方法的影響。
4.Provider組件利用 Object.is 檢測 value prop 的值是否有更新。注意 Object.is和 === 的行為不完全相同。
5.Consumer組件接收一個(gè)函數(shù)作為 children prop 并利用該函數(shù)的返回值生成組件樹的模式被稱為 Render Props 模式。
四、新Context API的應(yīng)用
新的Context API大大簡化了react狀態(tài)傳遞的問題,也出現(xiàn)了一些基于它的狀態(tài)管理庫,諸如:unstated、react-waterfall等等。下面我們主要嘗試使用新context api來造一個(gè)react-redux的輪子。
1.Provider
由于新的context api傳遞過程中不會(huì)被shouldComponentUpdate阻斷,所以我們只需要在Provider里面監(jiān)聽store變化即可:
import React, { PureComponent, Children } from "react"; import { IContext, IStore } from "../helpers/types"; import { Provider } from "../context"; interface IProviderProps { store: IStore; } export default class EnhancedProvider extends PureComponent{ constructor(props: IProviderProps) { super(props); const { store } = props; if (store == null) { throw new Error(`Store should not omit in `); } this.state = { // 得到當(dāng)前的state state: store.getState(), dispatch: store.dispatch, } store.subscribe(() => { // 單純的store.getState函數(shù)是不變的,需要得到其結(jié)果state才能觸發(fā)組件更新。 this.setState({ state: store.getState() }); }) } render() { return {Children.only(this.props.children)} ; } };
2.connect
相比較于react-redux,connect中的高階組件邏輯就簡單的多,不需要監(jiān)聽store變化,直接獲得Provider傳入的state然后再傳遞給子組件即可:
import React, { Component, PureComponent } from "react"; import { IState, Dispatch, IContext } from "./helpers/types"; import { isFunction } from "./helpers/common"; import { Consumer } from "./context"; export default (mapStateToProps: (state: IState) => any, mapDispatchToProps: (dispatch: Dispatch) => any) => (WrappedComponent: React.ComponentClass) => class ConnectedComponent extends Component{ render() { return {(context: IContext) => { const { dispatch, state } = context; const filterProps = {}; if (isFunction(mapStateToProps)) { Object.assign(filterProps, mapStateToProps(state)); } if (isFunction(mapDispatchToProps)) { Object.assign(filterProps, mapDispatchToProps(dispatch)); } return } };}}
好了,至此整個(gè)React-redux的接口和功能都已經(jīng)基本cover了,下面繼續(xù)介紹一些比較重要的性能優(yōu)化。
3.性能優(yōu)化 - 減少重復(fù)渲染
性能優(yōu)化最大的一部分就是要減少無意義的重復(fù)渲染,當(dāng)WrappedComponent的參數(shù)值沒有變化時(shí)我們應(yīng)該阻止其重新渲染。可以通過手寫shouldComponentUpdate方法實(shí)現(xiàn),也可以直接通過PureComponent組件來達(dá)到我們的目標(biāo):
render() { return{(context: IContext) => { const { dispatch, state } = context; const filterProps = {}; if (isFunction(mapStateToProps)) { Object.assign(filterProps, mapStateToProps(state)); } if (isFunction(mapDispatchToProps)) { // mapDispatchToProps 返回值始終不變,可以memory this.dpMemory = this.dpMemory || mapDispatchToProps(dispatch); Object.assign(filterProps, this.dpMemory); } return }// PureComponent內(nèi)部自動(dòng)實(shí)現(xiàn)了前后參數(shù)的淺比較 class Prevent extends PureComponent}} { render() { const { combinedProps, WrappedComponent } = this.props; return ; } }
這里需要注意的是,本示例的mapDispatchToProps未支持ownProps參數(shù),因此可以把它的返回值看成是不變的,否則每次調(diào)用它返回的action函數(shù)都是新創(chuàng)建的,從而導(dǎo)致Prevent接收到的參數(shù)始終是不同的,達(dá)不到預(yù)期效果。更為復(fù)雜的情況請參考react-redux源碼中selector相關(guān)的部分。
4.性能優(yōu)化 - 減少層級(jí)嵌套
性能優(yōu)化另一個(gè)要點(diǎn)就是減少組件的層級(jí)嵌套,新context api在獲取context值的時(shí)候需要嵌套一層Consumer組件,這也是其比舊context api劣勢的地方。除此之外,我們應(yīng)該盡量減少層級(jí)的嵌套。因此在前一個(gè)性能優(yōu)化中我們不應(yīng)該再次嵌套一個(gè)PureComponent,取而代之的是,我們可以直接在Cunsumer中實(shí)現(xiàn)一個(gè)memory機(jī)制,實(shí)現(xiàn)代碼如下:
private shallowEqual(prev: any, next: any) { const nextKeys = Object.keys(next); const prevKeys = Object.keys(prev); if (nextKeys.length !== prevKeys.length) return false; for (const key of nextKeys) { if (next[key] !== prev[key]) { return false; } } return true; } render() { return{(context: IContext) => { const { dispatch, state } = context; const filterProps = {}; if (isFunction(mapStateToProps)) { Object.assign(filterProps, mapStateToProps(state)); } if (isFunction(mapDispatchToProps)) { // mapDispatchToProps 返回值始終不變 this.dpMemory = this.dpMemory || mapDispatchToProps(dispatch); Object.assign(filterProps, this.dpMemory); } const combinedProps = { ...this.props, ...filterProps }; if (this.prevProps && this.shallowEqual(this.prevProps, combinedProps)) { // 如果props一致,那么直接返回緩存之前的結(jié)果 return this.prevComponent; } else { this.prevProps = combinedProps; // 對(duì)當(dāng)前的子節(jié)點(diǎn)進(jìn)行緩存 this.prevComponent = }; return this.prevComponent; } }}
下面是前后chrome開發(fā)人員工具中組件層級(jí)的對(duì)比,可以看到嵌套層級(jí)成功減少了一層,兩層嵌套是新context api的局限,如果要保持react-redux的接口模式則無法再精簡了。
公眾號(hào)ID:Miaovclass
關(guān)注妙味訂閱號(hào):“妙味前端”,為您帶來優(yōu)質(zhì)前端技術(shù)干貨;
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.hztianpu.com/yun/53168.html
摘要:要求通過要求數(shù)據(jù)變更函數(shù)使用裝飾或放在函數(shù)中,目的就是讓狀態(tài)的變更根據(jù)可預(yù)測性單向數(shù)據(jù)流。同一份數(shù)據(jù)需要響應(yīng)到多個(gè)視圖,且被多個(gè)視圖進(jìn)行變更需要維護(hù)全局狀態(tài),并在他們變動(dòng)時(shí)響應(yīng)到視圖數(shù)據(jù)流變得復(fù)雜,組件本身已經(jīng)無法駕馭。今天是 520,這是本系列最后一篇文章,主要涵蓋 React 狀態(tài)管理的相關(guān)方案。 前幾篇文章在掘金首發(fā)基本石沉大海, 沒什么閱讀量. 可能是文章篇幅太長了?掘金值太低了? ...
摘要:前端每周清單第期與模式變遷與優(yōu)化界面生成作者王下邀月熊編輯徐川前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點(diǎn)分為新聞熱點(diǎn)開發(fā)教程工程實(shí)踐深度閱讀開源項(xiàng)目巔峰人生等欄目。 showImg(https://segmentfault.com/img/remote/1460000013279448); 前端每周清單第 51 期: React Context A...
摘要:異步實(shí)戰(zhàn)狀態(tài)管理與組件通信組件通信其他狀態(tài)管理當(dāng)需要改變應(yīng)用的狀態(tài)或有需要更新時(shí),你需要觸發(fā)一個(gè)把和載荷封裝成一個(gè)。的行為是同步的。所有的狀態(tài)變化必須通過通道。前端路由實(shí)現(xiàn)與源碼分析設(shè)計(jì)思想應(yīng)用是一個(gè)狀態(tài)機(jī),視圖與狀態(tài)是一一對(duì)應(yīng)的。 React實(shí)戰(zhàn)與原理筆記 概念與工具集 jsx語法糖;cli;state管理;jest單元測試; webpack-bundle-analyzer Sto...
摘要:開發(fā)教程步步為營,掌握基礎(chǔ)技能發(fā)布機(jī)器學(xué)習(xí)速成課程為了幫助更多的人了解與學(xué)習(xí)機(jī)器學(xué)習(xí)相關(guān)的知識(shí)技能,發(fā)布了人工智能學(xué)習(xí)網(wǎng)站。更多相關(guān)內(nèi)容參考數(shù)據(jù)科學(xué)與機(jī)器學(xué)習(xí)實(shí)戰(zhàn)手冊。 showImg(https://segmentfault.com/img/remote/1460000013586587); 前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點(diǎn);分為新聞熱...
摘要:用戶點(diǎn)擊改變?nèi)譅顟B(tài)崔然渲染整顆組件樹有沒有解決方案呢當(dāng)然有創(chuàng)建一個(gè)只接收的新組件,并將組件中的邏輯都移到組件中。最終的示例使用全局狀態(tài)和生成全局狀態(tài)和崔然完整示例見結(jié)論在和出現(xiàn)之前,缺乏自帶的全局狀態(tài)管理能力。 React 16.3 版本,正式推了出官方推薦的 context API —— 一種跨層級(jí)的數(shù)據(jù)傳遞方法。React 16.8 版本,推出了全新的 hooks 功能,將原本只...
閱讀 3015·2021-11-24 09:39
閱讀 2949·2021-09-29 09:34
閱讀 3664·2021-09-24 10:23
閱讀 1820·2021-09-22 15:41
閱讀 1755·2019-08-30 15:55
閱讀 3562·2019-08-30 13:58
閱讀 2686·2019-08-30 13:11
閱讀 1716·2019-08-29 12:31