React中Context的API
我的掘金地址: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> ) } }