React Context 是一个强大的组件广播功能,但是也不能随意滥用,它并不适合一些简单的组件通信,如果杀鸡用牛刀可能会起到相反的效果
什么时候使用
- 当你要在组件树中共享一些全局数据的时候,比如要让你的应用风格可配置,或则语言可选 那么像这种
theme或language的属性就可以托管给Context
案例
先考虑不用context 的情况
// 假如现在你的组件树中有多个按钮需要随时切换背景颜色
// 你封装的按钮
class ThemeButton extends React.Component {
render() {
return <button style={{backgroundColor: this.props.theme}}>ThemeButton</button>
}
}
// 在某个组件中使用了这个按钮
function Toolbar(props) {
return (
<div>
<ThemeButton theme={props.theme}/>
</div>
)
}
// 为了能够在同一控制所有按钮的样式 你可以在App.js 中传入样式
class App extends Component {
render() {
return (
<div>
<Toolbar theme="blue"/>
</div>
);
}
}
你会发现 就一个简单的theme 属性要通过 App -> Toolbar -> ThemeButton ,如果嵌套的关系更加复杂,那么这种传递props的方式就不太合适了
用 Context 广播 来优化
// 声明一下我将所有组件写在一起只是方便演示,你可以将组件写在不同的文件中
//1. 首先你可以在src 下创建一个context的文件并创建index.js
import React from 'react'
export const ThemeContext = React.createContext('green')
之后你就可以使用这个导出的ThemeContext
import {ThemeContext} from '../context'
class ThemeButton extends React.Component {
static contextType = ThemeContext
render() {
return <button style={{backgroundColor: this.context}}>ThemeButton</button>
}
}
// 在某个组件中使用了这个按钮
function Toolbar(props) {
return (
<div>
<ThemeButton/>
</div>
)
}
// 为了能够在同一控制所有按钮的样式 你可以在App.js 中传入样式
class App extends Component {
render() {
return (
<ThemeContext.Provider value="blue">
<Toolbar/>
</ThemeContext>
);
}
}
如果你手动敲一遍上述的案例,你将发现Theme不在通过props逐层传递,而是通过Context 广播的方式,
我们来看看 createContext 这个Api
createContext 是 React 内置的方法,该方法返回一个对象,对象拥有两个属性分别是 Provider 和 Consumer。 这两个属性都是React 组件,通过这两个组件可以管理广播的播放,和接收,通过上述的案例可以发现我们并没有使用
Consumer这个组件 而只使用了Provider所以我们先分析这个组件到底做了什么
- 首先你要用
Provider包裹你的根组件,这样你的组件树都在广播的范围内了 - 定义广播的内容,Provider 接收一个value 的属性用来定义广播的内容,当然这个属性不是必须写的,默认广播的内容你可以直接传给
createContext这个方法 - 在要订阅广播的组将中将
contextType这个静态属性声明为你定义的上下文对象 - 之后你的类组件中将拥有context这个属性,组件会从组件树中离自身最近的那个匹配的 Provider 中读取值并赋给context,只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。
- 一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
- 当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。
- 你可以在订阅广播的组件中的任意生命周期中使用context 这个属性
其上就是广播的基本使用,案例中接收广播局限在类式组件中,除此之外
Comsumer还未使用过,那么想要在函数中接收广播,和了解Comsumer的使用,请继续阅读本章。
什么时候不该使用
Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据, 不过对于一些复用性较强的组件还是老老实实使用props,如果你使用 Context 会极大的提高组件的耦合,降低组件的复用性。
解决方案是
- 你可以在复用性强的组件的父级接收广播然后通过 props 传给该组件
- 如果数据不是全局数据, 为了避免过多的props传递,同时又不想使用广播,那么你也可以在父级将底层的组件组装好后,通过props 向下传递。不过这种组件的控制反转会使得父级变得复杂,同时底层的组件将变得局限起来,具体如何使用还要根据情况考虑
Context.Consumer
回归上述没有解决的问题,如果你要在函数组件中订阅广播可以像如下这么做
案例
import {ThemeContext} from '../context'
function ThemeButton () {
return (
<ThemeContext.Consumer>
{value=>(
<button style={{backgroundColor: value}}>ThemeButton</button>
)}
</ThemeContext.Consumer>
)
}
// 在某个组件中使用了这个按钮
function Toolbar(props) {
return (
<div>
<ThemeButton/>
</div>
)
}
// 为了能够在同一控制所有按钮的样式 你可以在App.js 中传入样式
function {
return (
<ThemeContext.Provider value="blue">
<Toolbar/>
</ThemeContext>
);
}
Consumer 这个组件可以使你的函数式组件也能订阅广播的内容,就像上述案例一样,这种方法需要一个函数作为子元素。这个函数接收当前的 context 值,并返回一个 React 节点。传递给函数的 value 值等价于组件树上方离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等同于传递给 createContext() 的 defaultValue。
不过通过 useContex hook 可以更优雅的订阅广播的内容
import {ThemeContext} from '../context'
import {useContext} from 'react;
function ThemeButton () {
const context = useContext(ThemeContext)
return (
<button style={{backgroundColor: value}}>ThemeButton</button>
)
}
广播的其他内容
到此为止如果你已经掌握上述的内容,那么你就基本掌握广播的使用,下面将介绍一些扩展的内容
- Context.displayName
为了优化 开发工具,你可以给你的广播起一个名字这样你就可以在 react devtools 中轻松的找到它
案例
import React from 'react'
const ThemeContext = React.createContext('green')
ThemeContext.displayName = 'ThemeCont';
export ThemeContext
- 如果你要动态广播内容
如果你要动态广播内容,你就不能写死数据,你可以将数据缓存在根组件的state 中,然后再传给 Provider 的 value
- 在嵌套组件中更新 Context
此时你的广播必须是动态的,你可以在你的广播内容中添加一个方法,这个方法定义在根组件中用于修改根组件的state,此时你在广播state中的内容和这个方法,这样你的子组件就可以修改广播内容
- 消费多个Context
为了确保 context 快速进行重渲染,React 需要使每一个 consumers 组件的 context 在组件树中成为一个单独的节点。
// Theme context,默认的 theme 是 “light” 值
const ThemeContext = React.createContext('light');
// 用户登录 context
const UserContext = React.createContext({
name: 'Guest',
});
class App extends React.Component {
render() {
const {signedInUser, theme} = this.props;
// 提供初始 context 值的 App 组件
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
}
function Layout() {
return (
<div>
<Sidebar />
<Content />
</div>
);
}
// 一个组件可能会消费多个 context
function Content() {
return (
<ThemeContext.Consumer>
{theme => (
<UserContext.Consumer>
{user => (
<ProfilePage user={user} theme={theme} />
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
如果两个或者更多的 context 值经常被一起使用,那你可能要考虑一下另外创建你自己的渲染组件,以提供这些值。
注意
因为 context 会使用参考标识(reference identity)来决定何时进行渲染,这里可能会有一些陷阱,当 provider 的父组件进行重渲染时,可能会在 consumers 组件中触发意外的渲染。举个例子,当每一次 Provider 重渲染时,以下的代码会重渲染所有下面的 consumers 组件,因为 value 属性总是被赋值为新的对象:
class App extends React.Component {
render() {
return (
<MyContext.Provider value={{something: 'something'}}>
<Toolbar />
</MyContext.Provider>
);
}
}
优化上述情况
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的使用场景、API、最佳实践及注意事项。阐述了何时使用和不适用Context,提供了Provider和Consumer组件的用法,并讨论了动态更新Context、消费多个Context以及避免渲染陷阱的策略。
1569

被折叠的 条评论
为什么被折叠?



