我的掘金地址:https://juejin.im/post/5c9b10…
1. Context
关于 context 官网文档有如下的描述:
If you want your application to be stable, don’t use context. It is an experimental API and it is likely to break in future releases of React.
If you aren’t familiar with state management libraries like Redux or MobX, don’t use context.
If you aren’t an experienced React developer, don’t use context. There is usually a better way to implement functionality just using props and state.
综上所述就是不要使用 context 这个 API。虽然说不要用,但是我们也是要了解下这个 API 到底是干嘛的, 毕竟有些优秀的库都是通过这个 API 实现而来,如:React-Redux。
简单了解 context 的作用就是在某个父组件中定义一个全局状态,这个状态可以在该父组件下的所有子组件中跨级传递共享。目前有两个版本分别是 16.x 之前和 16.x 之后的版本。
2. 老版本的 Context
在老版本中有如下几个方法:
getChildContext: 在父组件中声明一个函数,返回的结果是一个对象,这个对象就是 context,可以对子组件进行共享的状态。
childContextTypes: 在父组件中声明,执行 context 中的数据类型,如果不指定会产生错误。
contextTypes: 在子孙组件中进行声明,指定要接受 context 中哪些数据类型。
Tip: React.PropTypes has moved into a different package since React v15.5. Please use the prop-types library instead to define contextTypes. 如上,react15.5 已经弃用 React.PropTypes,需要安装 prop-types 库。
看个小例子:
// 父组件
import React from ‘react’
import DemoSun from ‘./componets/DemoSun’
import propTyps from ‘prop-types’
class Demo extends React.Component {
getChildContext() {
return {
color: ‘red’
}
}
render() {
return (
<div>
DEMO
我是什么颜色的太阳:<DemoSun />
</div>
)
}
}
Demo.childContextTypes = {
color: propTyps.string
}
export default Demo
// 子组件
import React from ‘react’
import propTyps from ‘prop-types’
class DemoSun extends React.Component {
render() {
return (
<div>
DemoSun
{this.context.color}
</div>
)
}
}
DemoSun.contextTypes = {
color: propTyps.string
}
export default DemoSun
结果如下,子组件可以获取 context 中的 color 的值。
如果 contextTypes 定义在某个组件中,则这个组件的生命周期函数中会增加一个参数:
constructor(props, context)
componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componentWillUpdate(nextProps, nextState, nextContext)
componentDidUpdate(prevProps, prevState, prevContext)
如果在无状态组件中使用 context 则如下:
const PropTypes = require(‘prop-types’);
const Button = ({children}, context) =>
<button style={{background: context.color}}>
{children}
</button>;
Button.contextTypes = {color: PropTypes.string};
关于老版的 context 就介绍到此,来关注下新版本的 context。
3. 新版本的 Context
新版本中使用 Provider 和 Consumer 模式,在顶层 Provider 中传入 value,在子孙中的 Consumer 中获取该值,并且能够传递函数,用来修改 context。
React.createContext(args):
const Mycontext = React.createContext(defaultValue)
新版的是通过该方法初始化一个 context 对象。当 React 渲染了一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配 Provider 中读取到当前的 context 值。只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。
Context.Provider
<Mycontext.Provider value={/* 某个值 */}></Mycontext.Provider>
每个 Context 对象都会返回一个 Provider 组件。它允许消费组件订阅 context 变化。其有一个 value 属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系,多个 Provider 也可以嵌套使用,里层的会覆盖外层数据。
当 Provider 的 value 值发生变化时,它内部的所有消费者组件都会重新渲染。Provider 及其内部 consumer 组件都不受 shouldComponentUpdate 函数的影响,无论 shouldComponentUpdate 返回 true 或者 false,因此当 consumer 组件在其祖先组件退出更新的情况下也可以更新。
Class.contexType
挂载在 class 上的 contextType 静态属性会被赋值为一个由 React.createContext() 的 Context 对象。这能让你使用 this.context 来消费最近 Context 上的那个值。你可以在任何生命周期中访问它,包括在 render 中。
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
}
componentDidUpdate() {
let value = this.context;
/* … */
}
componentWillUnmount() {
let value = this.context;
/* … */
}
render() {
let value = this.context;
/* 基于 MyContext 组件的值进行渲染 */
}
}
MyClass.contextType = MyContext;
Context.Consumer
<MyContext.Consumer>
{value=>/* 基于 context 值进行渲染 */}
</MyContext.Consumer>
这里,React 组件也可以订阅到 context 变更。这能让你在函数式组件中完成订阅 context。Consumer 的 children 必须是一个函数。
这需要函数作为子元素这种做法。这个函数接受当前的 context 值,返回一个 react 节点。传递给函数的 value 值等同于往上组件树离这个 context 最近的 Provider 提供的 value 值。如果没有对应的 Provider,value 参数等于传递给 createContext() 的 defaultValue。
4. 注意事项
context 会使用参考标识来决定何时进行渲染。这样就会当 provider 的父组件进行重新渲染时,可能会在 consumer 组件中触发意外的渲染。如下:
class App extends React.Componenet{
render() {
return (
<Provider value={{text: ‘text’}}>
<Demo />
</Provider>
)
}
}
如上,每次 value 都会创建一个新的对象。为了避免这种情况,我们可以将其提出到 state 中进行管理。
class App extends React.Componenet{
constructor(props) {
super(props)
this.state = {
text: ‘text’
}
}
render() {
return (
<Provider value={{text: this.state.text}}>
<Demo />
</Provider>
)
}
}