React组件通信总结

1,281 阅读4分钟

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

react组件通信无非就是这几种情况:父子组件通信、爷孙/祖孙之间通信、兄弟组件通信、其他关系组件通信。

父子组件的通信

父传子

因为 React 的设计实际上就是传递 Props 即可。那么场景体现在容器组件与展示组件之间,通过 Props 传递 state,让展示组件受控。

可以在子组件标签上定义属性将父组件的值传进去,在子组件可以使用props接收传进来的属性值。示例:

function Parent() {
  const [data, setData] = useState(1)
  const change = () => {
    setData(data + 1)
  }
  return (
    <div onClick={change}>
      父传子
      <Child data={data} />
    </div>
  )
}
function Child(props) {
  return (
    <div>
      子组件接收到的值:{props.data}
    </div>)
}

子传父

有两种方式可以支持子传父,可以使用回调函数与实例函数。

1、使用回调函数

在父组件内定义一个回调函数getChildVal,将这个函数绑定在子组件上通过props传入子组件,当子组件中是事件或者什么时机需要向父组件传值时,在子组件内就可以调用这个函数并传入值,此时定义在父组件的函数被触发,并且可以拿到子组件的传值

function Parent() {
  const [data, setData] = useState()
  const getChildVal = (val) => {
    console.log('从子组件获取的值',val)
    setData(val)
  }
  return (
    <div>
      父组件接收到的值:{data}
      <Child onchange={getChildVal} />
    </div>
  )
}
function Child(props) {
  const [val, setVal] = useState(1)
  const changeVal = () => {
    setVal(val+1)
    props.onchange(val+1)
  }
  return (
    <div onClick={changeVal}>
      子传父
    </div>)
}

2、使用实例(ref)

以function组件为例,具体操作为:1. 使用useRef初始化一个ref。2. 将这个ref设置在需要的元素或子组件上。3. 通过定义的ref.current访问元素

import React, {useRef, useRef} from 'react';
function Parent() {
  const nameRef = useRef(null)
  const btnClick = () => {
    console.log(nameRef.current, nameRef.current.value)
  }
  return (
    <div>
      <ChildRef ref={nameRef} />
      <button onClick={btnClick}>提交</button>
    </div>
  )
}
// 如果ref是设置在自定义组件,需要React.forwardRef处理一下
const ChildRef = React.forwardRef(Child)
function Child(props, ref) {
  return(
    <div>
      年龄:<input ref={ref}></input>
    </div>
  )
}

其他更多react关于ref的内容看这里

跨层级通信

多级通信有两种情况:

第一种是一个容器中包含了多层子组件,需要最底部的子组件与顶部组件进行通信。在这种情况下,如果不断透传 Props 或回调函数,不仅代码层级太深,后续也很不好维护。

第二种是两个组件不相关,在整个 React 的组件树的两侧,完全不相交。

那么基于多层级间跨级的通信一般有三个方案。

  1. 使用 React 的 Context API,最常见的用途是做语言包国际化
  2. 使用全局变量与事件。
  3. 使用状态管理框架。

先说一个透传示例:爷传孙

function Parent() {
  const [data, setData] = useState(1)
  const change = () => {
    setData(data + 1)
  }
  return (
    <div onClick={change}>
      爷传孙
      <Current data={data} />
    </div>
  )
}
function Current(props) {
    return <Child {...props} />
}
function Child(props) {
  return (
    <div>
      孙组件接收到的值:{props.data}
    </div>)
}

这种方法呢,要根据实际情况来,如果跨级比较多组件繁琐不建议使用,维护起来比较麻烦。

Context

使用方法:1. createContext创建一个context对象。2. 使用Provider设置value属性传递值

import React, { Component } from 'react';
// 1.创建context对象
const Context = React.createContext();
// 2.父辈组件使用Provider传值
class Demo extends Component{
  render() {
    return <Context.Provider value={10}>
    <ClassDemo />
  </Context.Provider>
  }
}
// 3.子组件用contextType进行取值
class ClassDemo extends Component{
  static contextType = Context  
  render() {
    console.log(this.context)
    return <div>0</div>
  }
}

自定义全局变量及事件

比如可以类似与vue一样,我们在react中去做一个EventBus去做不相关组件之间的通信。

先定义一个全局EventBus(例如在utils/eventbus.js中定义)

// Bus:事件派发、监听和回调管理
class Bus {
  constructor(){
    this.callbacks = {}
  }
  $on(name, fn){
    this.callbacks[name] = this.callbacks[name] || []
    this.callbacks[name].push(fn)
  }
  $emit(name, ...args){
    if(this.callbacks[name]){
      this.callbacks[name].forEach(cb => cb(...args))
    }
  }
  $off(name) {
    if (this.callbacks[name]) {
      delete this.callbacks[name]
    }
  }
}
const EventBus = new Bus()
export default EventBus;

使用该EventBus通信:

注册的一个事件后在多个地方都是可以触发的。比如说A组件中需要去监听一个全局状态,然后在B组件、C组件都可以去修改这个状态,那么就可以在B组件和C组件需要的时机去触发这个事件,A组件都可以监听相应到。

// A组件
import EventBus from '@/utils/eventbus';
function ALayout(){
  const [color, setColor] = useState('red')
  useEffect(()=>{
    EventBus.$on('change', (c, val, val2) => {
      setColor(c)
      // 还可以接收其他额外的值
      console.log('取到的传值:', val, val2)
    })
  },[])
  return (<div style={{color}}>A组件全局颜色</div>)
}
// B组件
import EventBus from '@/utils/eventbus';
function B(){
  // 触发事件并传值
  const emitEvent = () => {
    EventBus.$emit('change', 'blue', '其他值', 2)
  }
  return (<div onClick={emitEvent}>B组件设置为蓝色</div>)
}
// C组件
import EventBus from '@/utils/eventbus';
function C(){
  // 触发事件并传值
  const emitEvent = () => {
    EventBus.$emit('change', 'green')
  }
  return (<div onClick={emitEvent}>C组件设置为绿色</div>)
}

使用状态管理框架

比如 Flux、Redux 及 Mobx。

当组件绑定一个的model后,此model中的state数据改变时,相应其他组件中用到了这个model中的state也会同步改变。

这种方法的优点是由于引入了状态管理,使得项目的开发模式与代码结构得以约束,缺点是学习成本相对较高。