此次基于react文档15.8版本
在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context便提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
官网上的说明:
1.Context提供了一个无需为每层组件手动添加props, 就可以在组件树中进行数据传递的方法。
2.官网并没有给出明确Context是什么的说明, 只是给出了使用场景。(但可以打印一下组件的实例, 可以看出Context对象在组件实例中,展示形式为this.context= {}, Context对象被绑在父类继承过来的this上。但是读写context有多种方式,会在下边介绍)
3.官网并不推荐使用Context,因为会使得组件的复用性变差。
何时使用context?
import React from "react";
class GrandPar extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
let component = "GrandPar";
return (
<div
style = {
{background:"#eee",
border:"1px solid #000",
width:700,
alignItems:'center',
display:'flex',
flexDirection:'column'}
}
>
这里是GrandPar组件,本组件直接包含+间接包含了Par & Son组件<br/>
<Par component = {component}/>
</div>
);
}}
class Par extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
/**
* 此处注释的是与render种 <Son component = {component}/> 相对应,
* 代表向子组件传送props,可以有多种方式,一种是具体的属性名, 另一种是解构
*/
// let {
// component
// } = this.props;
return (
<div style = {
{background:"rgb(204, 133, 133)",
border:"1px solid #000",
width:500,
alignItems:'center',
display:'flex',
flexDirection:'column'}}
>
这里是Par组件直接包含Son组件<br/>
{/* <Son component = {component}/> */}
<Son {...this.props}/>
</div>
);
}}
class Son extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
let {
component
} = this.props;
console.log(this.props, "son组件的props包含了什么")
return (
<div style = {{background:"rgb(214, 198, 198)", border:"1px solid #000", width:300}}>
这里是Son组件<br/>
component是从最上层的组件传下来的<br/>
<br/>
{`component的值到底是多少呢?答案是: ${component}`}
</div>
);
}}
export default GrandPar;
由上代码可知当前是三层组件树,当Son组件 想使用GrandPar组件的component属性, 就必须要经过Par组件的传递(层层传递),Son组件才可以获取到component属性。
此时我们就可以使用context,是为了避免属性的层层传递就可以在Son组件中获取到GrandPar的属性, 无论组件树有多少层,只要是绑定了context对象的组件都可以获取到component属性, 如果没绑定就算包含在provider组件中, 也是获取不到的component属性值的, 除非使用Consumers, 就可以不用绑定context对象, 就可以获取到对应的父组件的值。context绑定与使用在下边会详解。
创建context的方式
const MyContext = React.createContext(defaultValue);
注意:
1.defaultValue只有当组件所处的树中没有匹配到Provider时, 其defaultValue参数才会生效。
2.将undefined传递给Provider时, 离自身最近的那个匹配Provider的组件的defaultValue并不会生效。
创建了一个Context对象, 当react渲染一个订阅了这个Context对象的组件, 这个组件便会从组件树中离自身最近的那个匹配Provider中读取到当前的context值。
context的传递方式 ProvderReact组件
<MyContext.Provider value = {'想要传递的值'} />import React from "react";export const themes = { value :"原来"};const myContext = React.createContext({value:"默认"});class Base extends React.Component { constructor(props, context) { super(props, context); console.log(props, context, "constructor") this.state = {}; } componentWillMount(){ console.log(this, "componentWillMount") } componentDidMount(){ console.log(this, "componentDidMount") } componentWillReceiveProps(nextProps, nextContent){ console.log(this, nextProps, nextContent, "componentWillReceiveProps") } shouldComponentUpdate(newProps, newState, nextContent) { console.log(this,newProps, newState, nextContent,"componentWillReceiveProps") return true; } componentWillUpdate(newProps, newState, nextContent) { console.log(newProps, newState, nextContent, "componentWillUpdate"); } componentDidUpdate(prevProps, prevState, preConext) { console.log(prevProps, prevState, preConext, 'componentDidUpdate') } render() { return ( <div> {this.context.value} <button {...this.props} onClick = {this.context.onChange}>快来点我</button> </div> ); }}Base.contextType = myContext;
class ConextComponent extends React.Component { constructor(props, context) { super(props, context); console.log(props, context) this.onChange = ()=>{ this.setState({ value:"改变后" }, ()=>{ console.log(this.state.value, "onChange") }) } this.state = { value :'未修改前', onChange : this.onChange }; } componentWillMount(){ console.log(this, "ConextComponent ------- componentWillMount") } componentDidMount(){ console.log(this, "ConextComponent -------componentDidMount") } componentWillReceiveProps(nextProps, nextContent){// console.log(this, nextProps, nextContent, "ConextComponent -------componentWillReceiveProps") } shouldComponentUpdate(newProps, newState, nextContent) { console.log(this,newProps, newState, nextContent,"ConextComponent -------componentWillReceiveProps") return true; } componentWillUpdate(newProps, newState, nextContent) { console.log(newProps, newState, nextContent, "ConextComponent -------componentWillUpdate"); } componentDidUpdate(prevProps, prevState, preConext) { console.log(prevProps, prevState, preConext, 'ConextComponent -------componentDidUpdate') } onChange = () =>{ this.setState({ value:"改变后" }) } render() { return ( <div> <myContext.Provider value = {self.state}> <div onClick={this.onChange}></div> <Base/> </myContext.Provider> </div> ); }}export default ConextComponent ;
由代码可见, 子组件Base的context是父组件ContextComponent通过MyContext.Provider上属性value去传递的。此处的value属性对应着子组件的context对象。在子组件中需通过this.context或者生命周期的context参数获取context对象的值。
上图是 :子组件的componentWillMount生命周期中获取到的context对象的值。
点击‘快来点我’按钮之后, context改变。
上图是 :子组件的获取到的context对象改变后的值。
注意 : MyContext.Provider上的属性, value(固定属性名称), 它的作用就是:子组件是通过这个属性获取到context对象传递的值。
每一个Context对象都会返回一个ProviderReact组件,此ProviderReact组件上绑定着value属性, 此组件允许消费组件(也就是子组件)订阅context的变化, 那这个变化的值, 就是通过value传递。
ProviderReact的特征:
1.ProviderReact组件接受一个value属性对应着一个context值, 然后传递给包含在ProviderReact组件内层的组件。
2.一个ProviderReact组件可以和多个组件有对应关系。
3.多个ProviderReact组件也可以嵌套使用, 里层的会覆盖外层的数据。
4.当Provider的value值发生变化时, 它内部的所有组件都会重新渲染。
5.Provider及其内部组件都不受制于shouldComponentUpdate生命周期。因此当内部组件在其祖先组件退出更新的情况下也能更新。无论shouldComponentUpdate返回trueOrfalse, 都无影响(自己打印shouldComponentUpdate方法可见, 使用了Provider包含的组件,并不会走到shouldComponentUpdate方法里。)。
6.在生命周期中, 如何获取context对象的值?, 在现有的生命周期中, 在最后一个参数的位置加上nextContext,或者使用this.context。二者选其一。 (前提是此组件绑定了context对象。)
7.对于面向函数的无状态组件(也就是函数组件), 可以通过函数的参数直接访问组件的Context对象的值。
const SatetComponent = (props, context) =>{
......
}将context对象绑定到组件上 (Class.contextType)第一种获取context的值的方式
export default MyComponent extends Reacr.Component{
coustructor(props){
super(props);
}
render(){
return (<div></div>)
}
}
const MyContext = React.createContext('defaultValue')
MyComponent.contextType = MyContext;已经挂在MyComponent 组件上的contextType属性会被重新赋值为一个由React.createContext()创建的Context对象。这便可以通过this.context来使用离ProviderReact最近的Context上的那个值。也可以在任何一个生命周期中访问到它。也就是此被绑定了context对象组建的任何地方, 都可以获取到context的值。
ConsumerReact组件第二种获取context的值的方式
<myContext.Consumer>
{(value1,value2, value3...) => {return (组件)}}
</myContext.Consumer>class BaseConsumer extends React.Component { constructor(props, context) { super(props, context); console.log(props, context, "constructor") this.state = {}; }
render() { return ( <myContext.Consumer> {({value, onChange})=>{ return ( <div> {value} <button {...this.props} onClick = {onChange}>快来点我</button> </div> ) }} </myContext.Consumer> ); }}
BaseConsumer.contextType = myContext;class ConextComponentConsutomer extends React.Component { constructor(props, context) { super(props, context); console.log(props, context); this.onChange = ()=>{ this.setState({ value:"改变后" }) } this.state = { value :'未修改前', onChange : this.onChange }; } render() { let self = this; return ( <div> <myContext.Provider value = {self.state}> <div onClick={self.onChange}></div> <BaseConsumer/> </myContext.Provider> </div> ); }}export default ConextComponentConsutomer ;ConsumerReact中渲染的是函数组件。
1.如果Consumer所在的组件上没有使用MyComponent.contextType = MyContext;那么就单纯的从函数组件的参数中去获取。
2.如果绑定了MyComponent.contextType = MyContext;, 那么this.context和函数组件参数中, 任选一种方式获取context对象值。
生命周期与Context的关系
修改context之后
备注:代码依旧是上边介绍provider的代码。
由生命周期的打印可见, 只有constructor、componentWillMount、componentDidMount、componentWillReceiveProps、componentWillUpdate, 中在现有的参数中,再加一个参数, 便是context, 并且可以获取到正确的context的值。
注意此代码里明明有shouldComponentUpdate生命周期函数, 但为什么打印里没有, 是因为, context并不受shouldComponentUpdate函数控制。所以并不会触发shouldComponentUpdate函数。
总体注意事项:因为context会使用参考标识来决定何时进行渲染, 这里可能会有一些陷阱,当provider的父组件进行重新渲染时, 可能会在consumer组件中发生意外的渲染。为防止这种情况, 将value状态提升到父节点的state里。