原来 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 的技术文章学习,后续我再和掘友们分享。

相关推荐
Larcher16 分钟前
新手也能学会,100行代码玩AI LOGO
前端·llm·html
徐子颐29 分钟前
从 Vibe Coding 到 Agent Coding:Cursor 2.0 开启下一代 AI 开发范式
前端
小月鸭41 分钟前
如何理解HTML语义化
前端·html
jump6801 小时前
url输入到网页展示会发生什么?
前端
诸葛韩信1 小时前
我们需要了解的Web Workers
前端
brzhang1 小时前
我觉得可以试试 TOON —— 一个为 LLM 而生的极致压缩数据格式
前端·后端·架构
yivifu2 小时前
JavaScript Selection API详解
java·前端·javascript
这儿有一堆花2 小时前
告别 Class 组件:拥抱 React Hooks 带来的函数式新范式
前端·javascript·react.js
十二春秋2 小时前
场景模拟:基础路由配置
前端
六月的可乐2 小时前
实战干货-Vue实现AI聊天助手全流程解析
前端·vue.js·ai编程