原来 React 组件之间是这样通讯的

概述

在 React 中,组件之间的通讯是必不可少的一环,我们在处理业务的过程中,可能要使用很多组件,并在它们之间进行传值通讯,比如,父组件与子组件之间如何通讯,子组件与父组件又如何通讯,两个不相邻的组件如何进行跨组件之间的通讯,接下来将会介绍在 React 中常用的组件之间通讯的方法。

组件之间通讯的方法

父子通讯

父组件与子组件之间的通讯可以通过属性传值进行通讯,React 会把父组件传递的所有数据放到 props 对象中传递给子组件。

jsx 复制代码
import React, { Component } from 'react'

// 父组件
class App extends Component {
  constructor() {
    super()

    this.state = {
      title: [
        {
          id: 1,
          name: '标题一'
        },
        {
          id: 2,
          name: '标题二'
        },
        {
          id: 3,
          name: '标题三'
        }
      ]
    }
  }
  
  render() {
    return (
      <div>
        <Nav title={title} />
      </div>
    )
  }
}

// 类式 子组件
class Nav extends Component {
  constructor(props) {
    super(props)
  }
  
  render() {
    const { title } = this.props
    return (
      <div>
        <ul>
          {
            title.map(item => {
              return <li key={item.id}>{item.name}</li>
            })
          }
        </ul>
      </div>
    )
  }
}

// 函数式 子组件
function Nav(props) {
  const { title } = props
  return (
    <div>
      <ul>
        {
          title.map(item => {
            return <li key={item.id}>{item.name}</li>
          })
        }
      </ul>
    </div>
  )
}

子父通讯

子组件与父组件之间的通讯可以通过父组件传给子组件一个回调函数,然后子组件可以调用这个函数并传参给父组件。

jsx 复制代码
import React, { Component } from 'react'

// 父组件
export class App extends Component {
  constructor() {
    super()

    this.state = {
       count: 0
    }
  }

  changeCount(count) {
    this.setState({ count: this.state.count + count })
  }
  
  render() {
    const { count } = this.state
    return (
      <div>
        <AddCount addClick={(count) => this.changeCount(count)}/>
      </div>
    )
  }
}

// 子组件
class AddCount extends Component {
  addCount(count) {
    this.props.addClick(count)
  }
  
  render() {
    const { title } = this.props
    return (
      <div>
        <button onClick={e => this.addCount(1)}>+1</button>
      </div>
    )
  }
}

跨组件通讯

Context

我们可以先明确几个概念:

Provider:生产者容器组件, 专门用于负责生产数据

Consumer:消费者容器组件, 专门用于消费生产者容器组件生产的数据的

容器组件:专门用于包裹其它组件的组件, 我们就称之为容器组件

在 React 中如果组件之间传递数据层次太深, 一层一层的传递比较麻烦, React 提供了 Context 上下文这个方法来传递数据。

此时我们可以通过调用 React.createContext({}) 这个创建上下文的方法,然后就会返回两个容器组件,即 ProviderConsumer ,利用这两个容器组件从祖先组件传递数据给到所有的后代组件,其中 Provider 接收一个 value 属性,传递给消费组件。

一个 Provider 可以和多个消费组件有对应关系,多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据,当 Providervalue 值发生变化时,它内部的所有消费组件都会重新渲染。

Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。它设计目的是为了共享那些对于一个组件树而言是全局的数据,例如当前认证的用户、主题或首选语言。

jsx 复制代码
import React, { Component } from 'react'

// 1. 创建一个上下文对象
const AppContext = React.createContext({})

// 2. 从上下文对象中获取容器组件
const { Provider, Consumer } = AppContext

// 定义子组件
class Childern extends Component{
  render(){
    return (
      <Consumer>
        {
          (value)=>{
            return (
              <div>
                <p>{value.name}</p>
                <p>{value.age}</p>
              </div>
            )
          }
        }
      </Consumer>
    )
  }
}

// 定义父组件
class Teacher extends Component{
  render(){
    return (
      <div>
        <Childern></Childern>
      </div>
    )
  }
}

class App extends Component{
  render(){
    return (
      /* 3. 此时我们可以在生产者容器组件中通过 value 来生产数据 */
      <Provider value={{name: '编程老师', age: 28}}>
        <Teacher></Teacher>
      </Provider>
    )
  }
}

还可以在创建 Context 到时候指定默认值,也可以通过给组件设置 contextType 从上下文中获取数据,挂载在 class 上的 contextType 属性会被重赋值为一个由 React.createContext() 创建的 Context 对象,然后就可以使用 this.context 来消费最近 Context 上的那个值。

jsx 复制代码
import React, { Component } from 'react'

// 1. 创建一个上下文对象
const AppContext = React.createContext({
    name: '编程老师',
    age: 28
})

class Childern extends Component{
  render(){
    return (
      <div>
        {/* 从当前组件的上下文中消费数据 */}
        <p>{this.context.name}</p>
        <p>{this.context.age}</p>
      </div>
    )
  }
}

// 2. 指定当前组件的上下文
Childern.contextType = AppContext

class Teacher extends Component{
  render(){
    return (
      <div>
        {/* 从当前组件的上下文中消费数据 */}
        <p>{this.context.name}</p>
        <p>{this.context.age}</p>
        <Childern></Childern>
      </div>
    )
  }
}

Teacher.contextType = AppContext

class App extends Component{
  render(){
    return (
      // 3. 使用生产者生产数据
      <div>
        <Teacher></Teacher>
      </div>
    )
  }
}

export default App

但是,如果存在多个生产者,就不能使用 contextType 这种形式接收数据,只能使用 Consumer 这种方式接受数据。

jsx 复制代码
import React, { Component } from 'react'

const AppContext1 = React.createContext({})
const AppContext2 = React.createContext({})

class Childern extends React.Component {
  render() {
    return (
      <AppContext1.Consumer>
        {(value) => {
          return (
            <AppContext2.Consumer>
              {(value2) => {
                return (
                  <div>
                    <p>{value.name}</p>
                    <p>{value.age}</p>
                    <p>{value2.course}</p>
                  </div>
                )
              }}
            </AppContext2.Consumer>
          )
        }}
      </AppContext1.Consumer>
    )
  }
}

class Teacher extends React.Component {
  render() {
    return (
      <div>
        <Childern></Childern>
      </div>
    )
  }
}

class App extends React.Component {
  render() {
    return (
      <AppContext1.Provider value={{ name: '编程老师', age: 28 }}>
        <AppContext2.Provider value={{ course: '前端' }}>
          <Teacher></Teacher>
        </AppContext2.Provider>
      </AppContext1.Provider>
    )
  }
}

export default App

事件总线

虽然通过 Context 我们已经能够实现跨组件通讯,但是 Context 只能实现从上往下传递,不能实现从下往上传递或者同级之间传递,我们可以借助第三方库 events 来实现跨组件之间的通讯。

jsx 复制代码
import React, { Component } from 'react'
import { EventEmitter } from 'events'

// 1. 创建一个全局的事件管理器对象
const eventBus = new EventEmitter()

class Childern extends Component{
  componentDidMount() {
    // 2. 监听事件
    eventBus.addListener('sayHello', this.handle)
  }
  
  // 注意点:最好在组件卸载的时候移除对应的事件
  componentWillUnmount() {
    eventBus.removeListener('sayHello', this.handle)
  }
  
  handle = (name, age) => {
    console.log(name, age)
  }
  
  render(){
    return (
      <div>Childern</div>
    )
  }
}

class Teacher extends Component{
  render(){
    return (
      <div>
        <p>Teacher</p>
        <button onClick={()=>{this.btnClick()}}>按钮</button>
      </div>
    )
  }
  
  btnClick(){
    // 3. 发出事件
    eventBus.emit('sayHello', '编程老师', 28)
  }
}

总结

可见,React 组件之间的通讯有很多种方式,你可以根据具体的业务来使用具体的通讯方式,但在平时开发时,使用的最多的还是 props 和 调用回调函数的方式。其实还有一种全局状态管理的解决方案,那就是 React Redux,类似于 Vue 全家桶中的 Pinia,而 React Redux 是 Redux 的官方 React UI 绑定库,由 Redux 官方团队维护,并 与 Redux 和 React 最新的 API 保持同步,关于 React Redux 的技术文章学习,后续我再和掘友们分享。

相关推荐
懒大王爱吃狼24 分钟前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
小牛itbull1 小时前
ReactPress:重塑内容管理的未来
react.js·github·reactpress
逐·風4 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#
Devil枫5 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
尚梦5 小时前
uni-app 封装刘海状态栏(适用小程序, h5, 头条小程序)
前端·小程序·uni-app
GIS程序媛—椰子6 小时前
【Vue 全家桶】6、vue-router 路由(更新中)
前端·vue.js
前端青山6 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
毕业设计制作和分享7 小时前
ssm《数据库系统原理》课程平台的设计与实现+vue
前端·数据库·vue.js·oracle·mybatis
清灵xmf9 小时前
在 Vue 中实现与优化轮询技术
前端·javascript·vue·轮询
大佩梨9 小时前
VUE+Vite之环境文件配置及使用环境变量
前端