react context优化四重奏教程示例

目录
  • 一、前言
  • 二、用法
  • 三、缺点
  • 四、context优化
    • 一重奏--使用PureComponent
    • 二重奏--使用shouldComponentUpdate
    • 三重奏--使用React.memo
    • 四重奏--Provider再封装+props.children
  • 总结

一、前言

我们在使用react的过程中,经常会遇到需要跨层级传递数据的情况。props传递数据应用在这种场景下会极度繁琐,且不利于维护,于是context应运而生

官方解释: Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props

二、用法

在正文之前,先简单介绍一下context的三种消费方法:

  • 1.通过Consumer来消费上下文
const globalContext = React.createContext();
class TestUseContextSon1 extends React.Component {
  render() {
    return (
      <globalContext.Consumer>
        {(value) => {
          return <div>{value.num}</div>;
        }}
      </globalContext.Consumer>
    );
  }
}
export default class TestUseContext extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      num: 2,
    };
  }
  render() {
    return (
      <globalContext.Provider value={{ num: this.state.num }}>
        <TestUseContextSon1 />
      </globalContext.Provider>
    );
  }
}
  • 2.通过静态变量contextType来消费上下文
const globalContext = React.createContext();
class TestUseContextSon2 extends React.Component {
  static contextType = globalContext;
  render() {
    return <div>{this.context.num}</div>;
  }
}
export default class TestUseContext extends React.Component {
  ...省略...
  render() {
    return (
      <globalContext.Provider value={{ num: this.state.num }}>
        <TestUseContextSon2 />
      </globalContext.Provider>
    );
  }
}
  • 3.通过hooks useContext来消费上下文
const globalContext = React.createContext();
const TestUseContextSon3 = (props) => {
  const con = useContext(globalContext);
  return <div>{con.num}</div>;
};
export default class TestUseContext extends React.Component {
  ...省略...
  render() {
    return (
      <globalContext.Provider value={{ num: this.state.num }}>
        <TestUseContextSon3 />
      </globalContext.Provider>
    );
  }
}

比较:

  • Consumer既可以在类组件中使用,也可以在函数组件中使用
  • contextType只能在类组件中使用
  • useContext只能在函数组件中使用

三、缺点

这里有一个例子:

import React, { useState } from "react";
const globalContext = React.createContext();
const Son1 = () => {
  return <div>Son1</div>;
};
const Son2 = () => {
  const value = useContext(globalContext);
  return <div>Son2---{value.num}</div>;
};
export const Demo = () => {
  const [value, setValue] = useState({ num: 1 });
  return (
    <globalContext.Provider value={value}>
      <Son1 />
      <Son2 />
    </globalContext.Provider>
  );
};

当我们改变value值时,会导致Son1Son2都发生重渲染,但这与我们的初衷相悖,造成了额外的开销,我们期望做到的是Son1不执行,Son2重新渲染。在较长的一段时间内,我都认为是使用了context导致Provider下面的子组件发生了重渲染。网上也有很多解释没有说清楚,容易误导人。

实际情况是value的变化导致了Son1Son2发生重渲染。如下示例: 即使我们不使用·context,当value发生变化时,Son1Son2也会重渲染。

const Son1 = () => {
  return <div>Son1</div>;
};
const Son2 = () => {
  return <div>Son2</div>;
};
export const Demo = () => {
  const [value, setValue] = useState({ num: 1 });
  return (
    <Son1 />
    <Son2 />
  );
};

那么问题来了,我们使用context的时候,必然要向<globalContext.Provider value={value}>Provider的value中传入一个状态,但是当状态改变时又不可避免的造成Provider下的所有子组件重新渲染,我们期望只有消费了上下文的子组件重新渲染,那么有什么方法能够避免这种额外的开销吗?

四、context优化

我们知道,所有消费了context的地方,只要Providervalue值发生变化,都会发生重渲染.只要我们有什么办法能够避开父组件状态发生变化,引起的子组件状态发生变化,那就可以减少很多不必要的开销。

一重奏--使用PureComponent

const globalContext = React.createContext();
class TestUseContextSon2 extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {};
  }
  render() {
    console.log("TestUseContextSon2----render");
    return (
      <globalContext.Consumer>
        {(value) => {
          console.log("Consumer----handle");
          return <div>{value.num}</div>;
        }}
      </globalContext.Consumer>
    );
  }
}
const TestUseContext = () => {
  const [value, setValue] = useState({ num: 1 });
  return (
      <globalContext.Provider value={value}>
        <button onClick={() => setValue({ num: value.num + 1 })}>
          点击
        </button>
        <TestUseContextSon2 />
      </globalContext.Provider>
  );
}

初始化的时候,两个console各执行一遍

点击按钮之后,TestUseContextSon2----render没有打印,Consumer----handle打印,达到预期结果。

二重奏--使用shouldComponentUpdate

此处由于作者比较任性,省略100字,基本效果其实和PureComponent一致,不做过多描述。

三重奏--使用React.memo

React.memo既可以用于函数组件,也可以用于类组件

const globalContext = React.createContext();
const TestUseContextSon3 = React.memo(function (props) {
  console.log("TestUseContextSon3----render");
  return (
      <globalContext.Consumer>
        {(value) => {
          console.log("Consumer----handle");
          return <div>{value.num}</div>;
        }}
      </globalContext.Consumer>
    );
});
const TestUseContext = () => {
  const [value, setValue] = useState({ num: 1 });
  return (
      <globalContext.Provider value={value}>
        <button onClick={() => setValue({ num: value.num + 1 })}>
          点击
        </button>
        <TestUseContextSon3 />
      </globalContext.Provider>
  );
}

点击按钮之后,TestUseContextSon2----render没有打印,Consumer----handle打印,达到预期结果。 那如果我们使用useContext来消费上下文呢?

const TestUseContextSon4 = React.memo(function (props) {
  const con = useContext(globalContext);
  console.log("TestUseContextSon4----render");
  return &lt;div&gt;{con.num}&lt;/div&gt;;
});

点击按钮之后,TestUseContextSon4----render打印,也就是说当我们使用useContext来消费上下文的时候,整个函数组件会重新执行。而Consumer仅仅只是局部执行,这意味更少的性能消耗。

四重奏--Provider再封装+props.children

上面所述的三种方法都存在一个弊端,Provider的直接下级组件都需要用memoPureComponentshouldComponentUpdate处理,才能屏蔽掉父级状态变化带来的影响,那么有没有一种更方便的方式呢?

代码如下:

/** 主题 */
const ThemeContext = React.createContext({ theme: "red" });
const ThemeProvider = (props) => {
  const [theme, setTheme] = useState({ theme: "red" });
  console.log("ThemeProvider-----", theme.theme);
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {props.children}
    </ThemeContext.Provider>
  );
};
const Son1 = function (props) {
  const { setTheme } = useContext(ThemeContext);
  return <button onClick={() => setTheme({ theme: "blue" })}>改变主题</button>;
};
const Son2 = function (props) {
  const { theme } = useContext(ThemeContext);
  console.log("Son2----", theme.theme);
  return <div>主题----{theme.theme}</div>;
};
const Son4 = function (props) {
  console.log("Son4---没有使用上下文");
  return <div>没有使用上下文</div>;
};
export default class ContextChildren extends React.Component {
  render() {
    return (
      <ThemeProvider>
        <Son1 />
        <Son2 />
        <Son4 />
      </ThemeProvider>
    );
  }
}

在上面这段代码中,<Son1 /><Son2 /><Son3 />并没有直接放到ThemeContext.Provider组件下面,而是将该组件再次封装成ThemeProvider组件,并将状态管理也放在ThemeProvider组件中,然后通过props.children来引入组件子节点。

效果如下:

当我们点击按钮时,打印如下:

点击按钮,setTheme执行,状态由{ theme: "red" }变为{ theme: "blue" },引起ThemeProvider组件重新执行,打印ThemeProvider----- blue,组件Son2由于消费了上下文,重新执行,打印Son2---- blue

那么问题来了,为什么没有打印Son4呢?我们没有使用memo、PureComponent等处理Son4组件,但是它确实不会重新执行。

出现这种现象,其实是props.children引起的,props.children指向一个对象,这个对象中存放着<Son1 /><Son2 /><Son3 />执行的结果,ThemeProvider执行的时候,props.children指向的对象没有发生变化,只有当ContextChildren组件重新渲染的时候,<Son1 /><Son2 /><Son3 />才会重新执行,由于我们将状态放置于ThemeProvider组件中,所以ContextChildren组件不会重新渲染,<Son1 /><Son2 /><Son3 />也就不会重新执行,所以Son4---没有使用上下文没有打印。

那如果将ThemeProvider组件改成这样呢?

const ThemeProvider = (props) => {
  const [theme, setTheme] = useState({ theme: "red" });
  console.log("ThemeProvider-----", theme.theme);
  const content = React.Children.map(props.children, (child) => {
    return child;
  });
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {content}
    </ThemeContext.Provider>
  );
};

Son4依然没有执行

再改一下:

const ThemeProvider = (props) => {
  const [theme, setTheme] = useState({ theme: "red" });
  console.log("ThemeProvider-----", theme.theme);
  const content = React.Children.map(props.children, (child) => {
    return React.cloneElement(child);
  });
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {content}
    </ThemeContext.Provider>
  );
};

我们使用React.cloneElementapi克隆一下child

Son4执行了,我想这是因为克隆之后指向发生变化,导致组件重新执行

总结

本文简单介绍了一下context的几种用法,以及如何来屏蔽父级状态变化(provider的value一般是和父级组件状态挂钩的)导致未消费上下文的子组件重新渲染导致的额外开销。

以上就是react context优化四重奏教程示例的详细内容,更多关于react context 优化教程的资料请关注我们其它相关文章!

(0)

相关推荐

  • React Context源码实现原理详解

    目录 什么是 Context Context 使用示例 createContext Context 的设计非常特别 useContext useContext 相关源码 debugger 查看调用栈 什么是 Context 目前来看 Context 是一个非常强大但是很多时候不会直接使用的 api.大多数项目不会直接使用 createContext 然后向下面传递数据,而是采用第三方库(react-redux). 想想项目中是不是经常会用到 @connect(...)(Comp) 以及 <Pro

  • React函数组件useContext useReducer自定义hooks

    目录 一.hooks(useContext) 二.hooks(useReducer) 三.hooks(useContext搭配useReducer使用) 四.自定义hooks 一.hooks(useContext) 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值.当前的 context 值由上层组件中距离当前组件最近的 <MyContext.Provider> 的 value prop 决定. 新建useContext.js

  • React 数据共享useContext的实现

    因为疫情, 最近接手一个新项目.是React的.上次写React已经过去1年多了. 虽然捡起来也不是什么难事,不过技术这个东西,长时间不用就容易忘记. 为了保证这个项目根其他平行项目的技术栈统一. 采用的是 Nextjs .styled-components.useContext .react-query.ts 今天不做重点介绍项目,还是说说在项目中碰到的问题. 这个足足折腾了差不多2个小时,我一直在排查其问题. 最后这个问题也是比较的诡异. ReferenceError: Cannot acc

  • 基于React Context实现一个简单的状态管理的示例代码

    目录 前言 封装一个父组件用来包裹其他子组件 子组件如何获取数据呢 class Component 方式 context.Consumer useContext 总结 参考 前言 在大多数情况下,我们开发项目都需要一个状态管理,方便我们在全局共享状态库,在React生态里比较流行的几个库 redux.mobx.recoil 但是对于小项目,我们完全可以自己封装一个状态管理,减少一个包的安装就可以减小打包以后的项目体积. 主要分两步: 封装一个顶层组件提供数据 子组件获取数据和更新数据 封装一个父

  • React 中的 useContext使用方法

    目录 什么是上下文呢? useContext使用的方法: 1.要先创建createContex 2.Provider 指定使用的范围 3.最后使用useContext useContext就是上下文 什么是上下文呢? 全局变量就是全局的上下文,全局都可以访问到它:上下文就是你运行一段代码,所要知道的所有变量 useContext使用的方法: 1.要先创建createContex 使用createContext创建并初始化 const C = createContext(null); 2.Prov

  • react中关于Context/Provider/Consumer传参的使用

    目录 Context/Provider/Consumer传参使用 Context 使用context向后代组件传参 Context/Provider/Consumer传参使用 react context这个api很少用到,所以一直不太清楚如何使用,最近在看antd的项目源码时,发现在组件中有类似Template.Comsumer的写法,一时没反应过来,本着碰到不懂得都要追根究底的原则,下面好好学习一下,Context这个api的使用 Context 作用 上下文(Context) 提供了一种通过

  • react context优化四重奏教程示例

    目录 一.前言 二.用法 三.缺点 四.context优化 一重奏--使用PureComponent 二重奏--使用shouldComponentUpdate 三重奏--使用React.memo 四重奏--Provider再封装+props.children 总结 一.前言 我们在使用react的过程中,经常会遇到需要跨层级传递数据的情况.props传递数据应用在这种场景下会极度繁琐,且不利于维护,于是context应运而生 官方解释: Context 提供了一种在组件之间共享此类值的方式,而不

  • React大屏可视化脚手架教程示例

    目录 使用 create-react-app 初始化 引入 antd UI库 使用 craco 插件来自定义配置 自定义 antd 主题,使用 less 作为 css 预处理器 修改 craco.config.js 文件 craco-less .module.less 模拟vue组件中style的scope 功能 配置 craco.config.js 文件 以上操作版本记录 使用 create-react-app 初始化 # 使用了 create-react-app 的 typescript 模

  • React Context原理深入理解源码示例分析

    目录 正文 一.概念 二.使用 2.1.React.createContext 2.2.Context.Provider 2.3.React.useContext 2.4.Example 三.原理分析 3.1.createContext 函数实现 3.2. JSX 编译 3.3.消费组件 - useContext 函数实现 3.4.Context.Provider 在 Fiber 架构下的实现机制 3.5.小结 四.注意事项 五.对比 useSelector 正文 在 React 中提供了一种「

  • React hook超详细教程

    目录 什么是hook useState useEffect useRef useCallback useMemo useContext useReducer 什么是hook React Hook是React 16.8版本之后添加的新属性,用最简单的话来说,React Hook就是一些React提供的内置函数,这些函数可以让函数组件和类组件一样能够拥有组件状态(state)以及进行副作用(side effect) 但是不要什么业务都使用hook,请在合适的时候使用hook,否则会造成性能问题.(能

  • webpack 5.68.0版本教程示例详解

    目录 起步 1. 基本安装 2. 配置出入口 plugin 1. html-webpack-plugin 2. progress-bar-webpack-plugin loader 1. css-loader与style-loader 2. url-loader与file-loader 3. sass-loader 4. postcss-loader 5. babel-loader 搭建环境 1. 开发环境与生产环境 2. 配置别名 代码分离 1. webpack-bundle-analyzer

  • React useCallback详细使用教程

    目录 一.useCallback的作用 二.useRef解决方案 三.useReducer解决方案 四.usePersistFn解决方案 一.useCallback的作用 usecallback不是用来解决组件中有过多内部函数导致的性能问题: 1.我们要知道,js创建一个函数的成本是非常小的,这点计算对于计算机来说是小case 2.其实使用useCallback会产成额外的性能:对deps的判断 3.其实每次组件重新渲染时,都无所谓避免重新创建内部函数,因为即使useCallback的deps没

  • Redis缓存IO模型的演进教程示例精讲

    目录 前言 事件模型 通信 copy数据的开销 数据怎么知道发给哪个socket socket的数据怎么通知程序来取 Reactor IO多路复用器 select epoll epoll是怎么做到的? 单线程到多线程的演进 单线程 异步线程 多线程 多线程的作用点? 多线程的原理 前言 redis作为应用最广泛的nosql数据库之一,大大小小也经历过很多次升级.在4.0版本之前,单线程+IO多路复用使得redis的性能已经达到一个非常高的高度了.作者也说过,之所以设计成单线程是因为redis的瓶

  • vue实例成员 插值表达式 过滤器基础教程示例详解

    目录 一. 什么是Vue 二.为什么学Vue 三.如何使用Vue 下载安装? 插值表达式 四.vue特点 1.虚拟DOM 2.数据的双向绑定 3.单页面应用 4.数据驱动 五.Vue实例 六.实例成员 - 挂载点 | el - 自定义插值表达式括号| delimiters - 数据 | data - 过滤器 | filters - 方法 | methods - js对象(即字典)补充 - 插值表达式转义 | delimters - 计算属性 | computed - 监听属性 | watch 一

  • go语言使用Chromedp实现二维码登陆教程示例源码

    目录 1 Chromedp是什么 2 为什么不使用Selenium 3 文章解决了什么需求 4.如何使用chromedp进行二维码登陆 4.1 安装chromedp 4.2 尝试打开网站 4.3 获取二维码(点击过程) 5. 如何将二维码展示在无图形化的终端上 6. 如何保存Cookies实现短时间免登陆 源码点击下载 1 Chromedp是什么 chromedp是一个更快.更简单的Golang库用于调用支持Chrome DevTools协议的浏览器,同时不需要额外的依赖(例如Selenium和

随机推荐