React 新 Context API 在前端狀態管理的實踐

2019獨角獸企業重金招聘Python工程師標準>>> hot3.png

本文轉載至:今日頭條技術博客

眾所周知,React的單向數據流模式導致狀態只能一級一級的由父組件傳遞到子組件,在大中型應用中較為繁瑣不好管理,通常我們需要使用Redux來幫助我們進行管理,然而隨著React 16.3的發布,新context api成為了新的選擇。

一、Redux的簡介以及缺陷

Redux來源于Flux并借鑒了Elm的思想,主要原理如下圖所示:

可以看到,Redux的數據流其實非常簡單,外部事件通過actionCreator函數調用dipsatch發布action到reducers中,然后各自的reducer根據action的類型(action.type) 來按需更新整個應用的state。

redux設計有以下幾個要點:

1.state是單例模式且不可變的,單例模式避免了不同store之間的數據交換的復雜性,而不可變數據提供了十分快捷的撤銷重做、“時光旅行”等功能。

2.state只能通過reducer來更新,不可以直接修改

3.reducer必須是純函數,形如(state,action) => newState

redux本身是個非常純粹的狀態管理庫,需要通過react-redux這個庫的幫助來管理react的狀態。react-redux主要包含兩個部分。

1.Provider組件:可以將store注入到子組件的cotext中,所以一般放在應用的最頂層。

2.connect函數: 返回一個高階函數,把context中由Provider注入的store取出來然后通過props傳遞到子組件中,這樣子組件就能順利獲取到store了。

雖然redux在React項目中得到了普遍的認可與使用率,然而在現實項目中redux還是存在著很多缺點:

1.樣板代碼過多:增加一個action往往需要同時定義相應的actionType然后再寫N個相關的reducer。例如當添加一個異步加載事件時,需要同時定義加載中、加載失敗以及加載完成三個actionType,需要一個相對應的reducer通過switch分支來處理對應的actionType,冗余代碼過多。

2.更新效率問題:由于使用不可變數據模式,每次更新state都需要拷貝一份完整的state造成了內存的浪費以及性能的損耗。

3.數據傳遞效率問題:由于react-redux采用的舊版context API,context的傳遞存在著效率問題。

其中,第一個問題目前已經存在著非常多的解決方案,諸如dva、rematch以及mirror等等,筆者也造過一個類似的輪子restated這里不做過多闡述。

第二個問題首先redux以及react-redux中已經做了非常詳盡的優化了,其次擅用shouldComponentUpdate方法也可以避免很多不必要的更新,最后,也可以使用一些不可變數據結構如immutable、Immr等來從根本上解決拷貝開銷問題。

第三個問題屬于React自身API的局限,從第三方庫的角度上來說,能做的很有限。

二、Context API

context API主要用來解決跨組件傳參泛濫的問題(prop drilling),舊的context API的語法形式如下:

 // 傳遞者,生成數據并放入context中class DeliverComponent extends Component {  getChildContext() {    return { color: "purple" };render() {    return <MidComponent /> }
}
DeliverComponent.childContextTypes = {  color: PropTypes.string
};// 中間與context無關的組件
const MidComponent = (props) => <ReceiverComponent />;// 接收者,需要用到context中的數據const ReceiverComponent = (props, context) =>  <div style={{ color: context.color }}> Hello, this is receiver. 
</div>;
ReceiverComponent.contextTypes = {  color: PropTypes.string
};ReactDOM.render(  <DeliverComponent><MidComponent><ReceiverComponent /></MidComponent></DeliverComponent>, document.getElementById('root'));

可以看到,使用context api可以把DeliverComponent中的參數color直接跨越MidComponent傳遞到ReceiverComponent中,不需要冗余的使用props參數傳遞,特別是ReceiverComponent層級特別深的時候,使用context api能夠很大程度上節省重復代碼避免bug。

舊Context API的缺陷

舊的context api主要存在如下的缺陷:

1.代碼冗余:提供context的組件要定義childContextTypesgetChildContext才能把context傳下去。同時接收context的也要先定義contextTypes才能正確拿到數據。

2.傳遞效率:雖然功能上context可以跨層級傳遞,但是本質上context也是同props一樣一層一層的往下傳遞的,當層級過深的時候還是會出現效率問題。

3.shouldComponentUpdate:由于context的傳遞也是一層一層傳遞,因此它也會受到shouldComponent的阻斷。換句話說,當傳遞組件的context變化時,如果其下面某一個中間組件的shouldComponentUpdate方法返回false,那么之后的接收組件將不會受到任何context變化。

為了解決舊版本的shouldComponentUpdate問題,保證所有的組件都能收到store的變化,react-redux只能傳遞一個getState方法給各個組件用于獲取最新的state(直接傳遞state可能會被阻斷,后面的組件將接收不到state的變化),然后每個connect組件都需要直接或間接監聽state的變化,當state發生改變時,通過內部notifyNestedSubs方法從上往下依次觸發各個子組件通過getState方法獲取最新的state更新視圖。這種方式效率較低而且比較hack。

三、新Context API

React自16.3開始提供了一個新的context api,徹底解決了舊Context API存在的種種問題。 下面是新context api(右)與使用舊context api的react-redux(左)數據流的比較:

可以看到,新的context api可以直接將context數據傳遞到傳遞到子組件中而不需要像舊context api那樣級聯傳遞。因此也可以突破shouldComponentUpdate的限制。新版的context api的定義如下:

type Context<T> = {  Provider: Provider<T>,Consumer: Consumer<T>,
};interface React {  createContext<T>(defaultValue: T): Context<T>;
}
type Provider<T> = React.Component<{  value: T,  children?: React.Node,
}>;type Consumer<T> = React.Component<{  children: (value: T) => React.Node,
}>;

下面是一個比較簡單的應用示例:

import React, { Component, createContext } from 'react';const DEFAULT_STATE = {color: 'red'};  const { Provider, Consumer } = createContext(DEFAULT_STATE);// 傳遞者,生成數據并放入context中class DeliverComponent extends Component {  state = { color: "purple" };render() {    return (      <Provider value={this.state}><MidComponent /></Provider>)}
}// 中間與context無關的組件const MidComponent = (props) => <ReceiverComponent />;// 接收者,需要用到context中的數據
const ReceiverComponent = (props) => (  <Consumer>{context => (<div style={{ color: context.color }}> Hello, this is receiver. </div>)}</Consumer>
);ReactDOM.render(  <DeliverComponent><MidComponent><ReceiverComponent /></MidComponent></DeliverComponent>, document.getElementById('root'));

可以看到新的context api主要包含一個Provider和Consumer對,在Provider輸入的數據可以在Consumer中獲得。 新context api的要點如下:

1.Provider和 Consumer必須來自同一次 React.createContext調用。也就是說 NameContext.Provider和 AgeContext.Consumer是無法搭配使用的。

2.React.createContext方法接收一個默認值作為參數。當 Consumer外層沒有對應的 Provider時就會使用該默認值。

3.Provider 組件的 valueprop 值發生變更時,其內部組件樹中對應的 Consumer組件會接收到新值并重新執行 children函數。此過程不受 shouldComponentUpdete 方法的影響。

4.Provider組件利用 Object.is 檢測 value prop 的值是否有更新。注意 Object.is和 === 的行為不完全相同。

5.Consumer組件接收一個函數作為 children prop 并利用該函數的返回值生成組件樹的模式被稱為 Render Props 模式。

四、新Context API的應用

新的Context API大大簡化了react狀態傳遞的問題,也出現了一些基于它的狀態管理庫,諸如:unstated、react-waterfall等等。下面我們主要嘗試使用新context api來造一個react-redux的輪子。 1.Provider

由于新的context api傳遞過程中不會被shouldComponentUpdate阻斷,所以我們只需要在Provider里面監聽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<IProviderProps, IContext> {  constructor(props: IProviderProps) {   super(props);    const { store } = props;    if (store == null) {      throw new Error(`Store should not omit in <Provider/>`);}   this.state = {      // 得到當前的statestate: store.getState(),dispatch: store.dispatch,}store.subscribe(() => {      // 單純的store.getState函數是不變的,需要得到其結果state才能觸發組件更新。this.setState({ state: store.getState() });})}render() {    return <Provider value={this.state}>    {Children.only(this.props.children)}</Provider>;}
};

2 connect

相比較于react-redux,connect中的高階組件邏輯就簡單的多,不需要監聽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<any>{render() {        return <Consumer>{(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 <WrappedComponent{...this.props}{...filterProps}/>}}</Consumer>}};

好了,至此整個React-redux的接口和功能都已經基本cover了,下面繼續介紹一些比較重要的性能優化。

3.性能優化 - 減少重復渲染

性能優化最大的一部分就是要減少無意義的重復渲染,當WrappedComponent的參數值沒有變化時我們應該阻止其重新渲染。可以通過手寫shouldComponentUpdate方法實現,也可以直接通過PureComponent組件來達到我們的目標:

render() {  return <Consumer>{(context: IContext) => {      const { dispatch, state } = context;      const filterProps = {};     if (isFunction(mapStateToProps)) {Object.assign(filterProps, mapStateToProps(state));}      if (isFunction(mapDispatchToProps)) {   // mapDispatchToProps 返回值始終不變,可以memorythis.dpMemory = this.dpMemory  || mapDispatchToProps(dispatch);Object.assign(filterProps, this.dpMemory);}return <PreventcombinedProps={{ ...this.props, ...filterProps }}WrappedComponent={WrappedComponent} />}}</Consumer>
}// PureComponent內部自動實現了前后參數的淺比較class Prevent extends PureComponent<any> {  render() {    const { combinedProps, WrappedComponent } = this.props;    return <WrappedComponent {...combinedProps} />;}
}

這里需要注意的是,本示例的mapDispatchToProps未支持ownProps參數,因此可以把它的返回值看成是不變的,否則每次調用它返回的action函數都是新創建的,從而導致Prevent接收到的參數始終是不同的,達不到預期效果。更為復雜的情況請參考react-redux源碼中selector相關的部分。

4.性能優化 - 減少層級嵌套

性能優化另一個要點就是減少組件的層級嵌套,新context api在獲取context值的時候需要嵌套一層Consumer組件,這也是其比舊context api劣勢的地方。除此之外,我們應該盡量減少層級的嵌套。因此在前一個性能優化中我們不應該再次嵌套一個PureComponent,取而代之的是,我們可以直接在Cunsumer中實現一個memory機制,實現代碼如下:

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 <Consumer>{(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一致,那么直接返回緩存之前的結果return this.prevComponent;} else {        this.prevProps = combinedProps;  // 對當前的子節點進行緩存this.prevComponent = <WrappedComponent {...combinedProps} />;        return this.prevComponent;}}}</Consumer>
}

下面是前后chrome開發人員工具中組件層級的對比,可以看到嵌套層級成功減少了一層,兩層嵌套是新context api的局限,如果要保持react-redux的接口模式則無法再精簡了。

公眾號ID:Miaovclass

關注妙味訂閱號:“妙味前端”,為您帶來優質前端技術干貨;

轉載于:https://my.oschina.net/u/3989863/blog/2253878

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/389384.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/389384.shtml
英文地址,請注明出處:http://en.pswp.cn/news/389384.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

機器學習模型 非線性模型_機器學習模型說明

機器學習模型 非線性模型A Case Study of Shap and pdp using Diabetes dataset使用糖尿病數據集對Shap和pdp進行案例研究 Explaining Machine Learning Models has always been a difficult concept to comprehend in which model results and performance stay black box (h…

5分鐘內完成胸部CT掃描機器學習

This post provides an overview of chest CT scan machine learning organized by clinical goal, data representation, task, and model.這篇文章按臨床目標&#xff0c;數據表示&#xff0c;任務和模型組織了胸部CT掃描機器學習的概述。 A chest CT scan is a grayscale 3…

Pytorch高階API示范——線性回歸模型

本文與《20天吃透Pytorch》有所不同&#xff0c;《20天吃透Pytorch》中是繼承之前的模型進行擬合&#xff0c;本文是單獨建立網絡進行擬合。 代碼實現&#xff1a; import torch import numpy as np import matplotlib.pyplot as plt import pandas as pd from torch import …

vue 上傳圖片限制大小和格式

<div class"upload-box clear"><span class"fl">上傳圖片</span><div class"artistDet-logo-box fl"><el-upload :action"this.baseServerUrl/fileUpload/uploadPic?filepathartwork" list-type"pic…

作業要求 20181023-3 每周例行報告

本周要求參見&#xff1a;https://edu.cnblogs.com/campus/nenu/2018fall/homework/2282 1、本周PSP 總計&#xff1a;927min 2、本周進度條 代碼行數 博文字數 用到的軟件工程知識點 217 757 PSP、版本控制 3、累積進度圖 &#xff08;1&#xff09;累積代碼折線圖 &…

算命數據_未來的數據科學家或算命精神向導

算命數據Real Estate Sale Prices, Regression, and Classification: Data Science is the Future of Fortune Telling房地產銷售價格&#xff0c;回歸和分類&#xff1a;數據科學是算命的未來 As we all know, I am unusually blessed with totally-real psychic abilities.眾…

openai-gpt_為什么到處都看到GPT-3?

openai-gptDisclaimer: My opinions are informed by my experience maintaining Cortex, an open source platform for machine learning engineering.免責聲明&#xff1a;我的看法是基于我維護 機器學習工程的開源平臺 Cortex的 經驗而 得出 的。 If you frequent any part…

Pytorch高階API示范——DNN二分類模型

代碼部分&#xff1a; import numpy as np import pandas as pd from matplotlib import pyplot as plt import torch from torch import nn import torch.nn.functional as F from torch.utils.data import Dataset,DataLoader,TensorDataset""" 準備數據 &qu…

OO期末總結

$0 寫在前面 善始善終&#xff0c;臨近期末&#xff0c;為一學期的收獲和努力畫一個圓滿的句號。 $1 測試與正確性論證的比較 $1-0 什么是測試&#xff1f; 測試是使用人工操作或者程序自動運行的方式來檢驗它是否滿足規定的需求或弄清預期結果與實際結果之間的差別的過程。 它…

puppet puppet模塊、file模塊

轉載&#xff1a;http://blog.51cto.com/ywzhou/1577356 作用&#xff1a;通過puppet模塊自動控制客戶端的puppet配置&#xff0c;當需要修改客戶端的puppet配置時不用在客戶端一一設置。 1、服務端配置puppet模塊 &#xff08;1&#xff09;模塊清單 [rootpuppet ~]# tree /et…

數據可視化及其重要性:Python

Data visualization is an important skill to possess for anyone trying to extract and communicate insights from data. In the field of machine learning, visualization plays a key role throughout the entire process of analysis.對于任何試圖從數據中提取和傳達見…

熊貓數據集_熊貓邁向數據科學的第三部分

熊貓數據集Data is almost never perfect. Data Scientist spend more time in preprocessing dataset than in creating a model. Often we come across scenario where we find some missing data in data set. Such data points are represented with NaN or Not a Number i…

Pytorch有關張量的各種操作

一&#xff0c;創建張量 1. 生成float格式的張量: a torch.tensor([1,2,3],dtype torch.float)2. 生成從1到10&#xff0c;間隔是2的張量: b torch.arange(1,10,step 2)3. 隨機生成從0.0到6.28的10個張量 注意&#xff1a; (1).生成的10個張量中包含0.0和6.28&#xff…

mongodb安裝失敗與解決方法(附安裝教程)

安裝mongodb遇到的一些坑 浪費了大量的時間 在此記錄一下 主要是電腦系統win10企業版自帶的防火墻 當然還有其他的一些坑 一般的問題在第6步驟都可以解決&#xff0c;本教程的安裝步驟不夠詳細的話 請自行百度或谷歌 安裝教程很多 我是基于node.js使用mongodb結合Robo 3T數…

【洛谷算法題】P1046-[NOIP2005 普及組] 陶陶摘蘋果【入門2分支結構】Java題解

&#x1f468;?&#x1f4bb;博客主頁&#xff1a;花無缺 歡迎 點贊&#x1f44d; 收藏? 留言&#x1f4dd; 加關注?! 本文由 花無缺 原創 收錄于專欄 【洛谷算法題】 文章目錄 【洛谷算法題】P1046-[NOIP2005 普及組] 陶陶摘蘋果【入門2分支結構】Java題解&#x1f30f;題目…

web性能優化(理論)

什么是性能優化&#xff1f; 就是讓用戶感覺你的網站加載速度很快。。。哈哈哈。 分析 讓我們來分析一下從用戶按下回車鍵到網站呈現出來經歷了哪些和前端相關的過程。 緩存 首先看本地是否有緩存&#xff0c;如果有符合使用條件的緩存則不需要向服務器發送請求了。DNS查詢建立…

python多項式回歸_如何在Python中實現多項式回歸模型

python多項式回歸Let’s start with an example. We want to predict the Price of a home based on the Area and Age. The function below was used to generate Home Prices and we can pretend this is “real-world data” and our “job” is to create a model which wi…

充分利用UC berkeleys數據科學專業

By Kyra Wong and Kendall Kikkawa黃凱拉(Kyra Wong)和菊川健多 ( Kendall Kikkawa) 什么是“數據科學”&#xff1f; (What is ‘Data Science’?) Data collection, an important aspect of “data science”, is not a new idea. Before the tech boom, every industry al…

文本二叉樹折半查詢及其截取值

using System;using System.ComponentModel;using System.Data;using System.Drawing;using System.Text;using System.Windows.Forms;using System.Collections;using System.IO;namespace CS_ScanSample1{ /// <summary> /// Logic 的摘要說明。 /// </summary> …

nn.functional 和 nn.Module入門講解

本文來自《20天吃透Pytorch》 一&#xff0c;nn.functional 和 nn.Module 前面我們介紹了Pytorch的張量的結構操作和數學運算中的一些常用API。 利用這些張量的API我們可以構建出神經網絡相關的組件(如激活函數&#xff0c;模型層&#xff0c;損失函數)。 Pytorch和神經網絡…