验证码: 看不清楚,换一张 查询 注册会员,免验证
  • {{ basic.site_slogan }}
  • 打开微信扫一扫,
    您还可以在这里找到我们哟

    关注我们

react context优化的方法有哪些

阅读:852 来源:乙速云 作者:代码code

react context优化的方法有哪些

      一、前言

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

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

      二、用法

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

      • 1.通过Consumer来消费上下文

      const globalContext = React.createContext();
      class TestUseContextSon1 extends React.Component {
        render() {
          return (
            
              {(value) => {
                return 
      {value.num}
      ;         }}       
          );   } } export default class TestUseContext extends React.Component {   constructor(props) {     super(props);     this.state = {       num: 2,     };   }   render() {     return (                            );   } }
      • 2.通过静态变量contextType来消费上下文

      const globalContext = React.createContext();
      class TestUseContextSon2 extends React.Component {
        static contextType = globalContext;
        render() {
          return 
      {this.context.num}
      ;   } } export default class TestUseContext extends React.Component {   ...省略...   render() {     return (                            );   } }
      • 3.通过hooks useContext来消费上下文

      const globalContext = React.createContext();
      const TestUseContextSon3 = (props) => {
        const con = useContext(globalContext);
        return 
      {con.num}
      ; }; export default class TestUseContext extends React.Component {   ...省略...   render() {     return (                            );   } }

      比较:

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

      • contextType只能在类组件中使用

      • useContext只能在函数组件中使用

      三、缺点

      这里有一个例子:

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

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

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

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

      那么问题来了,我们使用context的时候,必然要向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 (
            
              {(value) => {
                console.log("Consumer----handle");
                return 
      {value.num}
      ;         }}       
          );   } } const TestUseContext = () => {   const [value, setValue] = useState({ num: 1 });   return (                 setValue({ num: value.num + 1 })}>           点击                            ); }

      react context优化的方法有哪些

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

      react context优化的方法有哪些

      点击按钮之后,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 (
            
              {(value) => {
                console.log("Consumer----handle");
                return 
      {value.num}
      ;         }}       
          ); }); const TestUseContext = () => {   const [value, setValue] = useState({ num: 1 });   return (                 setValue({ num: value.num + 1 })}>           点击                            ); }

      react context优化的方法有哪些

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

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

      react context优化的方法有哪些

      点击按钮之后,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 (
          
            {props.children}
          
        );
      };
      const Son1 = function (props) {
        const { setTheme } = useContext(ThemeContext);
        return  setTheme({ theme: "blue" })}>改变主题;
      };
      const Son2 = function (props) {
        const { theme } = useContext(ThemeContext);
        console.log("Son2----", theme.theme);
        return 
      主题----{theme.theme}
      ; }; const Son4 = function (props) {   console.log("Son4---没有使用上下文");   return 
      没有使用上下文
      ; }; export default class ContextChildren extends React.Component {   render() {     return (                                              );   } }

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

      效果如下:

      react context优化的方法有哪些

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

      react context优化的方法有哪些

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

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

      出现这种现象,其实是props.children引起的,props.children指向一个对象,这个对象中存放着执行的结果,ThemeProvider执行的时候,props.children指向的对象没有发生变化,只有当ContextChildren组件重新渲染的时候,才会重新执行,由于我们将状态放置于ThemeProvider组件中,所以ContextChildren组件不会重新渲染,也就不会重新执行,所以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 (
          
            {content}
          
        );
      };

      Son4依然没有执行

      react context优化的方法有哪些

      再改一下:

      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 (
          
            {content}
          
        );
      };

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

      react context优化的方法有哪些

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

    分享到:
    *特别声明:以上内容来自于网络收集,著作权属原作者所有,如有侵权,请联系我们: hlamps#outlook.com (#换成@)。
    相关文章
    {{ v.title }}
    {{ v.description||(cleanHtml(v.content)).substr(0,100)+'···' }}
    你可能感兴趣
    推荐阅读 更多>