一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情。
前言
大家好呀,我是L同学。在上篇文章中react基础(六)— 父子组件通信,我们讲到了父子组件的通信。在很多情况下,我们有一些数据需要在多个组件间进行共享,例如用户信息,用户登陆状态等。今天我们来学习下跨组件通信。
跨组件通信
通过props传递
我们通过一个案例来学习跨组件通信。组件之间的嵌套关系如图所示,并且ProfileHeader孙组件中的用户昵称和等级数据都在父组件App中。
组件通信,在上篇文章中介绍过,我们可以使用props一层层传递。
import React, { Component } from 'react'
function ProfileHeader(props) {
return (
<div>
<h2>用户昵称:{props.nickname}</h2>
<h2>用户等级:{props.level}</h2>
</div>
)
}
function Profile(props) {
return (
<div>
<ProfileHeader nickname={props.nickname} level={props.level}></ProfileHeader>
<ul>
<li>设置1</li>
<li>设置2</li>
<li>设置3</li>
<li>设置4</li>
<li>设置5</li>
</ul>
</div>
)
}
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
nickname: 'haha',
level: 99
}
}
render() {
const {nickname, level} = this.state
return (
<div>
<Profile nickname={nickname} level={level}></Profile>
</div>
)
}
}
如果一层层嵌套很深的话,对于一些中间层不需要数据的组件来说,例如Profile组件,还需要写
<ProfileHeader nickname={props.nickname} level={props.level}></ProfileHeader>
这么长串东西。
我们可以使用属性展开来简化操作,用展开运算符 ... 来在 JSX 中传递整个 props 对象。
import React, { Component } from 'react'
function ProfileHeader(props) {
return (
<div>
<h2>用户昵称:{props.nickname}</h2>
<h2>用户等级:{props.level}</h2>
</div>
)
}
function Profile(props) {
return (
<div>
{/* <ProfileHeader nickname={props.nickname} level={props.level}></ProfileHeader> */}
<ProfileHeader {...props}></ProfileHeader>
<ul>
<li>设置1</li>
<li>设置2</li>
<li>设置3</li>
<li>设置4</li>
<li>设置5</li>
</ul>
</div>
)
}
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
nickname: 'haha',
level: 99
}
}
render() {
// const {nickname, level} = this.state
return (
<div>
{/* <Profile nickname={nickname} level={level}></Profile> */}
<Profile {...this.state}></Profile>
</div>
)
}
}
我们可以看到孙组件中可以正常显示父组件APP的数据。
通过context传递
以上方法有一个缺点就是如果层级很多的话,一层层得传递是非常麻烦的,并且代码是非常冗余的。
react提供了一个API: Context。Context提供了一种在组件之间共享此类值的方法,而不需要显式地通过组件树的逐层传递props。Context设计的目的是为了共享那些对于一个组件树而言是全局的数据,例如当前认证的用户、主题或首选语言。
React.createContext
首先,我们通过React.createContext创建一个需要共享的Context对象。如果一个组件订阅了Context,那么这个组件会从离自身最近的那个匹配的Provider中读取到当前的context值。React.createContext可以传递默认值defaultValue。defaultValue是组件在顶层查找过程中没有找到对应的Provider,那么就使用默认值。
Context.Provider
每一个Context对象都会返回一个Provider React实例,它允许消费组件订阅context的变化:
- Provider接收一个value属性,传递给消费组件
- 一个Provider可以和多个消费组件有对应关系
- 多个Provider也可以嵌套使用,里面的会覆盖外层的数据
- 当Provider的value值发生变化时,它内部的所有消费组件都会重新渲染
Class.contextType
挂载在class上的contextType属性会被重新赋值为一个由React.createContext()创建的Context对象。这能够让我们使用this.context来消费最近Context上的那个值。我们可以在任何生命周期中访问到它,包括render函数。
import React, { Component } from 'react'
// 1. 创建Context对象
const UserContext = React.createContext({
// 默认值
nickname: 'abc',
level: 0
})
class ProfileHeader extends Component {
render() {
console.log(this.context);
return (
<div>
<h2>用户昵称:{this.context.nickname}</h2>
<h2>用户等级:{this.context.level}</h2>
</div>
)
}
}
// 3. 通过设置contextType拿到数据
ProfileHeader.contextType = UserContext
function Profile() {
return (
<div>
<ProfileHeader />
<ul>
<li>设置1</li>
<li>设置2</li>
<li>设置3</li>
<li>设置4</li>
<li>设置5</li>
</ul>
</div>
)
}
export default class App extends Component {
constructor(props) {
super(props)
this.state = {
nickname: 'haha',
level: 99
}
}
render() {
return (
<div>
{/* 2. 通过Provider把之后对应要用到context的组件作为子组件 */}
<UserContext.Provider value={this.state}>
<Profile/>
</UserContext.Provider>
</div>
)
}
}
我们可以看到数据可以正常显示。
那么如果我们不把Profile作为子组件呢?
<UserContext.Provider value={this.state}>
</UserContext.Provider>
<Profile/>
我们可以看到数据显示的是默认值。
以上的方法要求ProfileHeader必须是个类组件,可以通过this.context拿到数据。那么如果ProfileHeader是函数组件该怎么办呢?
我们可以通过Context.Consumer拿到数据。React组件可以订阅到context变更,能让我们在函数式组件中完成订阅context。这里需要函数作为子元素。这个函数接收当前的context值,返回react节点。
function ProfileHeader() {
return (
<UserContext.Consumer>
{
value => {
return (
<div>
<h2>用户昵称:{value.nickname}</h2>
<h2>用户等级:{value.level}</h2>
</div>
)
}
}
</UserContext.Consumer>
)
}