关于react-context的官方文档
https://reactjs.org/docs/context.html
起因
很多同学认为一旦父子组件之间层级过深,多层级通讯时候第一反应就是要用vuex和redux,但往往杀鸡用了宰牛刀。但是vue和react中提供了很多方法去达到数据共享的效果,而不必显式地通过组件树的逐层传递 props。
比如在vue中provide 和 inject
1、这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。
2、provide提供数据,多层子组件 向上层寻找,只要找到 就不在向上层寻找了。
3、inject 向子组件注入数据;
vue中的mixin和extend也可以做到相关功能,这里就不做详细赘述,需要了解的同学可以自行去观看。
下面回到本篇文章的正题react-context,React官网的高级指引中指出了Context,优雅的解决这个问题。
context含义
前端同学都知道在js中的context指的是执行上下文,this指向谁,谁就是当前的执行上下文。
而react-context是什么呢?react-context官网中第一句就给出了解释:
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
意思便是context能够不必显式地通过组件树的逐层传递 props,可以跨层级进行数据传递。
其实质为跨层级的组件搭建一座桥梁。
context Api
React context的API有两个版本,React16.x之前的是老版本的context,之后的是新版本的context。
先简单说下老版本,之前也是实验性的存在,我觉得不太好用。生产者需要声明childContextTypes对象,对类数据类型进行校验,不提供就会报错。
具体的就不细说,需要了解的同学自行查看。
下面回到16.x版本的context,Context是16.3.0正式确定的API
学习context首先需要了解其三个核心的api: React.createContext()、Provider(发布者)、Consumer(订阅者)
React.createContext()和Provider
createContext()创建一个 Context 对象。
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
Provider 接收一个value
属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
当 Provider 的value
值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于shouldComponentUpdate
函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。
value的值自然是可以动态变更的,并且会传递给子组件中,点击变更value按钮会发现页面上的三个tomorrow变为三个yesterday。
import React, { useContext } from 'react'
const ThemeContext = React.createContext('today')
class App extends React.Component {
state = {
user: 'tomorrow'
}
change = () => {
this.setState({
user: 'yesterday'
})
}
render() {
const { user } = this.state
// createContext默认值是“today”。
// 无论多深,任何组件都能读取这个值。
// 这里Provider 把value值 “tomorrow” 作为当前的值传递下去。
return (
<ThemeContext.Provider value={ user }>
<div onClick={ this.change }>变更value</div>
<Toolbar />
</ThemeContext.Provider>
);
}
}
这里是react-hook函数式写法,用到react-hook的API:useContext获取context的值
挂载在 class 上的contextType
属性会被重赋值为一个由React.createContext()
创建的 Context 对象。这能让你使用this.context
来消费最近 Context 上的那个值。你可以在任何生命周期中访问到它,包括 render 函数中
function Toolbar() {
const context = useContext(ThemeContext)
return (
<div>
<div>{context}</div>
<Head />
</div>
);
}
Consumer
通过ThemeContext的属性Consumer消费用户数据
Consumer的子组件必须是一个function,通过function的参数接收顶层传入的数据
任何订阅者(Consumer)都可以直接修改context,这会导致后续的订阅者获取到修改后的context值,但这显然是不可取的。
如果需要修改,应该统一由发布者(Provider)修改,也就是类似APP组件中变更value按钮
class Head extends React.Component {
render() {
return (
<ThemeContext.Consumer>
{
context => (
<div>
<div>{this.context}</div>
<Title />
</div>
)
}
</ThemeContext.Consumer>
)
}
}
最后的子组件显示context内容
context还有个Api:Context.displayName
想了解的同学可以去官网看一下
class Title extends React.Component {
static contextType = ThemeContext
render() {
return (
<div>
{this.context}
</div>
)
}
}
注意事项
在 provider 的父组件进行重渲染时,可能会在 Consumers 组件中触发意外的渲染。
下面例子:对value直接赋值的时候就会触发这种不必要的渲染
const ThemeContext = React.createContext();
class App extends React.Component {
render() {
return (
<ThemeContext.Provider value={ {name: '666'} }>
<div onClick={ this.change }>变更value</div>
<Toolbar />
</ThemeContext.Provider>
);
}
}
可以通过把value值放在state中解决这种问题
const ThemeContext = React.createContext();
class App extends React.Component {
state = {
name: '666'
}
change = () => {
this.setState({
user: 'yesterday'
})
}
render() {
const { user } = this.state
return (
<ThemeContext.Provider value={ user }>
<div onClick={ this.change }>变更value</div>
<Toolbar />
</ThemeContext.Provider>
);
}
}
似曾相识的context -- redux
有没有发现redux的注入和context的注入非常相似。
通过React Developer Tools在浏览器很容易发现
react-redux实际上是通过Provider组件和connect方法进行连接react和redux,那么他们到底本质上就是通过Context来传递数据
以上就是个人对react-context学习的一点总结。context虽然轻便,但是官方还是没有大面积的去推广,还是存在些问题,毕竟和强大的react-redux对比,没有很好的管理体系,广泛认知度也比较低。但是凡事没有绝对好坏,只有适不适合。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。