React / Vue 嵌套组件通信

1,603 阅读3分钟

前言

有些数据需要在多个子组件间共享,或者父级组件需要向孙组件传递数据等情况,本文将梳理在React和Vue中嵌套的组件之间如何通信。

React 嵌套组件之间通信

React.createcontext()

首先来看下这个 api 返回值。

常用到的就是这个对象下的 Provider 和 Consumer 组件,来看下使用案例:

  1. 在 context.js 中拿到我们需要用到的这两个组件
    • Provider 是数据的来源,可以为多个 Consumer提供数据,如果组件中使用多个 Provider时,组件会找离自己最近的 Provider 拿数据
    • Consumer 就是使用数据的地方,任何被包裹的组件都可以通过向 Consumer 组件传递一个函数中的回调中拿到 Provider 组件中共享的数据。
  2. 在父组件中通过 Provider 的 value 属性共享数据。
  3. 在所有的子孙组件中,通过 Consumer 来接收到父组件中共享的数据
// context.js 
import React from 'react'
export const { Provider , Consumer} = React.createContext()

// father.jsx 
<Provider value={{title:'标题'}}>
    <Second></Second>
</Provider>

// Second.js
<Consumer>
    {
      state=>{
        return <Third {...state}></Third>
      }
    }
</Consumer>


// Third.js
render(){
    return <div> 
          接收到 provider 传递的值: {this.props.title}
     </div>   
}

导致的问题,一旦 Provider 组件接收的 value 值发生改变,所有 Consumer 组件包裹的组件都会重新渲染。

React.Children 方式传递孙组件

正常当前组件和孙组件传递数据通过 Props 传递,如 A -> B -> C 如果 B(子组件)有时候可能并不需要使用这些 props。所以找一种方式将数据直接传递给孙组件。

<Second>
  <Third value={{title:'标题'}}></Third>
</Second>

// Second.jsx
<div>
{
    // 兼容children 是单个和多个的情况
    React.Children.map(this.props.children,function(child){
        // 可以添加逻辑判断是否要选入 child 
        return child
    })
}
</div>

上面代码中,将Third(孙组件)作为 children 传递给 Second 组件,

并且不仅父级组件可以将一些数据直接传递给 Third 组件。如果 Third 组件的 children 也可以通过函数向外部传递出一些数据

<Third>
  {(value)=>{
    return <h1>{value}</h1>
  }}
</Third>
// third.jsx
render(){
    return <div> 
      接收到 provider 传递的值: {this.props.children('h1内容')}
    </div>   
}

Vue 嵌套组件之间通信

eventBus

Vue 中使用 eventBus ,其实就是创建一个 Vue 实例专门用来操作、管理数据。来看下面这个例子 使用步骤分为三个步骤

  1. 创建 Vue 实例,用于操作数据
  2. 父组件通过这个实例 .$on 方法来注册监听事件
  3. 子组件通过这个实例 .$emit 方法来派发事件。

方式1:单文件引入

// EventBus.js
import Vue from 'vue'
// 通过一个新的 Vue 实例来单独管理数据状态
export default new Vue()

// father.js

import EventBus from './EventBus'
// 需要使用箭头函数才能拿到本组件中的实例对象
EventBus.$on('increment',(value)=>{
  // ...
})
// 如果使用正常函数的话,this指向的是 EventBus 实例
EventBus.$on('decrease',function(value){
  // ...
})

// child.js

import EventBus from '../EventBus'
increment(){
  EventBus.$emit('increment',{value:this.value})
},
decrease(){
  EventBus.$emit('decrease',{value:this.value})
},

可能会好奇,为啥这个实例存在 $on 、$emit 这些方法,其实每个 Vue 实例中原型上都挂载了这些方法。可以在任意一个 Vue 组件中 console.log(this['__proto__']['__proto__'])

this问题

如果我们想通过 EventBus.$on 来修改当前组件实例的数据,回调函数需要使用箭头函数, EventBus 本身也是一个 Vue 实例,所以使用正常的 function this会指向 EventBus 导致没法修改当前组件的状态。

我的理解就是借用 EventBus 实例中的方法来修改其他实例中的数据,所以我们在修改数据时需要弄清楚 this 的指向。

为什么不直接在组件中使用 $on,$emit,$off 这些方法来修改数据,而需要新建一个实例来管理? 因为每个组件都是一个Vue实例,这些方法只能在单个组件中注册监听,如果跨组件则无法监听到,所以需要 new Vue() 实例话一个单独的组件专门用于管理这些数据。

方式二:全局挂载

import Vue from 'vue'
const EventBus = new Vue()
Vue.prototype.$bus = EventBus

// father.js
this.$bus.$on('add',()=>{
  this.num++
})
// child.js
add(){
  this.$bus.$emit('add')
}

slot

使用 React.Children 那种通过向子组件传递孙组件的方法用Vue实现逻辑如下

// 子组件
<Search :placeholder="placeholder">
  <div slot-scope="scope">
    {{ scope }}
  </div>
</Search>
// 孙组件
<slot content="{titel:'标题'}"></slot>

最后

这里没有使用 Vuex 和 redux 这类去管理数据,如果还有其他好用的数据管理的方法,欢迎在留言区交流👏