context典型应用场景
context,顾名思义就是上下文的意思, 在一个典型的react应用中,数据是通过props属性自上而下(由父及子)传递的,但这种做法对于某些属性而言是很繁琐的(例如UI主题),比如平时应用的主题色是蓝色,某次节日需要更换主题色,大到页面,小到孙子组件里面的button都得换色。此时若在最顶层定义然后往下逐层去传递的话非常繁琐。这个时候使用组件得跨层级通信比较方便,context提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递props。
react使用context实现祖代组件向后代组件跨层级传值,类似于vue中的provide/inject。
React.createContext
创建一个Context对象。当React渲染一个订阅了这个Context对象的组件,这个组件会从组件树中离自身最近的那个匹配的Provider中读取到当前的context值。
React.Provider
Provider接收一个value属性,传递给消费组件,允许消费组件订阅context的变化。一个Provider可以和多个消费组件有对应关系。多个Provider也可以嵌套使用,里层的会覆盖外层的数据。
当 Provider 的 value 值发⽣变化时,它内部的所有消费组 件都会重新渲染。Provider 及其内部 consumer 组件都不受 制于 shouldComponentUpdate 函数,因此当 consumer 组 件在其祖先组件退出更新的情况下也能更新。
Class.ContextType
挂载在 class 上的 contextType 属性会被重赋值为⼀个由 React.createContext() 创建的 Context 对象。这能让你 使⽤ this.context 来消费最近 Context 上的那个值。你可 以在任何⽣命周期中访问到它,包括 render 函数中。
你只通过该 API 订阅单⼀ context。
Context.Consumer
这⾥,React 组件也可以订阅到 context 变更。这能让你在函 数式组件中完成订阅 context。 开课吧web全栈架构师 这个函数接收当前的 context 值,返回⼀个 React 节点。传 递给函数的 value 值等同于往上组件树离这个 context 最近 的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给 createContext() 的 defaultValue。
具体使用Context
消费单个Context
以实现共享主题色需求为例。
步骤:创建Context => 获取Provider和Consumer => Provider提供 值 => Consumer消费值。
import React, { Component } from "react";
import { ThemeProvider } from "../themeContext";
import ContextTypePage from "./ContextTypePage";
import ConsumerPage from "./ConsumerPage";
class ContextPage extends Component {
constructor(props) {
super(props);
this.state = {
theme: {
themeColor: "red",
},
};
}
changeColor = () => {
const { themeColor } = this.state.theme;
this.setState({
theme: {
themeColor: themeColor === "red" ? "green" : "red",
},
});
};
render() {
const { theme } = this.state;
return (
<div className="App">
{/* 组件跨层级通信 */}
<button onClick={this.changeColor}>change color</button>
{/* 如果把这⾥的MyProvider注释掉,
ContextTypePage和ConsumerPage⾥将取不到theme值,⽽
取默认值pink */}
<ThemeProvider value={theme}>
<ContextTypePage />
<ConsumerPage />
</ThemeProvider>
</div>
);
}
}
export default ContextPage;
themeContext.js
import React from "react";
// 创建context 农民种菜, 如果没有匹配到Provider,取值默认值
export const ThemeContext = React.createContext({themeColor: "pink"});
// 接收者 批发商批发菜
export const ThemeProvider = ThemeContext.Provider;
//消费者 吃菜
export const ThemeConsumer = ThemeContext.Consumer;
ContextTypePage.js
import React, { Component } from "react";
import { ThemeContext } from "../themeContext";
export default class ContextTypePage extends Component {
static contextType = ThemeContext;
render() {
console.log("ContextTypePage", this.context); //
const { themeColor } = this.context;
return (
<div className="border">
<h3 className={themeColor}>ContextTypePage</h3>
</div>
);
}
}
ConsumerPage.js
import React, { Component } from "react";
import { ThemeConsumer } from "../themeContext";
export default class ConsumerPage extends Component {
render() {
return (
<div className="border">
<h3>ConsumerPage</h3>
<ThemeConsumer>{(ctx) => <HandleTabBar {...ctx} />}</ThemeConsumer>
</div>
);
}
}
function HandleTabBar({ themeColor }) {
console.log("themeColor", themeColor); //
// 补充ContextPage.js
// pages/MultipleContextPage.js
return <div className={themeColor}>⽂本</div>;
}
消费多个Context
补充ContextPage.js
<ThemeProvider value={theme}>
<ContextTypePage />
<ConsumerPage />
{/*多个Context */}
<UserProvider value={user}>
<MultipleContextsPage />
</UserProvider>
</ThemeProvider>
MultipleContextPage.js
import React, { Component } from "react";
import { ThemeConsumer } from "../themeContext";
import { UserConsumer } from "../userContext";
export default class MultipleContextsPage extends Component {
render() {
return (
<div className="border">
<h3>MultipleContextsPage</h3>
<ThemeConsumer>
{(theme) => (
<UserConsumer>
{(user) => <div className={theme.themeColor}>{user.name}</div>}
</UserConsumer>
)}
</ThemeConsumer>
</div>
);
}
}
如果两个或者更多的 context 值经常被⼀起使⽤,那你可能要考虑⼀下另外创建你⾃⼰的渲染组件,以提供这些值。
注意事项
因为 context 会使⽤参考标识(reference identity)来决定 何时进⾏渲染,这⾥可能会有⼀些陷阱,当 provider 的⽗组 件进⾏重渲染时,可能会在 consumers 组件中触发意外的渲 染。举个例⼦,当每⼀次 Provider 重渲染时,以下的代码会 重渲染所有下⾯的 consumers 组件,因为 value 属性总是被赋值为新的对象:
class App extends React.Component {
render() {
return (
<Provider value={{ something: "something" }}>
<Toolbar />
</Provider>
);
}
}
为了防⽌这种情况,将 value 状态提升到⽗节点的 state ⾥:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: { something: "something" },
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}
小结
在React的官⽅⽂档中,Context被归类为⾼级部分 (Advanced),属于React的⾼级API,但官⽅并不建议在稳定 版的App中使⽤Context。
不过,这并⾮意味着我们不需要关注Context。事实上,很 多优秀的React组件都通过Context来完成⾃⼰的功能,⽐如 react-redux的,就是通过Context提供⼀个 全局态的store,路由组件react-router通过Context管理路 由状态等等。在React组件开发中,如果⽤好Context,可以 让你的组件变得强⼤,⽽且灵活。
函数组件中可以通过useContext引⼊上下⽂,后⾯hooks 部分介绍。