react基础(七)— 跨组件通信

172 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情

前言

大家好呀,我是L同学。在上篇文章中react基础(六)— 父子组件通信,我们讲到了父子组件的通信。在很多情况下,我们有一些数据需要在多个组件间进行共享,例如用户信息,用户登陆状态等。今天我们来学习下跨组件通信。

跨组件通信

通过props传递

我们通过一个案例来学习跨组件通信。组件之间的嵌套关系如图所示,并且ProfileHeader孙组件中的用户昵称和等级数据都在父组件App中。 image.png 组件通信,在上篇文章中介绍过,我们可以使用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的数据。

image.png

通过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>
    )
  }
}

我们可以看到数据可以正常显示。

image.png 那么如果我们不把Profile作为子组件呢?

        <UserContext.Provider value={this.state}>
        </UserContext.Provider>
        <Profile/>

我们可以看到数据显示的是默认值。

image.png 以上的方法要求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>
  )
}