老样子我们边写小demo边理解context。
组件继承顺序:
index.js–>home.js–>show.js–>b.js(c.js)
在context中,Provider和Consumer有两种定义的方式:
1、const { Provider, Consumer } = React.createContext(defaultValue);
2、const testContext = React.createContext(defaultValue);
<testContext.Provider /> <testContext.Consumer />
首先,测试下通过祖先组件–>父组件–>子组件的传递:
在home.js中,我们定义一个Provider和即将传递的value:
import React, { Component } from 'react';
import Show from './show'
export const { Provider, Consumer } = React.createContext('hhhhh');
class Home extends Component {
constructor(props) {
super(props);
}
render() {
let value = 'hekki'
return (
<Provider value={value}>
<div>预备传的值:{value}</div>
<Show />
</Provider>
)
}
}
export default Home;
createContext(defaultValue):defaultValue选填
export const { Provider, Consumer }:抛出Provider和Consumer,子组件将通过Consumer接收传递的value
在show.js中订阅value:
import React, { Component } from 'react';
import B from './b';
import C from './c';
import { Consumer } from './home';
import { withRouter } from 'react-router-dom';
class Show extends Component {
render() {
return (
<div>
<Consumer>
{(value) =>
<div>
<div>子组件的传值:{value}</div>
<B />
<C />
</div>
}
</Consumer>
</div>
)
}
}
export default withRouter(Show);
value的获取通过回调函数获取,同时也可以再次向下传递值
在b.js订阅value:
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { Consumer } from './home'
class B extends Component {
render() {
return (
<Consumer>
{(value) =>
<div >
我是B组件,接收的值是:{value}
</div>
}
</Consumer>
)
}
}
export default withRouter(B);
最终的效果如下:
现在来测试一下祖先组件–>子组件的情况:
我们将父组件的订阅内容清空:
return (
<div>
<B />
<C />
</div>
)
再看实现的效果:
那么,我们定义多个Provider呢?
未提及的代码不做更改,我在show.js中又定义了一个Provider:
export const { Provider, Consumer } = React.createContext();
class Show extends Component {
render() {
const show = '我是第二个context定义的全局的值'
return (
<div>
<Provider value={show}>
<B />
<C />
</Provider>
</div>
)
}
}
再到c.js做出更改:
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { Consumer } from './show'
class C extends Component {
render() {
return (
<div >
<Consumer >
{(value) =>
<div>我是C组件,接收的值是:{value}</div>
}
</Consumer>
</div>
)
}
}
export default withRouter(C);
最终的效果如下:
如果,非该组件的子组件Provider之后能够订阅到值吗?
我再定义了一个a.js组件,这个组件没有存在在上面的继承链上,写好Provider之后再由b.js引用:
a.js:
export const testContext = React.createContext();
class A extends Component {
render() {
return (
<testContext.Provider value={'helloworld'}>
<div>我是A组件</div>
</testContext.Provider>
)
}
}
b.js:
class B extends Component {
render() {
return (
<div>
<Consumer>
{(value) =>
<div >
我是B组件,接收的值是:{value}
</div>
}
</Consumer>
<testContext.Consumer>
{
(value) =>
<div>这里显示接收的A组件的值:{value}</div>
}
</testContext.Consumer>
</div>
)
}
}
可以得出结论是:Provider里面一定要包含继承的子组件,在继承链上是可以跳跃引用的(子组件引用祖先组件),如果不包含继承的子组件,则不能被Consumer引用。在一个工程中,可以有多个context存在。
在上述情况下,溯源是比较好找的,因为export一个Provider,对应一个Consumer。在需要接收内容的地方,会引用对应的Consumer。
那么,如果需要传值动态更改要怎么办呢?
为了方便观察,我将b.js屏蔽,修改show.js的代码:
export const { Provider, Consumer } = React.createContext();
class Show extends Component {
constructor() {
super();
this.state = {
isClick: false
}
}
handleOnClick = () => {
const isClick = !this.state.isClick;
this.setState({
isClick
})
}
render() {
return (
<div>
<Provider value={this.state}>
<Button onClick={() => { this.handleOnClick() }} >点击改变数据</Button>
{/* <B /> */}
<C />
</Provider>
</div>
)
}
}
在这里,我将value定义成了父组件的state,再定义了一个按钮,当按下按钮的时候,改变state里的值,这样,子组件接收的值就会实时改变。
这是对应的子组件接收值的代码:
class C extends Component {
render() {
console.log(this.props)
return (
<div >
<Consumer >
{(value) =>
<div>我是C组件,接收的值是:{value.isClick ? 'hello' : 'goodbye'}</div>
}
</Consumer>
</div>
)
}
}
实现的效果如图:
context也支持嵌套,为了方便理解,我又一次修改show.js
export const { Provider, Consumer } = React.createContext();
export const fistText = React.createContext();
export const secondText = React.createContext();
class Show extends Component {
constructor() {
super();
this.state = {
isClick: false
}
}
handleOnClick = () => {
const isClick = !this.state.isClick;
this.setState({
isClick
})
}
render() {
return (
<div>
<Provider value={this.state}>
<Button onClick={() => { this.handleOnClick() }} >点击改变数据</Button>
<B />
<C />
</Provider>
<fistText.Provider value={'这是第一个context的传值'}>
<secondText.Provider value={'这是第二个context的传值'}>
<D />
</secondText.Provider>
</fistText.Provider>
</div>
)
}
}
对应的d.js:
import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';
import { fistText, secondText } from './show'
export const testContext = React.createContext();
class D extends Component {
render() {
return (
<fistText.Consumer>
{(value) =>
<secondText.Consumer>
{(val) =>
<div>
我是D组件,我接收的值:{value},{val}
</div>
}
</secondText.Consumer>
}
</fistText.Consumer>
)
}
}
export default withRouter(D);
得到的效果:
最后,对context做一个总结:
1、由React.createContext()注册,有两种写法(前文已讲),Provider定义传递数值,Consumer订阅
2、context是对继承链传值的一个优化,将需要继承的值提出来作为全局变量,如果子组件需要继承祖先组件的数据不需要一级一级的porps传值,可直接引用。
3、子组件不能更改context,但是父组件可以实时更改传递的值
4、context支持嵌套,但是太深的嵌套建议优化结构。
5、和Redux相比,Redux和Vuex功能类似,是将需要全局使用的变量提出来,任何组件可以修改他们,不用考虑继承,不用考虑相互间是否是子组件。但是Redux是单独的一套体系,如果工程、值传递不复杂的情况下,context可以提高优先级。
如果有不正确的,欢迎指正,我及时修改。
完整代码地址:https://github.com/PYLDora/React-Context
本文参考:
https://react.docschina.org/docs/context.html
https://www.jianshu.com/p/65b348bf86ad