文章目录
一、什么是上下文(context)?
上下文,当一个组件创建了上下文后,其后代组件(子组件、孙子组件、曾孙组件…)都可以使用该上下文中的数据。
在之前的学习中,我们知道react的数据要么来源于自身的状态state,要么来源于父级传过来的props。
有了上下文之后,组件又多了一个数据来源,那就是context上下文。
react16版本前的的上下文和16版本之后的上下文是不一样的。
二、上下文的使用
旧版本的上下文
旧版本的react认为只有类才有状态,只有类组件才可以创建上下文。
上下文的创建
上下文的创建流程:
1.给类组件创建静态属性childContextTypes,使用该属性对上下文中的数据进行约束。
2.给类组件创建方法getChildContext,该方法需要返回的是一个对象,对象和childContextTypes的数据需要一一对应。
创建的上下文的代码如下:
import React, {
Component } from 'react'
import Proptypes from 'prop-types'
export default class Context extends Component {
// 约束上下文中的数据类型
static childContextTypes = {
num1: Proptypes.number,
num2: Proptypes.number,
}
// 获取上下文中的数据
getChildContext() {
console.log("getChildContext")
return {
num1:1,
num2:2,
}
}
render() {
console.log('render')
return (
<div>
</div>
)
}
}
上面的代码运行之后我们会发现getChildContext方法是在render之后调用的。
上下文的使用
在讲解上下文的使用之前,我们先模拟父子组件嵌套的情况,代码如下:
import React, {
Component } from 'react'
import Proptypes from 'prop-types'
export default class Context extends Component {
// 约束上下文中的数据类型
static childContextTypes = {
num1: Proptypes.number,
num2: Proptypes.number,
}
// 获取上下文中的数据
getChildContext() {
return {
num1:1,
num2:2,
}
}
render() {
return (
<div>
<h1>Context</h1>
<A/>
</div>
)
}
}
class A extends React.Component{
render(){
return <div>
<h1>A</h1>
<B/>
</div>
}
}
class B extends React.Component{
render(){
return <div>
<h1>B</h1>
</div>
}
}
其大致关系如下:
注意!!!当B组件想要获取Context组件的上下文的时候,B组件同样需要对接收的上下文进行约束。
使用的步骤如下:
1.创建一个静态属性contextType(ps:创建上下文是的约束是childContextTypes),用于约束接受的上下文的数据类型。
2.上下文可以通过constructor的第二个参数获取,或者从组件的context属性获取
先说说通过constructor第二个参数获取上下文的方式:
import React, {
Component } from 'react'
import Proptypes from 'prop-types'
export default class Context extends Component {
// 约束上下文中的数据类型
static childContextTypes = {
num1: Proptypes.number,
num2: Proptypes.number,
}
// 获取上下文中的数据
getChildContext() {
return {
num1: 1,
num2: 2,
}
}
render() {
return (
<div>
<h1>Context</h1>
<A />
</div>
)
}
}
class A extends React.Component {
render() {
return <div>
<h1>A</h1>
<B />
</div>
}
}
class B extends React.Component {
static contextTypes = {
num1:Proptypes.number
}
constructor(props, context) {
super(props)
console.log(context)
}
render() {
return <div>
<h1>B</h1>
</div>
}
}
运行上面的代码,我们可以看到如下结果:
可以看出,B组件内打印的context对象只出现约束的数据。
再说说通过this.context获取上下文的方式:
import React, {
Component } from 'react'
import Proptypes from 'prop-types'
export default class Context extends Component {
// 约束上下文中的数据类型
static childContextTypes = {
num1: Proptypes.number,
num2: Proptypes.number,
}
// 获取上下文中的数据
getChildContext() {
return {
num1: 1,
num2: 2,
}
}
render() {
return (
<div>
<h1>Context</h1>
<A />
</div>
)
}
}
class A extends React.Component {
render() {
return <div>
<h1>A</h1>
<B />
</div>
}
}
class B extends React.Component {
static contextTypes = {
num1:Proptypes.number,
num2:Proptypes.number,
}
constructor(props,context) {
super(props,context) //将context交给父类处理
console.log(this.context) //如果没有在super里写,这里会打印undefined
}
render() {
return <div>
<h1>B</h1>
</div>
}
}
运行上面的代码,我们可以看到如下结果:
函数里的上下文
函数里如何获取上下文?
由于函数里没有this,所以只能通过第二个参数获取,获取之前仍然需要对数据进行约束,不然打印的是空对象。
import React, {
Component } from 'react'
import Proptypes from 'prop-types'
export default class Context extends Component {
// 约束上下文中的数据类型
static childContextTypes = {
num1: Proptypes.number,
num2: Proptypes.number,
}
// 获取上下文中的数据
getChildContext() {
return {
num1: 1,
num2: 2,
}
}
render() {
return (
<div>
<h1>Context</h1>
<A />
</div>
)
}
}
class A extends React.Component {
render() {
return <div>
<h1>A</h1>
<B />
</div>
}
}
function B(props, context) {
console.log(context)
return <div>
<h1>B</h1>
</div>
}
//约束接收的上下文数据
B.contextTypes = {
num1: Proptypes.number,
num2: Proptypes.number,
}
打印结果如下:
上下文的数据更改
上下文的值可以改变吗?怎么改变?
我们知道数据是自上而下流动的,所以更改上下文中的数据只能是创建上下文的本身,子组件没有权利更改,所以在子组件里想要更改上下文的数据,可以通过事件,通知创建上下文的组件更改数据,即上下文的数据里有个更改上下文的方法,子组件调用即可。
假设以下场景,子组件通过点击上下文的数据:
import React, {
Component } from 'react'
import Proptypes from 'prop-types'
export default class Context extends Component {
state = {
num1:1
}
// 约束上下文中的数据类型
static childContextTypes = {
num1: Proptypes.number,
num2: Proptypes.number,
onChange:Proptypes.func,
}
// 获取上下文中的数据
getChildContext() {
return {
num1: this.state.num1,
num2: 2,
onChange:()=>{
this.setState({
num1:this.state.num1 + 1
})
}
}
}
render() {
return (
<div>
<h1>Context</h1>
<A />
</div>
)
}
}
class A extends React.Component {
render() {
return <div>
<h1>A</h1>
<B />
</div>
}
}
function B(props, context) {
console.log(context)
return <div>
<h1>B</h1>
<h2>这是B组件,值来自于context:{
context.num1}</h2>
<button onClick={
context.onChange}>+1</button>
</div>
}
B.contextTypes = {
num1: Proptypes.number,
num2: Proptypes.number,
onChange:Proptypes.func,
}
刚渲染完毕的页面:
点击按钮之后:
小结:上下文的数据更改,可以通过在上下文中加入一个处理函数,用于后代组件更改上下文的数据。创建上下文的组件本身读取不到自己的context(明明可以通过自己的state获取为何还要自己创建上下文自己读取),并且在存在多个context时,读取的值是离他最近的组件的context。
新版本的上下文
新版本的上下文是一个独立于组件的对象,通过React.createContext(默认值)创建,其返回值是上下文对象。
上下文的创建
创建上下文,打印其返回的结果:
import React, {
Component } from 'react'
import NewContext from './components/NewContext'
const ctx = React.createContext({
num1:1,
num2:2,
})
console.log(ctx)
export default class App extends Component {
render() {
return (
<div>
<NewContext />
</div>
)
}
}
通过控制台我们可以看到该对象有两个属性,分别是Consumer和Provider,。
1.Provider(生产者):这是一个上下文组件,用来包裹子组件,其有个value属性,定义上下文数据。
2.Consumer(消费者):这也是上下文组件,不同的是该组件主要用于函数组件,当函数组件想要接收上下文数据的时候,可以使用该组件,组件的子节点(props.children)是一个函数,参数为上下文数据,需要返回元素。
新版context的使用示例:
import React, {
Component } from 'react'
//创建上下文,传入默认数据,一般是单独作为一个模块导出
const ctx = React.createContext({
num1: 1,
num2: 2,
})
export default class NewContext extends Component {
state = {
num1:3,
num2:4
}
render() {
return (
<div>
<h1>NewContext</h1>
<ctx.Provider value={
this.state}>
<A />
</ctx.Provider>
</div>
)
}
}
class A extends React.Component {
render() {
return <div>
<h1>A</h1>
<B />
</div>
}
}
class B extends React.Component{
static contextType = ctx; //约束接收的上下文,旧版本是contextTypes
render(){
return <div>
<h1>B</h1>
<h2>来自上下文的数据:{
this.context.num1}</h2>
</div>
}
}
页面输出结果:
页面结果如下,从图中我们可以看到上下文组件的props:
上下文的更改
和旧版的上下文一样,改变数据依旧是遵循数据自上而下流动,通过改变数据的来源进而改变上下文的数据。
代码示例:
import React, {
Component } from 'react'
const ctx = React.createContext({
num1: 1,
num2: 2,
})
export default class NewContext extends Component {
state = {
num1:3,
num2:4,
onChange:()=>{
this.setState({
num1:this.state.num1 + 1,
})
}
}
render() {
return (
<div>
<h1>NewContext</h1>
<ctx.Provider value={
this.state}>
<A />
</ctx.Provider>
</div>
)
}
}
class A extends React.Component {
render() {
return <div>
<h1>A</h1>
<B />
</div>
}
}
class B extends React.Component{
static contextType = ctx;
render(){
return <div>
<h1>B</h1>
<h2>来自上下文的数据:{
this.context.num1}</h2>
<button onClick={
this.context.onChange} >context的num1+1</button>
</div>
}
}
新版context在函数组件的使用方式
函数组件使用上下文需要Consumer这个组件,使用示例如下:
import React, {
Component } from 'react'
const ctx = React.createContext({
num1: 1,
num2: 2,
})
export default class NewContext extends Component {
state = {
num1:3,
num2:4,
onChange:()=>{
this.setState({
num1:this.state.num1 + 1,
})
}
}
render() {
return (
<div>
<h1>NewContext</h1>
<ctx.Provider value={
this.state}>
<A />
</ctx.Provider>
</div>
)
}
}
class A extends React.Component {
render() {
return <div>
<h1>A</h1>
<B />
</div>
}
}
function B(){
return <div>
<h1>B</h1>
<h2>来自context的值:<ctx.Consumer>{
val => <>{
val.num1}</>}</ctx.Consumer></h2>
</div>
}
页面输出结果:
小结:使用生产者-消费者模式的上下文,上下文的来源可以是多个,不同于旧版的就近原则,如果在类里定义了static contextType = (创建上下文时的返回值),那么this.context的来源只有一个。
Context Hook
通过context hook可以轻松充当消费者,拿取上一个生产者的数据。
import React, {
useContext} from 'react'
const ctx = React.createContext();
function Text(){
const value = useContext(ctx)
return <h1>{
value}</h1>
}
export default function App(){
return <>
<ctx.Provider value="value">
<Text/>
</ctx.Provider>
</>
}
总结
1.旧版的上下文是在组件内部定义的,并且使用与接收都要对数据进行约束,对于多个上下文的读取,遵循着就近原则。
2.新版的上下文可以抽离成模块,其模式为生产者-使用者,并且对于多个上下文的数据使用并不存在因为读取冲突而需要遵循就近原则。