详解Vue中组件通信的方式

目录
文章目录隐藏
  1. prop/$emit
  2. .sync 修饰符
  3. $attrs 和$listeners
  4. provide 和 inject
  5. EventBus
  6. vuex
  7. $refs
  8. $parent 和$children
  9. 结语

vue 是数据驱动视图更新的框架, 所以对于 vue 来说组件间的数据通信非常重要;我们常用的方式莫过于通过 props 传值给子组件,但是 vue 还有其他很多不常用的通信方式,了解他们,也许在以后在写代码的时候能给你带来更多的思路和选择。

prop/$emit

父组件通过 prop 的方式向子组件传递数据,而通过$emit 子组件可以向父组件通信。

prop/$emit

我们可以通过 prop 向子组件传递数据;用一个形象的比喻来说,父子组件之间的数据传递相当于自上而下的下水管子,管子中的水就像数据,水只能从上往下流,不能逆流。这也正是 Vue 的设计理念之单向数据流。而 prop 正是管道与管道之间的一个衔接口,这样水(数据)才能往下流。

prop 向子组件传递数据

运行结果如下:

prop 向子组件传递数据

在子组件中我们通过 props 对象定义了接收父组件值的类型和默认值,然后通过$emit()触发父组件中的自定义事件。prop/$emit 传递数据的方式在日常开发中用的非常多,一般涉及到组件开发都是基于通过这种方式;通过父组件中注册子组件,并在子组件标签上绑定对自定义事件的监听。他的优点是传值取值方便简洁明了,但是这种方式的缺点是:

  • 由于数据是单向传递,如果子组件需要改变父组件的 props 值每次需要给子组件绑定对应的监听事件。
  • 如果父组件需要给孙组件传值,需要子组件进行转发,较为不便。

.sync 修饰符

有些情况下,我们希望在子组件能够“直接修改”父组件的 prop 值,但是双向绑定会带来维护上的问题;vue 提供了一种解决方案,通过语法糖.sync 修饰符。

.sync 修饰符在 vue1.x 的时候曾作为双向绑定功能存在,即子组件可以修改父组件中的值。但是它违反了单向数据流的设计理念,所以在 vue2.0 的时候被干掉了。但是在 vue2.3.0+ 以上版本又重新引入了。但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on 监听器。说白了就是让我们手动进行更新父组件中的值了,从而使数据改动来源更加的明显。

sync 修饰

我们在 Child 组件传值时给每个值添加一个.sync 修饰,在编译时会被扩展为如下代码:

Child 组件传值

因此子组件中只需要显示的触发 update 的更新事件:

update 的更新事件

运行结果如下:

运行结果

这种“双向绑定”的操作是不是看着似曾相识?是的,v-model 本质上也是一种语法糖,只不过它触发的不是 update 方法而是 input 方法;而且 v-model 没有.sync 来的更加灵活,v-model 只能绑定一个值。

总结:.sync 修饰符优化了父子组件通信的传值方式,不需要在父组件再写多余的函数来修改赋值。

$attrs 和$listeners

当需要用到从 A 到 C 的跨级通信时,我们会发现 prop 传值非常麻烦,会有很多冗余繁琐的转发操作;如果 C 中的状态改变还需要传递给 A,使用事件还需要一级一级的向上传递,代码可读性就更差了。

$attrs 和$listeners

因此 vue2.4+版本提供了新的方案:$attrs 和$listeners,我们先来看一下官网对$attrs 的描述:

包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件——在创建高级别的组件时非常有用。

官网描述

这一大段话第一次读非常的绕口,而且晦涩难懂,不过没关系,我们直接上代码:

官网对$attrs 的描述

我们首先定义了两个 msg,一个给子组件展示,另一个给孙组件展示,首先将这两个数据传递到子组件中,同时将两个改变 msg 的函数传入。

改变 msg 的函数传入

在子组件中我们通过 props 获取子组件所需要的参数,即 childMsg;剩余的参数就被归到了$attrs 对象中,我们可以在页面中展示出来,然后把它继续往孙组件中传;同时把所有的监听函数归到$listeners,也继续往下传。

监听函数归到$listeners

在孙组件中我们继续取出所需要的数据进行展示或者操作,运行结果如下:

数据进行展示

当我们在组件上赋予一个非 prop 声明时,比如 child 组件上的 notuse 和 grandchildmsg 属性我们没有用到,编译之后的代码会把这个属性当成原始属性对待,添加到 html 原生标签上,所以我们查看代码是这样的:

child 组件上的 notuse

这样会很难看,我们可以在组件上加上 inheritAttrs 属性将它去掉:

inheritAttrs 属性

总结:$attrs 和$listeners 很好的解决了跨一级组件传值的问题。

provide 和 inject

虽然$attrs 和$listeners 可以很方便的从父组件传值到孙组件,但是如果跨了三四级,并且想要的数据已经被上级组件取出来,这时$attrs 就不能解决了。

provide/inject 是 vue2.2+版本新增的属性,简单来说就是父组件中通过 provide 来提供变量,然后再子组件中通过 inject 来注入变量。这里 inject 注入的变量不像$attrs,只能向下一层;inject 不论子组件嵌套有多深,都能获取到。

$attrs 和$listeners

我们在父组件通过 provide 注入了两个变量,并且在两秒之后修改变量的值,然后就在子组件和孙组件取出来。

父组件通过 provide 注入了两个变量

运行结果如下:

运行结果

可以看到子组件和孙组件都能取出值,并且渲染出来。需要注意的是,一旦子组件注入了某个数据,在 data 中就不能再声明这个数据了。

同时,过了两秒后我们发现 childmsg 和 grandmsg 的值并没有按照预期的改变,也就是说子组件并没有响应修改后的值,官网的介绍是这么说的:

提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

vue 并没有把 provide 和 inject 设计成响应式的,这是 vue 故意的,但是如果传入了一个可监听的对象,那么就可以响应了:

provide 和 inject 设计成响应式

那么为什么上面的 props 和$attrs 都是响应式的,连破坏“单向数据流”的.sync 修饰符都是响应式的,但到了 provide/inject 就不是响应式的了呢?

首先我们试想一下,如果有多个子组件同时依赖于一个父组件提供的数据,那么一旦父组件修改了该值,那么所有组件都会受到影响,这是我们不希望看到的;这一方面增加了耦合度,另一方面使得数据变化不可控制。接着看一下 vue 是怎么来实现 provide/inject 的。

vue 是怎么来实现 provide/inject

可以看到初始化 provide 的时候将父组件的 provide 挂载到 _provided,但它不是一个响应式的对象;然后子组件通过$parent 向上查找所有父组件的 _provided 获取第一个有目标属性的值,然后遍历绑定到子组件上;因为只是初始化的时候绑定的,而且 _provided 也不是响应式的,所以造成了 provide/inject 的这种特性。

那么 provide/inject 这么危险,又不是响应式的,它能拿来做什么呢?打开 element-ui 的源码搜索 provide,我们可以看到非常多的组件使用了 provide/inject,我们就拿 form、form-item 和 button 举个例子。

form 和 form-item 都可以传入一个属性 size 来控制子组件的尺寸,但是子组件的位置是不固定的,可能会嵌套了好几层 el-row 或者 el-col,如果一层一层的通过 props 传 size 下去会很繁琐,这是 provide/inject 就派上用处了。

provide/inject 传入属性 size

我们通过父组件将 elFormItem 本身注入到子组件中,子组件通过 inject 获取父组件本身然后动态地计算 buttonSize。

总结:provide/inject 能够解决多层组件嵌套传值的问题,但是是非响应的,即 provide 与 inject 之间没有绑定,注入的值是在子组件初始化过程中决定的。

EventBus

EventBus 我刚开始直接翻译理解为事件车,但比较官方的翻译是事件总线。它的实质就是创建一个 vue 实例,通过一个空的 vue 实例作为桥梁实现 vue 组件间的通信。它是实现非父子组件通信的一种解决方案,所有的组件都可以上下平行地通知其他组件,但也就是太方便所以若使用不慎,就会造成难以维护的“灾难”。

EventBus 我刚开始直接翻译理解为事件车首先创造一个空的 vue 对象并将其导出,他是一个不具备 DOM 的组件,它具有的仅仅只是它实例方法而已,因此它非常的轻便。创造一个空的 vue 对象并将其导出

将其挂载到全局,变成全局的事件总线,这样在组件中就能很方便的调用了。

全局的事件总线

我们先定义了两个子组件 child1 和 child2,我们希望这两个组件能够直接给对方发送消息。

两个子组件 child1 和 child2

我们初始化时在 child1 和 child2 中分别注册了两个接收事件,然后点击按钮时分别触发这两个自定义的事件,并传入数据,最后两个组件分别能接收到对方发送的消息,最后效果如下:

运行结果

前面也提到过,如果使用不善,EventBus 会是一种灾难,到底是什么样的“灾难”了?大家都知道 vue 是单页应用,如果你在某一个页面刷新了之后,与之相关的 EventBus 会被移除,这样就导致业务走不下去。还要就是如果业务有反复操作的页面,EventBus 在监听的时候就会触发很多次,也是一个非常大的隐患。这时候我们就需要好好处理 EventBus 在项目中的关系。通常会用到,在页面或组件销毁时,同时移除 EventBus 事件监听。

EventBus 事件监听

总结:EventBus 可以用来很方便的实现兄弟组件和跨级组件的通信,但是使用不当时也会带来很多问题;所以适合逻辑并不复杂的小页面,逻辑复杂时还是建议使用 vuex。

vuex

在 vue 组件开发中,经常会遇到需要将当前组件的状态传递给其他非父子组件组件,或者一个状态需要共享给多个组件,这时采用上面的方式就会非常麻烦。vue 提供了另一个库 vuex 来解决数据传递的问题;刚开始上手会感觉 vuex 非常的麻烦,很多概念也容易混淆,不过不用担心,本文不深入讲解 vuex。

vuex 实现了单向的数据流,在全局定义了一个 State 对象用来存储数据,当组件要修改 State 中的数据时,必须通过 Mutation 进行操作。

vuex 实现了单向的数据流

我们首先在全局定义了 count.js 模块用来存放数据和修改数据的方法,然后在全局引入。

模块用来存放数据和修改数据

运行结果如下:

运行结果如下

我们就可以在任何组件中来调用 mutations 和 actions 中的方法操作数据了。vuex 在数据传值和操作数据维护起来比较方便,但是有一定的学习成本。

$refs

有时候我们需要在 vue 中直接来操作 DOM 元素,比如获取 DIV 的高度,或者直接调用子组件的一些函数;虽然原生的 JS 也能获取到,但是 vue 为我们提供了更方便的一个属性:$refs。如果在普通的 DOM 元素上使用,获取到的就是 DOM 元素;如果用在子组件上,获取的就是组件的实例对象。

vue 中直接来操作 DOM 元素

我们首先创建一个简单的子组件,有两个函数用来增减 num 的值。

两个函数用来增减 num 的值

我们给子组件增加一个 ref 属性 child,然后通过$refs.child 来获取子组件的实例,通过实例来调用子组件中的函数。

ref 属性 child

可以看到我们获取到的是一个 VueComponent 对象,这个对象包括了子组件的所有数据和函数,可以对子组件进行一些操作。

$parent 和$children

如果页面有多个相同的子组件需要操作的话,$refs 一个一个操作起来比较繁琐,vue 提供了另外的属性:$parent 和$children 来统一选择。

$parent 和$children我们在父组件中插入了两个相同的子组件,在子组件中通过$parent 调用了父组件的函数,并在父组件通过$children 获取子组件实例的数组。

$parent 调用了父组件的函数

我们在 Parent 中打印出$parent 属性看到是最外层#app 的实例。

因此我们把常见使用场景分为以下三类:

  • 父子组件通信: props; $parent/$children; provide/inject; $ref; $attrs/$listeners
  • 兄弟组件通信: EventBus; Vuex
  • 跨级通信: EventBus; Vuex; provide/inject; $attrs/$listeners

结语

以上就是码云笔记整理的关于 VUE 组件通信方式的一些见解,希望对大家有帮助,如果大家有更好的见解,欢迎留言讨论。

「点点赞赏,手留余香」

0

给作者打赏,鼓励TA抓紧创作!

微信微信 支付宝支付宝

还没有人赞赏,快来当第一个赞赏的人吧!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。
码云笔记 » 详解Vue中组件通信的方式

发表回复