React 高级指引之 Context

React 高级指引之 Context

 

1.为什么使用 Context

 
首先来考虑这样一个场景,有一段文本内容用于展示主题的字体颜色及字体大小。我们可以通过组件的属性来传递,代码如下:

 
顶层组件,定义主题

// 引入中间组件
import IntermediaryComponents from './IntermediaryComp'

// 顶层组件,定义主题
class Context extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: {
        color: 'light',  // light,dark
        size: 'small',  // small, middle, large
      }
    }
  }

  render() {
    const { theme } = this.state;
    return <IntermediaryComponents theme={theme} />
  }
}
export default Context;

 
中间组件

// 引入展示主题组件
import ThemeConsumerComp from './ThemeConsumerComp'

// 中间组件,不做任何操作,只为引入下一级组件做隔层传递数据展示
class IntermediaryComponents extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    const { theme } = this.props;
    return <ThemeConsumerComp theme={theme} />
  }
}
export default IntermediaryComponents;

 
展示组件

// 最底端展示主题的组件
class ThemeConsumerComp extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    const { theme } = this.props;
    return (
      <p>顶层组件定义的主题:颜色为{theme.color}, 大小为{theme.size}</p>
    )
  }
}

 
可以看到,在中间组件中,并没有使用到 theme ,但是为了能传递到展示主题的组件中,我们不得不在中间组件里面先接收 props,再把 props 里面的 theme 传递下去。

在上面的例子中,仅仅嵌套三层组件即便是显示的传递 theme 也并不麻烦。

不过请试想,当嵌套的层数达到五层,十层呢,再使用通过 props 一层一层的去传递,那不仅工作量会大大增加,也不易维护,当我们需要再新增一个变量时又得再次显示传递多层。

 
这就是为什么需要使用 Context:

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

 

2.何时应该使用 Context

 
Context 的主要应用场景在于多层级的各个组件获取相同的数据。

 
但是 Context 也需要谨慎使用,其可能导致组件的复用性变差。例如大量用到 context 传下来的值的组件和提供 context 的组件的耦合性会增加。

 
注:如果仅仅想避免层层传递一些属性,而这些属性又仅有一两个组件可能使用到,使用 组合组件 在有些时候会是个更好的解决办法。简单来说,就是直接把这个组件整个作为属性传递下去,需要用的属性直接带在这个组件上,那么虽然也需要层层显示传递。但此时就只需要传递一个组件而不用考虑大量的属性。

// 引入展示主题组件
import ThemeConsumerComp from './ThemeConsumerComp'

// 顶层组件,定义主题
class Context extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: {
        color: 'light',  // light,dark
        size: 'small',  // small, middle, large
      }
    }
  }

  render() {
    const { theme } = this.state;
    return (
    	<IntermediaryComponents
    		themeComp={<ThemeConsumerComp theme={theme} />}
    	/>
    )
  }
}
export default Context;

 
如代码中展示的那样,直接把用到 theme 的组件 作为属性传递下去。中间过程的组件就不需要再考虑 theme 是否变化了
 
当然,一般来说,顶层的 theme 可能会在很多不同组件中使用到,Context 能使用“广播”的形式让所有消费组件访问到,也能访问到后续的数据更新**。
 
使用 context 的通用的场景包括管理当前的 locale,theme,或者一些缓存数据。
 
注: 消费组件,简单来说能获取到到 context 的值的组件。

 

3.相关 API 简介

 

3.1 React.createContext
const ThemeContext = React.createContext(defaultValue);

 
上面一行代码创建了一个 Context 对象。当某组件(A组件)使用 ThemeContext.Provider 时会提供一个 Context 环境,A组件下的所有消费组件都可以访问到该 provider 上的 value 值。

 
注1:只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。即某组件创建了 Context 对象,但是并没有使用 provider 创建 context 环境。

注2:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。

注3:当消费组件想读取 value 时,会匹配离自身最近的 Provider ,这种情况出现于多个的 context 提供的 Provider 嵌套时。

 

3.2 Context.Provider
<ThemeContext.Provider value={theme}>
  <IntermediaryComponents />
</ThemeContext.Provider>

 
这里提到的 provider 是每个 Context 对象都会返回的一个 Provider React 组件,它允许消费组件订阅 context 的变化。

可以看到,provider 可以接受一个 value 属性,其消费组件可以获取到这个 value

 
注1:当多个 provider 嵌套时,里层的provider 在数据相同情况下会覆盖外面的 provider。类似于作用域变量的获取。

注2:当 provider 的值发生变化时,它内部所有的消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。

注3:通过新旧值检测来确定变化,使用了与 Object.is 相同的算法。关于比较算法,可以查阅:
ES 标准中的相等比较算法

注4provider 上的 value 的值最好交由 state 来管理,这是因为若是直接写成对象,在父组件渲染时value 的值每次都会被重新赋值,导致下面的消费组件也会跟着渲染。

 

3.3 Class.contextType

 
挂载在 class 组件上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。

你可以在任何生命周期中访问到它。

class ThemeConsumerComp extends React.Component {
  render() {
    return (
      <div>
		<p>顶层组件定义的主题:颜色为{this.context.color}, 大小为{this.context.size}</p>
      </div>
    )
  }
}

ThemeConsumerComp.contextType = ThemeContext;

export default ThemeConsumerComp;

 
当然,你也可以使用 static 这个类属性来初始化你的 contextType。

class ThemeConsumerComp extends React.Component {
  static contextType = ThemeContext;
  render() {
    return (
      <div>
		<p>顶层组件定义的主题:颜色为{this.context.color}, 大小为{this.context.size}</p>
      </div>
    )
  }
}

export default ThemeConsumerComp;

 

3.4 Context.Consumer

 
在上面的 class.contextType 中给出了 class 组件可以怎么消费最近 Context 上的那个值。

那么函数组件并没有实例,也就没有 this ,该如何拿到 context 上的值呢,这就是即将说到的 Context.Consumer 组件。(class 组件也可以使用该方式)
 
Context.Consumer 需要一个函数作为子元素,该函数接收一个参数,即是当前最近 context 上的值,然后会返回一个 React 元素(通过 JSX 的方式利于书写)。
 

const ThemeConsumerComp = () => {
  return (
    <div>
      <ThemeContext.Consumer>
        {
          (theme) => {
            return (
              <p>顶层组件定义的主题:颜色为{theme.color}, 大小为{theme.size}</p>
            )
          }
        }
      </ThemeContext.Consumer>
    </div>
  )
}

export default ThemeConsumerComp;

注:如果没有对应的 Provider,value 参数等同于传递给 createContext() 的 defaultValue。这一点可以与 3.1 节进行比照。

 

3.5 Context.displayName

 
context 对象接受一个名为 displayName 的 property,类型为字符串。实际上相当于起了一个别名,可以在使用 React DevTools 时来方便确认 context 要显示的内容。

ThemeContext.displayName = 'MyThemeDisplayName';

 
在 React DevTools 中:

<ThemeContext.Provider> // 展示"MyThemeDisplayName.Provider"
<ThemeContext.Consumer> // 展示"MyThemeDisplayName.Consumer"

 

4.完整示例

 

4.1 修改主题

 

1.themeContext.ts

 
抽出定义 context 对象的相关内容。
 
该部分实现了创建了一个 Context 对象。当某组件(A组件)使用 ThemeContext.Provider 时会提供一个 Context 环境,A组件下的所有消费组件都可以访问到该 provider 上的 value 值。
 

import React from 'react';

export const defalutValue = {
  color: 'light',  // light,dark
  size: 'middle',  // small, middle, large
};

// 通过 createContext 定义一个 context
export const ThemeContext = React.createContext(defalutValue);

 

2.context.tsx

 
通过 provider 可以添加一个 value 属性,其它消费组件可以获取到这个 value。
 
value 的值最好由 state 接管,可以避免父组件重新渲染时导致子组件不必要的渲染。(参考:3.2 Context.Provider 注4 )。
 

import React from 'react';
import { ThemeContext } from './themeContext';
import IntermediaryComponents from './IntermediaryComp'

/**
 * 顶层组件,定义主题
 */
class Context extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      theme: {
        color: 'light',  // light,dark
        size: 'middle',  // small, middle, large
      }
    }
  }

  changeTheme = () => {
    const { theme: { color } } = this.state;
    const theme = {
      color: color === 'light' ? 'dark' : 'light',  // light,dark
      size: color === 'light' ? 'large' : 'middle',  // small, middle, large
    }
    this.setState({ theme });
  }

  render() {
    const { theme } = this.state;
    return (
      <div>
        {/* 使用定义的 context 的 provider 做父容器,value 是传递的值, 
        如果需要传多个值,可以用对象封装一下 */}
        <ThemeContext.Provider value={theme}>
          <IntermediaryComponents />
        </ThemeContext.Provider>
        <hr />
        {/* 修改顶层组件的主题,就能全局实现修改主题 */}
        <button onClick={this.changeTheme}>修改主题</button>
      </div>
    )
  }
}

export default Context;

 

3.IntermediaryComponents.tsx

 
中间组件,不做任何操作,只为引入下一级组件做隔层传递数据展示
 

import React from 'react';
import ThemeConsumerComp from './ThemeConsumerComp'

class IntermediaryComponents extends React.Component {

  render() {
    return (
      <div>
          <ThemeConsumerComp />
      </div>
    )
  }
}
export default IntermediaryComponents;

 

4.ThemeConsumerComponents.tsx

 
通过 Context.Consumer 使用一个函数作为子元素,该函数接收一个参数,即是当前最近 context 上的值,然后会返回一个 React 元素。

class 组件也可以使用该方式接受,或者使用 contextType 的方式(参考: 3.3 Class.contextType)
 

import React from 'react'

import { ThemeContext } from './themeContext';

class ThemeConsumerComp extends React.Component {
  
  render() {
    return (
      <ThemeContext.Consumer>
        {
          (theme) => {
            return (
              <p>顶层组件定义的主题:颜色为{theme.color}, 大小为{theme.size}</p>
            )
          }
        }
      </ThemeContext.Consumer>
    )
  }
}

export default ThemeConsumerComp;

 

4.2 在嵌套组件中更新 Context

 
在实际工作中,修改主题的按钮可能存在于不同的地方,因此支持在消费组件中更新 context 是很有必要的。

为了实现该功能,可以在 context 中传递一个函数。

下面代码是在 4.1 示例中进行部分修改
 

1.themeContext.ts

 
保证代码健壮性,在 defalutValue 增加 toggleTheme。

import React from 'react';

export const defalutValue = {
  color: 'light',  // light,dark
  size: 'middle',  // small, middle, large
  toggleTheme: () => {},
};

// 通过 createContext 定义一个 context
export const ThemeContext = React.createContext(defalutValue);

 

2.context.tsx

 
重点在于增加 this.toggleTheme ,并随着 theme 一起作为 provider 的 value 提供给消费组件。

import React from 'react';
import { ThemeContext } from './themeContext';
import IntermediaryComponents from './IntermediaryComp'

class Context extends React.Component {
  constructor(props) {
    super(props);

    this.toggleTheme = () => {
      this.setState(state => ({
        theme: {
          color: state.theme.color === 'light' ? 'dark' : 'light', // light,dark
          size: state.theme.color === 'light' ? 'large' : 'middle', // small, middle, large
          toggleTheme: this.toggleTheme,
        }
      }));
    };

    this.state = {
      theme: {
        color: 'light',  // light,dark
        size: 'middle',  // small, middle, large
        toggleTheme: this.toggleTheme,
      }
    }
  }

  render() {
    const { theme } = this.state;
    return (
      <div>
        {/* 使用定义的 context 的 provider 做父容器,value 是传递的值, 
        如果需要传多个值,可以用对象封装一下 */}
        <ThemeContext.Provider value={theme}>
          <IntermediaryComponents />
        </ThemeContext.Provider>
        <hr />
      </div>
    )
  }
}

export default Context;

 

3.IntermediaryComponents.tsx

该部分代码与 示例 4.1 中一致。

 

4.ThemeConsumerComponents.tsx

 
调用 context 环境中的 theme.toggleTheme() 来实现主题的修改。

import React from 'react'

import { ThemeContext } from './themeContext';

class ThemeConsumerComp extends React.Component {
  
  render() {
    return (
      <ThemeContext.Consumer>
        {
          (theme) => {
            return (
              <>
                <p>顶层组件定义的主题:颜色为{theme.color}, 大小为{theme.size}</p>
                <button onClick={() => { theme.toggleTheme(); }}>
                	在消费组件中更改主题按钮
                </button>
              </>
            )
          }
        }
      </ThemeContext.Consumer>
    )
  }
}

export default ThemeConsumerComp;

 

5.消费多个 context

 
为了确保 context 快速进行重渲染,React 需要使每一个 consumers 组件的 context 在组件树中成为一个单独的节点。

<ThemeContext.Provider value={theme}>
  <UserContext.Provider value={signedInUser}>
    <Layout />
    </UserContext.Provider>
</ThemeContext.Provider>
  • 5
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
ReactContext是一种在组件之间共享数据的方法。它通过创建一个context对象,其中包含一个Provider和Consumer组件来实现。 Provider组件用于提供共享的数据,而Consumer组件用于获取这些共享的数据。在Provider组件内部,可以通过value属性来传递数据。而在Consumer组件内部,通过函数的方式获取这些数据。 下面是一个使用Context的例子: connect函数接受一个mapState函数作为参数,该函数用于将context的值映射到组件的props中。 总结来说,ReactContext提供了一种在组件之间共享数据的方式,可以通过Provider组件提供数据,再通过Consumer组件获取数据。同时,通过封装Consumer组件的方式,可以简化使用过程。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [React中的context](https://blog.csdn.net/sxm666666/article/details/115704805)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* [Reactcontext上下文详解](https://blog.csdn.net/astonishqft/article/details/82868126)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值