Skip to content

React组件通信

组件是React应用的基本构建块,而组件间的通信是构建复杂React应用的关键。本文将详细介绍React中各种组件通信方式,包括最佳实践和注意事项。

父子组件通信

父子组件是最基本的组件关系,其通信方式也最为直接:

  • 父组件通过 props 向子组件传递数据
  • 子组件通过回调函数向父组件传递数据/事件

最佳实践

  • 使用prop-types或TypeScript进行类型检查,避免类型错误
  • 遵循单向数据流原则,不在子组件中直接修改props
  • 使用解构赋值使props的使用更清晰
  • 为回调函数添加适当前缀(如handle、on等)以区分事件处理函数

类组件实现

javascript
// 父组件(类组件)
import React, { Component } from 'react'
import Child from './Child'
import PropTypes from 'prop-types' // 引入prop-types进行类型检查

class Parent extends Component {
  constructor(props) {
    super(props)
    this.state = {
      name: '小明',
      age: 20
    }
  }

  // 处理来自子组件的事件
  handleClick = () => {
    this.setState({
      name: '小红',
      age: 25
    })
  }

  render() {
    const { name, age } = this.state // 使用解构赋值
    return (
      <div className="parent-container">
        <h1>父组件</h1>
        <p>姓名:{name}</p>
        <p>年龄:{age}</p>
        {/* 通过props向子组件传递数据和回调函数 */}
        <Child 
          name={name} 
          age={age} 
          handleClick={this.handleClick} 
        />
      </div>
    )
  }
}

export default Parent
javascript
// 子组件(类组件)
import React, { Component } from 'react'
import PropTypes from 'prop-types'

class Child extends Component {
  // 使用PropTypes进行类型检查
  static propTypes = {
    name: PropTypes.string.isRequired,
    age: PropTypes.number.isRequired,
    handleClick: PropTypes.func.isRequired
  }

  // 调用父组件传递的回调函数
  handleClick = () => {
    this.props.handleClick()
  }

  render() {
    const { name, age } = this.props // 使用解构赋值
    return (
      <div className="child-container">
        <h2>子组件</h2>
        <p>姓名:{name}</p>
        <p>年龄:{age}</p>
        <button onClick={this.handleClick}>修改父组件数据</button>
      </div>
    )
  }
}

export default Child

函数组件实现

javascript
// 父组件(函数组件)
import React, { useState } from 'react'
import Child from './Child'
import PropTypes from 'prop-types'

const Parent = () => {
  // 使用useState Hook管理状态
  const [name, setName] = useState('小明')
  const [age, setAge] = useState(20)

  // 处理来自子组件的事件
  const handleClick = () => {
    setName('小红')
    setAge(25)
  }

  return (
    <div className="parent-container">
      <h1>父组件</h1>
      <p>姓名:{name}</p>
      <p>年龄:{age}</p>
      {/* 通过props向子组件传递数据和回调函数 */}
      <Child 
        name={name} 
        age={age} 
        handleClick={handleClick} 
      />
    </div>
  )
}

export default Parent
javascript
// 子组件(函数组件)
import React from 'react'
import PropTypes from 'prop-types'

// 使用解构赋值直接获取props
const Child = ({ name, age, handleClick }) => {
  return (
    <div className="child-container">
      <h2>子组件</h2>
      <p>姓名:{name}</p>
      <p>年龄:{age}</p>
      <button onClick={handleClick}>修改父组件数据</button>
    </div>
  )
}

// 使用PropTypes进行类型检查
Child.propTypes = {
  name: PropTypes.string.isRequired,
  age: PropTypes.number.isRequired,
  handleClick: PropTypes.func.isRequired
}

export default Child

使用Refs进行组件通信

Refs提供了一种方式,允许我们直接访问DOM节点或在render方法中创建的React元素。

父组件访问子组件

javascript
// 父组件通过Ref访问子组件
import React, { Component, createRef } from 'react'

class Parent extends Component {
  constructor(props) {
    super(props)
    // 创建Ref
    this.childRef = createRef()
  }

  // 调用子组件方法
  handleClick = () => {
    // 通过current访问子组件实例
    this.childRef.current.childMethod()
  }

  render() {
    return (
      <div>
        <h1>父组件</h1>
        <button onClick={this.handleClick}>调用子组件方法</button>
        {/* 将ref传递给子组件 */}
        <Child ref={this.childRef} />
      </div>
    )
  }
}

class Child extends Component {
  // 子组件方法
  childMethod = () => {
    console.log('子组件方法被调用')
    alert('子组件方法被调用')
  }

  render() {
    return (
      <div>
        <h2>子组件</h2>
      </div>
    )
  }
}

export default Parent

函数组件中使用forwardRef和useImperativeHandle

javascript
// 函数组件中使用Ref
import React, { useRef, useImperativeHandle, forwardRef } from 'react'

const Parent = () => {
  // 创建Ref
  const childRef = useRef()

  // 调用子组件方法
  const handleClick = () => {
    childRef.current.childMethod()
  }

  return (
    <div>
      <h1>父组件</h1>
      <button onClick={handleClick}>调用子组件方法</button>
      {/* 将ref传递给子组件 */}
      <Child ref={childRef} />
    </div>
  )
}

// 使用forwardRef包装子组件
const Child = forwardRef((props, ref) => {
  // 使用useImperativeHandle自定义暴露给父组件的实例值
  useImperativeHandle(ref, () => ({
    childMethod: () => {
      console.log('子组件方法被调用')
      alert('子组件方法被调用')
    }
  }))

  return (
    <div>
      <h2>子组件</h2>
    </div>
  )
})

export default Parent

Refs的最佳实践

  • 避免过度使用Refs,大多数情况下应优先考虑声明式编程和数据流
  • 不要在渲染期间访问Refs,可能导致不一致的UI
  • 使用Refs主要用于:
    • 管理焦点、文本选择或媒体播放
    • 触发命令式动画
    • 集成第三方DOM库

兄弟组件通信

兄弟组件之间没有直接联系,通常有以下几种通信方式:

  1. 通过共同的父组件进行状态提升(State Lifting)
  2. 使用Context API进行共享状态管理
  3. 使用第三方状态管理库(如Redux、MobX等)

方式一:状态提升

将共享状态提升到最近的共同父组件中,再通过props传递给子组件。

javascript
// 父组件
import React, { useState } from 'react'
import BrotherA from './BrotherA'
import BrotherB from './BrotherB'

const Parent = () => {
  // 状态提升:将共享状态放在父组件中
  const [sharedData, setSharedData] = useState('初始共享数据')

  // 提供修改共享状态的方法
  const updateSharedData = (newData) => {
    setSharedData(newData)
  }

  return (
    <div>
      <h1>父组件</h1>
      <p>共享数据: {sharedData}</p>
      {/* 将共享状态和更新方法传递给两个子组件 */}
      <BrotherA sharedData={sharedData} updateData={updateSharedData} />
      <BrotherB sharedData={sharedData} updateData={updateSharedData} />
    </div>
  )
}

export default Parent
javascript
// 兄弟组件A
import React from 'react'
import PropTypes from 'prop-types'

const BrotherA = ({ sharedData, updateData }) => {
  return (
    <div>
      <h2>兄弟组件A</h2>
      <p>接收到的共享数据: {sharedData}</p>
      <button onClick={() => updateData('来自组件A的数据')}>更新共享数据</button>
    </div>
  )
}

BrotherA.propTypes = {
  sharedData: PropTypes.string.isRequired,
  updateData: PropTypes.func.isRequired
}

export default BrotherA
javascript
// 兄弟组件B
import React from 'react'
import PropTypes from 'prop-types'

const BrotherB = ({ sharedData, updateData }) => {
  return (
    <div>
      <h2>兄弟组件B</h2>
      <p>接收到的共享数据: {sharedData}</p>
      <button onClick={() => updateData('来自组件B的数据')}>更新共享数据</button>
    </div>
  )
}

BrotherB.propTypes = {
  sharedData: PropTypes.string.isRequired,
  updateData: PropTypes.func.isRequired
}

export default BrotherB

方式二:Context API

Context提供了一种在组件树中共享数据的方式,无需显式地通过props逐层传递。

类组件中使用Context

javascript
// 创建Context
import React, { Component, createContext } from 'react'

// 创建一个Context对象
const MyContext = createContext()

// 父组件提供Context
class Parent extends Component {
  constructor(props) {
    super(props)
    this.state = {
      name: '小明',
      age: 20
    }
  }

  handleClick = () => {
    this.setState({
      name: '小红',
      age: 25
    })
  }

  render() {
    // 通过Provider提供值
    return (
      <MyContext.Provider value={{
        name: this.state.name,
        age: this.state.age,
        handleClick: this.handleClick
      }}>
        <div>
          <h1>父组件</h1>
          <p>姓名:{this.state.name}</p>
          <p>年龄:{this.state.age}</p>
          <Child1 />
          <Child2 />
        </div>
      </MyContext.Provider>
    )
  }
}

// 子组件1
class Child1 extends Component {
  // 使用静态contextType访问Context
  static contextType = MyContext

  handleClick = () => {
    this.context.handleClick()
  }

  render() {
    const { name, age } = this.context
    return (
      <div>
        <h2>子组件1</h2>
        <p>姓名:{name}</p>
        <p>年龄:{age}</p>
        <button onClick={this.handleClick}>修改数据</button>
      </div>
    )
  }
}

// 子组件2
class Child2 extends Component {
  static contextType = MyContext

  handleClick = () => {
    this.context.handleClick()
  }

  render() {
    const { name, age } = this.context
    return (
      <div>
        <h2>子组件2</h2>
        <p>姓名:{name}</p>
        <p>年龄:{age}</p>
        <button onClick={this.handleClick}>修改数据</button>
      </div>
    )
  }
}

export { Parent, Child1, Child2, MyContext }