Context 用法汇总 + 注意点
前言:
父子组件间传递数据,我们可以通过props。但是跨多个组件传递数据时,我们需要一层层往下传递props,代码冗余的同时也降低了可读性。
Context正可以解决这个问题,它可以跨组件提供数据、触发更新,不需要层层下发。
但同时,由于Context的值传递不是链式的,对于Context值更新的触发源的追踪会比较困难。整体的维护成本也会增加。
1.context创建以及取数.jsx
涉及到的Context如下:
React.CreateContext创建一个Context对象Context.Provider提供context数据,函数式、类式组件通用Context.Consumer消费context数据,函数式、类式组件通用- 函数式组件中,
useContext消费context数据 - 类式组件中,
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使用时需要注意:
DemoContext.Provider通过value提供的对象发生变化时,会触发依赖该context的所有组件的刷新- 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也是基于发布订阅模式实现