Context非父子组件间通信

  • React中的非父子传参一般有三种方法,第一种是中间人方法,就是比如兄弟组件间通信,就是通过一个子组件传数据给两个子组件组件的共同父组件,再通过这个共同的父组件传给另一个子组件,这种方法与Vue中的实现非常的相似,但是也非常的不推荐使用,因为使得代码非常的冗余。第二种方法就是消息的订阅与发布,与Vue中的事件总线也非常的相似,也不是很推荐使用,第三种方法则是官方推荐的使用的跨级通信方法:context
  • React提供了一个APIContext
    • Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
    • Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言;

Context相关API

1. React.createContext(相当于Vue创建全局的事件总线)

创建一个需要共享的Context对象:

如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的 Provider 中读取到当前的context值;

defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值

// 创建一个Context共享对象
const GlobalContext = React.createContext({name: "默认名称", age: 1})

2. Context.Provider(做数据提供)

每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化:

Provider 接收一个 value 属性,传递给消费组件;

一个` Provider 可以和多个消费组件有对应关系;

多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据;

Provider value 值发生变化时,它内部的所有消费组件都会重新渲染;

<GlobalContext.Provider value={{name: "lam", age: 18}}>
<Father/>
</GlobalContext.Provider>

3. Class.contextType(将全局的context拉到自己的类组件中)

挂载在类组件上的 contextType 属性会被重赋值为一个由React.createContext()创建的Context对象:

这能让你使用 this.context 来消费最近Context上的那个值;

你可以在任何生命周期中访问到它,包括render函数中;

// 设置绑定唯一全局context(使得该组件可以通过this.context来获取最近的Context中的数据)
son.contextType = GlobalContext

4. Context.Consumer(使用事件总线中的数据)

这里,React组件也可以订阅到 context 变更。这能让你在 函数式组件 中完成订阅 context

这里需要 函数作为子元素(function as child)这种做法;

这个函数接收当前的 context 值,返回一个 React 节点;

<GlobalContext.Consumer>
{
value => {
return <h2>名字: {value.name} 年龄: {value.age}</h2>
}
}
</GlobalContext.Consumer>

Context使用流程

  • 例如我们现在有一个爷爷组件Grandpa.js, 现在需要传过父亲组件Father.js向孙子组件Son.js传递数据(遗产,金钱,房子)
  1. 创建一个全局Context, 由于这个Context会在爷爷组件和孙子组件中都用到, 所以我们单独创建一个context文件夹, 在文件夹的01-GlobalContext.js
// 创建全局事件context()
import React from 'react'
//创建全局context并设置默认数据(没有Provider的时候调用)
const GlobalContext = React.createContext({money: "没钱", house: 0})

// 并将context暴露出去
export default GlobalContext
  1. 在爷爷组件中, 引入GlobalContext并通过其内置的Provider中的value属性为后代提供数据
// 爷爷组件

import React, { Component } from 'react'
import GlobalContext from './01-GlobalContext.js'//1.引入全局context
import Father from './03-组件2(父亲).js'//引入父亲组件

export default class grandpa extends Component {
state={//爷爷的财产
money:500,//金钱500元
house:2//房子两套
}
render() {
return (
<div>
<button onClick={this.addMoney}>点击增加爷爷的金钱</button>
<button onClick={this.addHouse}>点击增加爷爷的房子</button>
<h2>爷爷有财产:金钱:{this.state.money}元</h2>
<h2>爷爷有财产:房子:{this.state.house}套</h2>
--------------------------------------------------------
{/* 2.爷爷作为消息(数据)的提供者(将父亲包裹) */}
<GlobalContext.Provider value={this.state}>
{/* 父亲组件 */}
<Father></Father>
</GlobalContext.Provider>
</div>
)
}

// 增加金钱
addMoney = () => {
this.setState({money:this.state.money+100},()=>{
console.log('爷爷的金钱遗产为',this.state.money);
})
}

// 增加房子
addHouse = () => {
this.setState({house:this.state.house+1},()=>{
console.log('爷爷的房子遗产为',this.state.house);
})
}
}
// 父亲组件(不重要)

import React, { Component } from 'react'
// import GlobalContext from './01-GlobalContext.js'//1.引入全局context
import Son from './02-组件1(孙子).js'//引入孙子组件

export default class father extends Component {
render() {
return (
<div>
<h1>父亲组件</h1>
--------------------------------------------------------
{/* 儿子组件 */}
<Son></Son>
</div>
)
}
}
  1. 在要接受数据的孙子组件中, 设置组件的contextType属性并指定为哪一个Context, 因为可能会有多个Context, 所以需要明确指定是哪一个
// 孙子组件

import React, { Component } from 'react'
import GlobalContext from './01-GlobalContext.js'//1.引入全局context

export default class son extends Component {
render() {
// 获取全局Context数据有两种
// 直接在this.context中获取(只能用在该组件设置了contextType属性)
const heritage = this.context
console.log('方法1成功获取到爷爷的财产',heritage);
return (
<div>
<h1>孙子组件</h1>
<h1>从爷爷中继承过来的遗产有:</h1>
<p>方法一获取金钱:{heritage.money}</p>
{/* 方法2,通过Consumer方法调用立即执行函数来获取爷爷的数据 */}
<GlobalContext.Consumer>
{
(value) => {
console.log('方法2,通过Consumer获取到的数据',value);
return <p>方法二获取房子: {value.house} 套</p>
}
}
</GlobalContext.Consumer>
--------------------------------------------------------
</div>
)
}
}

// 设置绑定唯一全局context(使得该组件可以通过this.context来获取最近的Context中的数据)
son.contextType = GlobalContext
  1. 获取数据, 并且使用数据
    • 有两种方法可以获取, 如果该孙子组件设置了contextType属性指向固定的context的话, 那么可以直接在this.context中获取该指定的context中的数据
    • 同样可以使用Context.Consumer的方法来获取数据,引入指定的Context后调用其内置的consumer属性来包裹住函数的方法来获取(通过return方法返指定的DOM结构)
// 孙子组件

import React, { Component } from 'react'
import GlobalContext from './01-GlobalContext.js'//1.引入全局context

export default class son extends Component {
render() {
// 获取全局Context数据有两种
// 直接在this.context中获取(只能用在该组件设置了contextType属性)
const heritage = this.context
console.log('方法1成功获取到爷爷的财产',heritage);
return (
<div>
<h1>孙子组件</h1>
<h1>从爷爷中继承过来的遗产有:</h1>
<p>方法一获取金钱:{heritage.money}</p>
{/* 方法2,通过Consumer方法调用立即执行函数来获取爷爷的数据(一般用于多层组件通信嵌套) */}
<GlobalContext.Consumer>
{
(value) => {
console.log('方法2,通过Consumer获取到的数据',value);
return <p>方法二获取房子: {value.house} 套</p>
}
}
</GlobalContext.Consumer>
--------------------------------------------------------
</div>
)
}
}

// 设置绑定唯一全局context(使得该组件可以通过this.context来获取最近的Context中的数据)
son.contextType = GlobalContext

结果展示:

image

  • 没有Provider的时候调用context的默认值
    image

多层组件嵌套使用Consumer

  • 使用爷爷组件通过父亲组件项孙子组件传递样式以及用户数据
import React, { Component } from 'react';

// 设置两个context(用consumer来实现嵌套组件数据的传输)
const UserContext = React.createContext({ nickname: "默认", level: -1 })
const ThemeContext = React.createContext({ color: "black" });

// 孙子组件
class ProfileHeader extends Component {
render() {
return (
<div>
<UserContext.Consumer>{/* 第一层通过consumer获取用户数据 */}
{value => {
return (
<ThemeContext.Consumer>{/* 第二层通过consumer获取页面样式 */}
{
theme => (
<div>
<h2 style={theme}>用户昵称: {value.nickname}</h2>
<h2 style={theme}>用户等级: {value.level}</h2>
</div>
)
}
</ThemeContext.Consumer>
)
}}
</UserContext.Consumer>
</div>
)
}
}

// 父亲组件(没什么作用)
class Profile extends Component {
render() {
return (
<div>
<ProfileHeader />
</div>
)
}
}

// 爷爷组件
export default class App extends Component {
render() {
return (
<div>
{/* 第一层提供数据 */}
<UserContext.Provider value={{ nickname: "lam", level: 99 }}>
{/* 第二层提供样式 */}
<ThemeContext.Provider value={{ color: "greenyellow" }}>
<Profile />{/* 通过父组件传给孙子组件 */}
</ThemeContext.Provider>
</UserContext.Provider>
<h2>其他内容</h2>
</div>
)
}
}

结果展示:

image