React-Context用法汇总 +注意点

Context 用法汇总 + 注意点

前言

父子组件间传递数据,我们可以通过props。但是跨多个组件传递数据时,我们需要一层层往下传递props,代码冗余的同时也降低了可读性。
Context正可以解决这个问题,它可以跨组件提供数据、触发更新,不需要层层下发。
但同时,由于Context的值传递不是链式的,对于Context值更新的触发源的追踪会比较困难。整体的维护成本也会增加。

1.context创建以及取数.jsx

涉及到的Context如下:

  1. React.CreateContext 创建一个Context对象
  2. Context.Provider 提供context数据,函数式、类式组件通用
  3. Context.Consumer 消费context数据,函数式、类式组件通用
  4. 函数式组件中,useContext 消费context数据
  5. 类式组件中,static contextType声明 + this.context 消费context数据

创建Context + 取数Demo代码(可直接运行查看)

jsvascript 复制代码
import React, { Component, useContext } from 'react'

/**
 * 一、context创建以及取数
 * 涉及API:
 * 1、React.createContext()
 * 2. Context.Provider 提供原始数据
 * 3. Context.Consumer 消费原始数据
 * 4. static contextType = DemoContext; 类组件中取数
 * 5. useContext 函数式组件中取数
 */
const DemoContext = React.createContext({
  name: 'zhangsan',
  age: 0,
});

export default class ContextClassDemo extends Component {
  render() {
    return (
      <div>
        {/* 通过Provider提供原始数据 */}
        <DemoContext.Provider value={{ name: 'lisi', age: 18 }}>
          {/* 类式组件访问context */}
          <ClassChildComponent />
          {/* 函数式组件访问context */}
          <FuncChildComponent />
        </DemoContext.Provider>
      </div>
    )
  }
}

// 类组件访问context
class ClassChildComponent extends Component {

  // 1、使用this.context、constructor中访问都需要声明,不然context是空
  // 2、使用Consumer不需要声明
  static contextType = DemoContext;

  constructor(props, context) {
    super(props, context);

    console.log(context, this.context);
  }

  render() {
    const { name, age } = this.context;

    return (
      <div>
        <div>类式组件方案</div>
        <div>用法一 this.context访问</div>
        <div>需要在类中声明 static contextType = DemoContext;</div>
        <div>
          <p>Name: {name}</p>
          <p>Age: {age}</p>
        </div>

        <div>用法二 Consumer组件访问</div>
        <DemoContext.Consumer>
          {value => (
            <div>
              <p>Name: {value.name}</p>
              <p>Age: {value.age}</p>
            </div>
          )}
        </DemoContext.Consumer>
      </div>
    )
  }
}

// 函数组件访问context
function FuncChildComponent() {
  const context = useContext(DemoContext);

  return (
    <div>
      <div>函数式组件方案</div>
      <div>用法一 useContext访问</div>
      <div>
        <p>Name: {context.name}</p>
        <p>Age: {context.age}</p>
      </div>

      <div>用法二 Consumer组件访问</div>
      {/* Consumer方法访问函数式组件、类组件中都适用 */}
      <DemoContext.Consumer>
        {value => (
          <div>
            <p>Name: {value.name}</p>
            <p>Age: {value.age}</p>
          </div>
        )}
      </DemoContext.Consumer>
    </div>
  )
}

2.数据改变-父组件类式组件.jsx

注意点:
DemoContext.Provider判断是否有数据变化是浅比较,只改变传入数据的属性是不会生效的。

例如:

jsvascript 复制代码
this.data = {name: '张三'}
...(省略)
<DemoContext.Provider value={this.data}>
...(省略)
// 每次改变data的属性时,不会触发刷新
this.data.name = 'lisi';

只有当data变化时才会触发:

jsvascript 复制代码
this.data = {name: '张三'}
...(省略)
<DemoContext.Provider value={this.data}>
...(省略)
// data此时指向一个新的对象
this.data = {name: 'lisi'};

数据改变-父组件类式组件

jsvascript 复制代码
import React, { Component, useContext } from 'react'

/**
 * 二、数据改变-父组件类式组件,数据变更
 */
const DemoContext = React.createContext({
  name: 'zhangsan',
  age: 0,
});

export default class ContextClassDataChangeDemo extends Component {
  constructor(props) {
    super(props);

    this.state = {
      name: 'zhangsan',
      age: 0,
    }
  }

  // 年龄+1
  handleAgeAdd = () => {
    this.setState({
      age: this.state.age + 1,
    })
  }

  // 姓名变更
  handleNameChange = () => {
    this.setState({
      name: this.state.name == 'lisi' ? 'zhangsan' : 'lisi',
    })
  }

  render() {
    return (
      <div>
        <div>数据改变</div>
        <div onClick={this.handleAgeAdd}>年龄+1</div>
        <div onClick={this.handleNameChange}>姓名变更</div>

        {/* 通过Provider提供原始数据 */}
        <DemoContext.Provider value={this.state}>
          {/* 类式组件访问context */}
          <ClassChildComponent />
          {/* 函数式组件访问context */}
          <FuncChildComponent />
        </DemoContext.Provider>
      </div>
    )
  }
}

// 类组件访问context
class ClassChildComponent extends Component {

  // 1、使用this.context、constructor中访问都需要声明,不然context是空
  // 2、使用Consumer不需要声明
  static contextType = DemoContext;

  constructor(props, context) {
    super(props, context);

    console.log(context, this.context);
  }

  render() {
    const { name, age } = this.context;

    return (
      <div>
        <div>类式组件方案</div>
        <div>用法一 this.context访问</div>
        <div>需要在类中声明 static contextType = DemoContext;</div>
        <div>
          <p>Name: {name}</p>
          <p>Age: {age}</p>
        </div>

        <div>用法二 Consumer组件访问</div>
        <DemoContext.Consumer>
          {value => (
            <div>
              <p>Name: {value.name}</p>
              <p>Age: {value.age}</p>
            </div>
          )}
        </DemoContext.Consumer>
      </div>
    )
  }
}

// 函数组件访问context
function FuncChildComponent() {
  const context = useContext(DemoContext);

  return (
    <div>
      <div>函数式组件方案</div>
      <div>用法一 useContext访问</div>
      <div>
        <p>Name: {context.name}</p>
        <p>Age: {context.age}</p>
      </div>

      <div>用法二 Consumer组件访问</div>
      {/* Consumer方法访问函数式组件、类组件中都适用 */}
      <DemoContext.Consumer>
        {value => (
          <div>
            <p>Name: {value.name}</p>
            <p>Age: {value.age}</p>
          </div>
        )}
      </DemoContext.Consumer>
    </div>
  )
}

3.数据改变-父组件函数式组件.jsx

数据改变-父组件函数式组件

jsvascript 复制代码
import React, { Component, useContext } from 'react'

/**
 * React Context 数据更新演示
 * 核心逻辑:
 * 1. 父类组件维护Context的数据源(state)
 * 2. 父组件通过setState更新state,Provider的value随之变化
 * 3. 所有消费该Context的子组件会自动重新渲染,获取最新数据
 */

// 1. 创建Context对象,设置默认值(无Provider时生效)
const DemoContext = React.createContext({
  name: '默认名称-zhangsan',
  age: 0,
});

// 根组件:维护Context数据源,并提供更新方法
export default class ContextClassDataChangeDemo extends Component {
  constructor(props) {
    super(props);
    // 2. 父组件state作为Context的数据源
    this.state = {
      name: 'zhangsan',
      age: 0,
    }
  }

  // 年龄+1的更新方法(箭头函数避免this指向问题)
  handleAgeAdd = () => {
    // 注意:更新依赖旧值时,推荐用函数式setState(避免闭包陷阱)
    this.setState(prevState => ({
      age: prevState.age + 1,
    }));
    console.log('年龄已更新:', this.state.age + 1);
  }

  // 姓名切换(zhangsan ↔ lisi)
  handleNameChange = () => {
    this.setState(prevState => ({
      name: prevState.name === 'lisi' ? 'zhangsan' : 'lisi',
    }));
    console.log('姓名已切换');
  }

  render() {
    return (
      <div style={{ padding: '20px', border: '1px solid #eee' }}>
        <h3>Context 数据更新演示(类组件数据源)</h3>
        {/* 交互按钮(增加cursor指针,提升体验) */}
        <div 
          onClick={this.handleAgeAdd}
          style={{ margin: '10px 0', padding: '8px', background: '#e6f7ff', cursor: 'pointer' }}
        >
          点击年龄+1(当前:{this.state.age})
        </div>
        <div 
          onClick={this.handleNameChange}
          style={{ margin: '10px 0', padding: '8px', background: '#f6ffed', cursor: 'pointer' }}
        >
          点击切换姓名(当前:{this.state.name})
        </div>

        {/* 3. Provider的value绑定父组件state,state更新则value更新 */}
        <DemoContext.Provider value={this.state}>
          <hr />
          {/* 类组件消费更新后的Context */}
          <ClassChildComponent />
          <hr />
          {/* 函数组件消费更新后的Context */}
          <FuncChildComponent />
        </DemoContext.Provider>
      </div>
    )
  }
}

// --------------- 类组件消费更新后的Context ---------------
class ClassChildComponent extends Component {
  // 绑定Context,通过this.context访问最新数据
  static contextType = DemoContext;

  // 可选:监听Context更新(生命周期钩子)
  componentDidUpdate(prevProps, prevState) {
    // 对比前后context数据,确认更新
    if (this.context.age !== prevState.age || this.context.name !== prevState.name) {
      console.log('类组件Context数据已更新:', this.context);
    }
  }

  render() {
    const { name, age } = this.context;

    return (
      <div style={{ margin: '10px 0', padding: '10px', background: '#f5f5f5' }}>
        <h4>类组件 - 实时展示Context数据</h4>
        
        <div>✅ 方式1:this.context访问(自动更新)</div>
        <div>姓名:{name} | 年龄:{age}</div>

        <div style={{ marginTop: '10px' }}>✅ 方式2:Consumer访问(自动更新)</div>
        <DemoContext.Consumer>
          {(value) => (
            <div>姓名:{value.name} | 年龄:{value.age}</div>
          )}
        </DemoContext.Consumer>
      </div>
    )
  }
}

// --------------- 函数组件消费更新后的Context ---------------
function FuncChildComponent() {
  // useContext会自动监听Context变化,数据更新时组件重新渲染
  const context = useContext(DemoContext);

  return (
    <div style={{ margin: '10px 0', padding: '10px', background: '#f0f8ff' }}>
      <h4>函数组件 - 实时展示Context数据</h4>
      
      <div>✅ 方式1:useContext访问(自动更新)</div>
      <div>姓名:{context.name} | 年龄:{context.age}</div>

      <div style={{ marginTop: '10px' }}>✅ 方式2:Consumer访问(自动更新)</div>
      <DemoContext.Consumer>
        {(value) => (
          <div>姓名:{value.name} | 年龄:{value.age}</div>
        )}
      </DemoContext.Consumer>
    </div>
  )
}

4.context频繁刷新问题.jsx

涉及API:

  • 1、React.PureComponent
  • 2、shouldComponentUpdate

Context使用时需要注意:

  1. DemoContext.Provider 通过value提供的对象发生变化时,会触发依赖该context的所有组件的刷新
  2. Context触发的更新会跳过 shouldComponentUpdate 判断

因此,当value是通过 <DemoContext.Provider value={``{a, b, c}}> 这种方式提供时,需要额外注意,因为每次该组件刷新时,都会重新构建一个包含a、b、c的新对象,会触发依赖DemoContext的其他组件的刷新。

正常来说,PureComponent 当props没有变化时,不会触发刷新。

但是我们如果通过staticType绑定context,当demo中的身高变化时,类子组件的props没有任何变化,但是还是在控制台打印了'render'。

即使不是PureComponent,强制设置shouldComponentUpdate返回false,也会更新。

解决方案

第一步,可以通过useMemo将传入的value包裹起来,确保只有当需要的属性变化时,触发useMemo的更新,进而触发value变化时,触发子组件刷新。

第二部,拆分Context,避免Context过于臃肿,业务间耦合严重,尽量做到解耦
context频繁刷新问题 Demo

javascript 复制代码
import React, { Component, PureComponent, useContext, useState } from 'react'

/**
 * 四.数据改变-问题-子组件频繁刷新
 * 1、DemoContext.Provider 通过value提供的对象发生变化时,会触发用到该context的组件的刷新
 * 2、Context触发的更新会跳过 shouldComponentUpdate 判断
 * 综上,Provider value的变化会带来不必要的刷新
 * 
 * 涉及API:
 * 1、React.PureComponent
 * 2、shouldComponentUpdate
 */
const DemoContext = React.createContext({
  name: 'zhangsan',
  age: 0,
});

export default function ContextClassDataChangeDemo() {
  const [data, setData] = useState({
    name: 'zhangsan',
    age: 0,
  })
  
  const [height, setHeight] = useState(170)

  const addAge = () => {
    setData({
      ...data,
      age: data.age + 1,
    })
  }

  const changeName = () => {
    setData({
      ...data,
      name: data.name == 'lisi' ? 'zhangsan' : 'lisi',
    })
  }

  const incrementHeight = () => {
    setHeight(height + 1)
  }

  return <div>
    <div>
      <button onClick={addAge}>年龄+1</button>
      <button onClick={changeName}>姓名变更</button>
      <button onClick={incrementHeight}>身高+1</button>
    </div>

    <DemoContext.Provider value={{name: data.name, age: data.age}}>
      <ClassChildComponent />
      <FuncChildComponent height={height}/>
    </DemoContext.Provider>
  </div>
}

// 类组件访问context
class ClassChildComponent extends PureComponent {

  // 1、使用this.context、constructor中访问都需要声明,不然context是空
  // 2、使用Consumer不需要声明
  static contextType = DemoContext;

  constructor(props, context) {
    super(props, context);

    console.log(context, this.context);
  }

//   shouldComponentUpdate(nextProps, nextContext) {
//     // 返回false,可以观测到
//     // context数据变化时,视图更新了
//     // props.height变更的时候,视图无变化没变
//     // 结论:
//     //   context会忽略shouldComponentUpdate的结果,所以对于Context.Provider传入的value需要慎重对待,
//     //   频繁更新Contet会带来不必要的视图刷新
//     console.log('shouldComponentUpdate');
//     return false
//   }

  render() {
    const { name, age } = this.context;

    console.log('类-子组件 render触发',name, age);
    return (
      <div>
        <div>类式组件方案</div>
        <div>用法一 this.context访问</div>
        <div>需要在类中声明 static contextType = DemoContext;</div>
        <div>
          <p>Name: {name}</p>
          <p>Age: {age}</p>
        </div>

        <div>用法二 Consumer组件访问</div>
        <DemoContext.Consumer>
          {value => (
            <div>
              <p>Name: {value.name}</p>
              <p>Age: {value.age}</p>
            </div>
          )}
        </DemoContext.Consumer>
      </div>
    )
  }
}

// 函数组件访问context
function FuncChildComponent(props) {
  const context = useContext(DemoContext);

  return (
    <div>
      <div>函数式组件方案</div>
      <div>用法一 useContext访问</div>
      <div>
        <p>Name: {context.name}</p>
        <p>Age: {context.age}</p>
      </div>

      <div>用法二 Consumer组件访问</div>
      {/* Consumer方法访问函数式组件、类组件中都适用 */}
      <DemoContext.Consumer>
        {value => (
          <div>
            <p>Name: {value.name}</p>
            <p>Age: {value.age}</p>
          </div>
        )}
      </DemoContext.Consumer>

      <div>Height:{props.height}</div>
    </div>
  )
}

// TODO

// React 源码阅读中,预计Context也是基于发布订阅模式实现

相关推荐
徐同保3 小时前
python如何手动抛出异常
java·前端·python
极客小云3 小时前
【实时更新 | 2026年国内可用的npm镜像源/加速器配置大全(附测速方法)】
前端·npm·node.js
半兽先生3 小时前
告别 AI 乱写 Vue!用 vue-skills 构建前端智能编码标准
前端·vue.js·人工智能
木易 士心4 小时前
ESLint 全指南:从原理到实践,构建高质量的 JavaScript/TypeScript 代码
javascript·ubuntu·typescript
前端达人4 小时前
都2026年了,还在用Options API?Vue组合式API才是你该掌握的“正确姿势“
前端·javascript·vue.js·前端框架·ecmascript
Dxy12393102164 小时前
Python检查JSON格式错误的多种方法
前端·python·json
chao-Cyril5 小时前
从入门到进阶:前端开发的成长之路与实战感悟
前端·javascript·vue.js
shalou29015 小时前
Spring 核心技术解析【纯干货版】- Ⅶ:Spring 切面编程模块 Spring-Instrument 模块精讲
前端·数据库·spring
大时光5 小时前
js 封装 动画效果
前端