Vue

Vue中组件通信的方式

回顾并总结了Vue2.x中组件通信的方式

Yixuan Lang
2021-08-11
6 min

# Vue中组件通信的方式

都知道组件是Vue中最强大的功能之一,在Vue中进行组件化开发时,经常会提到“组件通信”。这是使用Vue非常重要的部分,Vue中每一个.vue文件我们都可以视之为一个组件通信的发送者,该文件中有着自身组件所维护的数据,该数据可以通过某种通信方式传递给其他的.vue文件使用。该篇文章主要针对Vue中组件通信的方式进行梳理与总结。

Vue组件通信

# 一、组件之间的关系

在正式了解组件间通信方式前,我们先来看看组件之间的关系是什么样子的。

组件之间的关系

组件之间的关系就像是我们的家谱一样,根据上图我们可以把组件之间的关系分成两个大类

  • 父子关系
  • 非父子关系(兄弟关系、堂兄弟关系、隔代关系...)

根据这些关系,本文会详细介绍这些组件关系在组件通信的问题上是如何进行解决的

# 二、组件通信的方式


# (一) 父子组件通信

# 1. props / $emit

# props传递数据

  • 使用场景:父组件给子组件传值
  • 使用方式:在父组件里给使用的子组件绑定自定义的属性进行传值,子组件可以通过props属性进行接收
// 父组件
<template>
  <div id="parent">
    <Children :name="name"/>//前者自定义名称便于子组件调用,后者要传递数据名
  </div>
</template>
<script>
  import Users from "./components/Children"
  export default {
    name: 'Parent',
    components: { Children },
    data(){
      return{
        name: 'Langyixuan'
      }
    }
  }
</script>
// 子组件 
<template>
  <div>
    <span>{{name}}</span>
  </div>
</template>

<script>
export default {
  name: 'Children',
  props: ['name']
}
</script>

总结: prop 只可以从上一级组件传递到下一级组件(父子组件),即所谓的单向数据流。而且 prop 只读,不可被修改,所有修改都会失效并警告。

# $emit触发自定义事件

  • 使用场景:子组件传递数据给父组件
  • 使用方式:在父组件中使用的子组件上绑定自定义事件,在子组件里设置触发自定事件的机制,通过$emit方式触发父组件上的自定义事件,并将想要传递的数据通过参数的形式让父组件接收。
// 父组件
<template>
  <div id="parent">
    <Children @receiveData="receiveData"/>  
  </div>
</template>
<script>
  import Children from './components/Children'
  export default {
    name: 'Parent',
    components: { Children },
    data() {
      return {
        name: ''
      }
    },
    methods: {
      receiveData(name){
        this.name = name
      }
    }
  }
</script>
// 子组件 
<template>
  <div>
      <button @click="sendData">点击我给父组件发送数据</button>
  </div>
</template>

<script>
export default {
  name: 'Children',
  props: ['name'],
  methods: {
    sendData(){
      this.$emit('receiveData', this.name)  // 触发父组件的自定义事件并通过参数的形式传递数据
    }
  }
}
</script>

# 2. $parent / $children

# $parent

  • 使用场景:子组件获取父组件的数据
  • 使用方式:可以在一个子组件中通过$parent属性来访问其父组件的实例对象,这样就可以访问暴露在父组件上的任意数据了
  • 使用注意:虽然$parent可以非常方便的访问父组件的数据,但是这种方式可能会修改父组件中的数据,导致父组件的数据变更后,很难去定位这个数据是从哪里发起的,所以绝大多数情况下,不推荐使用
// 父组件中
<template>
  <div id="parent">
    <Children />
  </div>
</template>

<script>
import Children from './components/Children'
export default {
  name: 'Parent',
  components: { Children },
  data() {
    return {
      msg: 'Welcome'
    }
  },
  mounted() {
    console.log(this.$children[0].name)  // 'Langyixuan'
  }
}
</script>
// 子组件中
<script>
export default {
  data() {
    return {
      name: 'Langyixuan'
    }
  },
  mounted() {
    console.log(this.$parent.msg)   // 'Welcome'
  }
}
</script>

# $children

$chidren和$parent的使用方式同理,使用场景是从父组件中可以直接通过$children获取到子组件实例对象

要注意边界情况,如在#app上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent得到的是undefined,而在最底层的子组件拿$children是个空数组。也要注意得到$parent$children的值不一样,$children 的值是数组,而$parent是个对象

# 3. $refs

  • 使用场景:访问子组件中的数据
  • 使用方式:ref属性放在普通的DOM元素上使用时,引用指向的就是DOM元素对象。如果放在子组件上使用时,引用就指向子组件的实例对象。所以可以通过该种方式直接使用$refs来访问组件的实例对象
// 父组件
<template>
  <div id="parent">
    <Children ref="child1"/>  
  </div>
</template>
<script>
  import Children from './components/Children'
  export default {
    name: 'Parent',
    components: { Children },
    data() {
      return {
        name: ''
      }
    },
    mounted(){
      this.name = this.$refs.child1.name
    }
  }
</script>
// 子组件 
<script>
export default {
  name: 'Children',
  data(){
    return {
      name: 'Langyixuan'
    }
  }
}
</script>

# (二) 非父子组件通信

# 1. provide 和 inject

  • 使用场景:给后代组件传递数据
  • 使用方式:provideinject 需要在一起使用,它可以使一个祖先组件向其所有子孙后代注入一个依赖,可以指定想要提供给后代组件的数据/方法,不论组件层次有多深,都能够使用。
  • 使用注意:provideinject 绑定不是响应的,它被设计是为组件库和高阶组件服务的,平常业务中的代码不建议使用。
<!--祖先组件-->
<script>
export default {
    provide: {
        author: 'Langyixuan',
    },
    data() {},
}
</script>

<!--后代组件-->
<script>
export default {
    inject: ['author'],
    created() {
        console.log('author', this.author) // => Langyixuan
    },
}
</script>

# 2. $attrs 和 $listeners

# $attrs

  • 使用场景:当要和一个嵌套很深的组件进行通信时,如果使用 propevents 就会显的十分繁琐,中间的组件只起到了一个中转站的作用,像下面这样:
<!--父组件-->
  <parent-component :message="message">我是父组件</parent-component>
<!--子组件-->
  <child-component :message="message">我是子组件</child-component>
<!--孙子组件-->
  <grand-child-component :message="message">我是孙子组件</grand-child-component>
  • 使用方式:$attrs 包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。

# $listeners

  • 使用方式:$listeners 会包含所有父组件中的 v-on 事件监听器 (不包含 .native 修饰器的) ,可以通过 v-on="$listeners" 传入内部组件。

下面以父组件和孙子组件的通信为例介绍$attrs$listeners的使用:

<!--父组件 parent.vue-->
<template>
    <child :name="name" :message="message" @sayHello="sayHello"></child>
</template>
<script>
export default {
    inheritAttrs: false,
    data() {
        return {
            name: '通信',
            message: 'Hi',
        }
    },
    methods: {
        sayHello(mes) {
            console.log('mes', mes) // => "hello"
        },
    },
}
</script>
<!--子组件 child.vue-->
<template>
    <grandchild v-bind="$attrs" v-on="$listeners"></grandchild>
</template>
<script>
export default {
    data() {
        return {}
    },
    props: {
        name,
    },
}
</script>
<!--孙子组件 grand-child.vue-->
<template>
</template>
<script>
export default {
    created() {
        this.$emit('sayHello', 'hello')
    },
}
</script>

# 3. EventBus全局事件总线

  • 使用场景:任意两个组件之间进行通信。除了父子、祖先这种比较直系的关系,组件之间还存在兄弟、堂兄弟等等一些比较复杂的组件关系,这种关系在比较小型的项目中可以使用eventBus全局事件总线的方式进行通信。
  • 使用方式:注册一个全局的事件总线EventBus,发送数据的组件通过$emit触发自定义事件,$emit第二个参数为传递的值。接收的组件通过$on监听自定义事件以参数的形式接收数据
  • 使用注意:如果业务有反复操作的页面,EventBus在监听的时候就会触发很多次,也是一个非常大的隐患。这时候我们就需要好好处理EventBus在项目中的关系。通常会用到,在vue页面销毁时,同时使用$off移除EventBus事件监听。
// mian.js
// 注册全局事件总线
Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能  
// 组件A(发送数据的组件)
<script>
export default {
  data() {
    return {
      msg: 'hello'
    }
  }
  methods: {
    sayHello() {
      this.$bus.$emit('sayHello', this.msg);  // 触发自定义事件sayHello, 并传递msg状态
    }
  }
}
</script>
// 组件B(接收数据的组件)
<script>
export default {
  created() {
    this.$bus.$on('sayHello', target => {
      console.log(target);  // => 'hello'
    })
  }
}
</script>

# 4. Vuex

  • 使用场景:任意组件关系之间的通信,Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。对一个中大型单页应用来说是不二之选。

  • 使用注意:对于小型的项目,通信十分简单,这时使用 Vuex 反而会显得冗余和繁琐,这种情况最好不要使用 Vuex,可以自己在项目中实现简单的 Store。

  • 使用方式:官方文档

    • state:用于数据的存储,是store中的唯一数据源

    • getters:如vue中的计算属性一样,基于state数据的二次包装,常用于数据的筛选和多个数据的相关性计算

    • mutations:类似函数,改变state数据的唯一途径,且不能用于处理异步事件

    • actions:类似于mutation,用于提交mutation来改变状态,而不直接变更状态,可以包含任意异步操作

    • modules:类似于命名空间,用于项目中将各个模块的状态分开定义和操作,便于维护

Vuex

# 总结

  • 父子关系的组件数据传递选择 props$emit进行传递,也可选择ref
  • 兄弟关系的组件数据传递可选择$bus,其次可以选择$parent进行传递
  • 祖先与后代组件数据传递可选择attrslisteners或者 ProvideInject
  • 复杂关系的组件数据传递可以通过vuex存放共享的变量

# 参考文章

vue 组件通信看这篇就够了(12种通信方式)

vue中8种组件通信方式, 值得收藏!