Vue组件间通信方式|青训营笔记

176 阅读4分钟

这是我参与「第四届青训营 」笔记创作活动的第1天。

概述

在基于Vue开发项目时,必然需要用到组件间的通信。本文将总结Vue中常见的组件通信方式,并对每种方式的适用场景进行分析。

组件间通信方式可以分为以下四种类型:

  • 父子组件之间通信
  • 兄弟组件之间通信
  • 祖先与后代之间通信
  • 非关系组件之间通信 image.png

组件间的通信方式

1.通过props传递

适用场景:父组件给子组件传递信息 父组件在子组件的组件标签中通过字面量传递,子组件通过props接收

通过props传递数据的方式很简单,在此不做示例。

但是在使用props时有一些需要注意的点:

  1. 不能在子组件中修改props传过来的数据,这样会破坏vue提倡的单向数据流。
  2. 对于普通类型,在修改数据时会报错,如果有修改需求的话,可以通过data或computed将传过来的数据做一个转换。
  3. 对于引用类型,修改元素而不修改引用时,不会报错,且在子组件中修改了,父组件中也会做相应的更新,相当于实现了父子组件数据的双向绑定,但是不推荐这么做。如果有父子组件双向数据绑定的需求的话,可以使用v-model或.sync(在后面会介绍)。

2.自定义事件

适用场景:子组件给父组件传递信息 子组件通过$emit触发自定义事件,第二个参数可以传递数据 父组件通过绑定监听器获得传过来的数据

示例:

子组件

this.$emit('add', good)  

父组件

<Children @add="cartAdd($event)" />  

3.ref

适用场景:父组件需要主动获取子组件中的数据或方法 ref特性可以为子组件赋予一个ID引用,通过这个ID,父组件可以获取子组件的组件实例对象,也就能拿到对象身上的数据和方法了。

父组件

<Children ref="foo" />  
  
this.$refs.foo  // 获取子组件实例,通过子组件实例我们就能拿到对应的数据  

在使用ref时的注意点: 1.$refs不是响应式的,它只能获得那一时刻的状态,因此要避免在computed中使用获取到的数据。

4.全局事件总线$Bus

适用场景:任意组件之间传递

main.js中:

// 创建全局事件总线
Vue.prototype.Bus = new Vue();
```

在一个组件中传递数据:

this.$bus.$emit('foo',参数)  

在另一个组件中接收:

this.$bus.$on('foo', callback)  

5.Vuex数据仓库

适用场景:复杂关系的组件间通信 Vuex相当于一个存储数据的仓库,组件取数据/修改数据等,都对这个仓库进行处理。

image.png

6.插槽

适用场景:父组件向子组件传递结构 插槽可以分为

  • 默认插槽
  • 具名插槽
  • 作用域插槽:子组件的数据来源于父组件,但是子组件的自己的结构由父亲决定。
  1. 默认插槽:

在子组件<submit-button>中:

<button type="submit">
  <slot></slot>
</button>

如果我们希望这个 <button> 内绝大多数情况下都渲染文本“Submit”,但是有时候却希望渲染文本为别的东西,怎么实现呢? 我们可以将“Submit”作为后备内容,将它放在 <slot> 标签内:

<button type="submit">
  <slot>Submit</slot>
</button>

父级组件中使用 <submit-button> 并且不提供任何插槽内容时:

<submit-button></submit-button>

后备内容“Submit”将会被渲染:

<button type="submit">
  Submit
</button>

但是如果我们在父组件中提供内容:

<submit-button>
  Save
</submit-button>

则这个提供的内容将会被渲染从而取代后备内容:

<button type="submit">
  Save
</button>

2.具名插槽

有时我们写了一个子组件,希望:

<template>
    <div class="container">
      <header>
        <!-- 我们希望把页头放这里 -->
      </header>
      <main>
        <!-- 我们希望把主要内容放这里 -->
      </main>
      <footer>
        <!-- 我们希望把页脚放这里 -->
      </footer>
    </div>
</template>

对于这样的情况,<slot> 元素有一个特殊的 attribute:name。这个 attribute 可以用来定义额外的插槽:

<template>
  <div class="container">
    <header>
      <slot name="header"></slot>
    </header>
    <main>
      <slot></slot>
    </main>
    <footer>
      <slot name="footer"></slot>
    </footer>
  </div>
</template>

一个不带 name 的 <slot> 出口会带有隐含的名字“default”。 父组件在向具名插槽提供内容的时候,我们可以在一个 <template> 元素上使用 v-slot 指令,并以 v-slot 的参数的形式提供其名称:

<template>
  <myslot>
    <div>大家好我是父组件</div>
    <template v-slot:header>
      <h1>Here might be a page title</h1>
    </template>

    <p>A paragraph for the main content.</p>
    <p>And another one.</p>

    <template v-slot:footer>
      <p>Here's footer info</p>
    </template>
  </myslot>
</template>

渲染结果为:

Here might be a page title
大家好我是父组件
A paragraph for the main content.

And another one.

Here's footer info

注意:v-slot只能添加在<template>上,具名插槽v-slot在书写时,可以缩写为#。

3.作用域插槽

作用域插槽比较难理解,大概意思是子组件的数据来源于父组件,但是子组件的自己的结构有父亲决定。 作用域插槽具体理解后续补充。

7.v-model指令

适用场景:实现父子组件的双向数据绑定 v-model的本质是v-bind和v-on的语法糖。在一个组件上使用v-model相当于为这个组件绑定了名为value的prop和名为input的事件。 当组件中的某个prop需要实现和父组件的双向绑定,也就是当子组件中prop值改变时,父组件同时改变,可以使用v-model来实现:

父组件

<template>
    <div>
    <!--父组件通过v-model将名为value的total传给子组件,同时监听一个名为input的事件-->
        <child v-model="total"></child>
    </div>
</template>

<script>
import Child from "./child.vue"
export default {
    components: {
        Child
    },
    data: function () {
        return {
            total: 0
        };
    },
}
</script>

子组件

<template>
    <div>
        <span>{{value}}</span>
        <button @click="reduce">减少5</button>
    </div>
</template>

<script>
export default {
    props: {
    //得到父组件传过来的value
        value: Number  
    },
    methods: {
        reduce: function(){
            //触发名为input的事件,并传递参数,这个参数会赋值给父组件中的total
            this.$emit("input", this.value - 5)
        }
    }
}
</script>

点击子组件中减少5这个按钮,子组件中的value-5,同时父组件中的total-5。

8. .sync修饰符

适用场景:父子组件间数据双向绑定

和v-medol一样,.sync也是一个语法糖。

注意点:v-model和.sync都能实现父子组件数据的双向绑定。相比之下,.sync更灵活,v-model在一个组件中只能有一个,而.sync可以有多个。

9. $parent和$children

适用场景:父子组件通信

$parent 属性可以用来从一个子组件访问父组件的实例,$children 属性可以获取当前实例的直接子组件。

看起来使用 $parent 比使用prop传值更加简单灵活,可以随时获取父组件的数据或方法,又不像使用 prop 那样需要提前定义好。但使用 $parent 会导致父组件数据变更后,很难去定位这个变更是从哪里发起的,所以在绝大多数情况下,不推荐使用。

10. $attrs和$listeners

适用场景:父组件给子组件传递信息

$attrs是组件实例的属性,可以获得父组件传递的props(前提子组件没有通过props接受) $listeners也是组件实例的属性,可以获取父组件传递过来的自定义事件(以对象形式呈现)