Context
非父子组件间通信
React
中的非父子传参一般有三种方法,第一种是中间人方法,就是比如兄弟组件间通信,就是通过一个子组件传数据给两个子组件组件的共同父组件,再通过这个共同的父组件传给另一个子组件,这种方法与Vue
中的实现非常的相似,但是也非常的不推荐使用,因为使得代码非常的冗余。第二种方法就是消息的订阅与发布
,与Vue
中的事件总线也非常的相似,也不是很推荐使用,第三种方法则是官方推荐的使用的跨级通信方法:context
React
提供了一个API
:Context
;
Context
提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
;
Context
设计目的是为了共享那些对于一个组件树而言是“全局”
的数据,例如当前认证的用户、主题或首选语言;
Context
相关API
1. React.createContext
(相当于Vue
创建全局的事件总线)
创建一个需要共享的Context对象:
如果一个组件订阅了Context
,那么这个组件会从离自身最近的那个匹配的 Provider
中读取到当前的context
值;
defaultValue
是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值
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
函数中;
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
传递数据(遗产,金钱,房子
)
- 创建一个全局
Context
, 由于这个Context
会在爷爷组件和孙子组件中都用到, 所以我们单独创建一个context
文件夹, 在文件夹的01-GlobalContext.js
import React from 'react'
const GlobalContext = React.createContext({money: "没钱", house: 0})
export default GlobalContext
|
- 在爷爷组件中, 引入GlobalContext并通过其内置的
Provider
中的value
属性为后代提供数据
import React, { Component } from 'react' import GlobalContext from './01-GlobalContext.js' import Father from './03-组件2(父亲).js'
export default class grandpa extends Component { state={ money: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 Son from './02-组件1(孙子).js'
export default class father extends Component { render() { return ( <div> <h1>父亲组件</h1> -------------------------------------------------------- {/* 儿子组件 */} <Son></Son> </div> ) } }
|
- 在要接受数据的孙子组件中, 设置组件的
contextType
属性并指定为哪一个Context
, 因为可能会有多个Context
, 所以需要明确指定是哪一个
import React, { Component } from 'react' import GlobalContext from './01-GlobalContext.js'
export default class son extends Component { render() { 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> ) } }
son.contextType = GlobalContext
|
- 获取数据, 并且使用数据
- 有两种方法可以获取, 如果该孙子组件设置了
contextType
属性指向固定的context
的话, 那么可以直接在this.context
中获取该指定的context
中的数据
- 同样可以使用
Context.Consumer
的方法来获取数据,引入指定的Context
后调用其内置的consumer
属性来包裹住函数的方法来获取(通过return方法返指定的DOM结构
)
import React, { Component } from 'react' import GlobalContext from './01-GlobalContext.js'
export default class son extends Component { render() { 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> ) } }
son.contextType = GlobalContext
|
结果展示:
- 没有
Provider
的时候调用context
的默认值
多层组件嵌套使用Consumer
- 使用爷爷组件通过父亲组件项孙子组件传递
样式
以及用户数据
import React, { Component } from 'react';
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> ) } }
|
结果展示: