Vue.js组件精讲 第4章 组件的通信2:派发与广播——自行实现dispatch和broadcast方法

上一讲的 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。然后有两种场景它不能很好的解决:

  • 父组件向子组件(支持跨级)传递数据;
  • 子组件向父组件(支持跨级)传递数据。

这种父子(含跨级)传递数据的通信方式,Vue.js 并没有提供原生的 API 来支持,而是推荐使用大型数据状态管理工具 Vuex,而我们之前已经介绍过 Vuex 的场景与在独立组件(或库)中使用的限制。本小节则介绍一种在父子组件间通信的方法 dispatch 和 broadcast。

$on 与 $emit

如果您使用过较早的 Vue.js 1.x 版本,肯定对 $dispatch 和 $broadcast 这两个内置的方法很熟悉,不过它们都在 Vue.js 2.x 里废弃了。在正式介绍主角前,我们先看看 $on 与 $emit 这两个 API,因为它们是本节内容的基础。

$emit 会在当前组件实例上触发自定义事件,并传递一些参数给监听器的回调,一般来说,都是在父级调用这个组件时,使用 @on 的方式来监听自定义事件的,比如在子组件中触发事件:

// child.vue,部分代码省略
export default {methods: {handleEmitEvent () {this.$emit('test', 'Hello Vue.js');}}
}

在父组件中监听由 child.vue 触发的自定义事件 test:

<!-- parent.vue,部分代码省略-->
<template><child-component @test="handleEvent">
</template>
<script>export default {methods: {handleEvent (text) {console.log(text);  // Hello Vue.js}}}
</script>

这里看似是在父组件 parent.vue 中绑定的自定义事件 test 的处理句柄,然而事件 test 并不是在父组件上触发的,而是在子组件 child.vue 里触发的,只是通过 v-on 在父组件中监听。既然是子组件自己触发的,那它自己也可以监听到,这就要使用 $on 来监听实例上的事件,换言之,组件使用 $emit 在自己实例上触发事件,并用 $on 监听它。

听起来这种神(sāo)操作有点多此一举,我们不妨先来看个示例:

<template><div><button @click="handleEmitEvent">触发自定义事件</button></div>
</template>
<script>export default {methods: {handleEmitEvent () {// 在当前组件上触发自定义事件 test,并传值this.$emit('test', 'Hello Vue.js')}},mounted () {// 监听自定义事件 testthis.$on('test', (text) => {window.alert(text);});}}
</script>

$on 监听了自己触发的自定义事件 test,因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。

仅上面的示例,的确是多此一举的,因为大可在 handleEmitEvent 里直接写 window.alert(text),没必要绕一圈。

之所以多此一举,是因为 handleEmitEvent 是当前组件内的 调用的,如果这个方法不是它自己调用,而是其它组件调用的,那这个用法就大有可为了。

了解了 $on 和 $emit 的用法后,我们再来看两个“过时的” API。

Vue.js 1.x 的 $dispatch 与 $broadcast

虽然 Vue.js 1.x 已经成为过去时,但为了充分理解本节通信方法的使用场景,还是有必要来了解一点它的历史。

在 Vue.js 1.x 中,提供了两个方法:$dispatch 和 $broadcast ,前者用于向上级派发事件,只要是它的父级(一级或多级以上),都可以在组件内通过 $on (或 events,2.x 已废弃)监听到,后者相反,是由上级向下级广播事件的。

来看一个简单的示例:

<!-- 注意:该示例为 Vue.js 1.x 版本 -->
<!-- 子组件 -->
<template><button @click="handleDispatch">派发事件</button>
</template>
<script>
export default {methods: {handleDispatch () {this.$dispatch('test', 'Hello, Vue.js');}}
}
</script>
<!-- 父组件,部分代码省略 -->
<template><child-component></child-component>
</template>
<script>export default {mounted () {this.$on('test', (text) => {console.log(text);  // Hello, Vue.js});}}
</script>

$broadcast 类似,只不过方向相反。这两种方法一旦发出事件后,任何组件都是可以接收到的,就近原则,而且会在第一次接收到后停止冒泡,除非返回 true。

这两个方法虽然看起来很好用,但是在 Vue.js 2.x 中都废弃了,官方给出的解释是:

因为基于组件树结构的事件流方式有时让人难以理解,并且在组件结构扩展的过程中会变得越来越脆弱。

虽然在业务开发中,它没有 Vuex 这样专门管理状态的插件清晰好用,但对独立组件(库)的开发,绝对是福音。因为独立组件一般层级并不会很复杂,并且剥离了业务,不会变的难以维护。

知道了 $dispatch 和 $broadcast 的前世今生,接下来我们就在 Vue.js 2.x 中自行实现这两个方法。

自行实现 dispatch 和 broadcast 方法

自行实现的 dispatch 和 broadcast 方法,不能保证跟 Vue.js 1.x 的 $dispatch 和 $broadcast 具有完全相同的体验,但基本功能是一样的,都是解决父子组件(含跨级)间的通信问题。

通过目前已知的信息,我们要实现的 dispatch 和 broadcast 方法,将具有以下功能:

  • 在子组件调用 dispatch 方法,向上级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该上级组件已预先通过 $on监听了这个事件;
  • 相反,在父组件调用 broadcast 方法,向下级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该下级组件已预先通过 $on监听了这个事件。

实现这对方法的关键点在于,如何正确地向上或向下找到对应的组件实例,并在它上面触发方法。在设计一个新功能(features)时,可以先确定这个功能的 API 是什么,也就是说方法名、参数、使用样例,确定好 API,再来写具体的代码。

因为 Vue.js 内置的方法,才是以 $ 开头的,比如 $nextTick$emit 等,为了避免不必要的冲突并遵循规范,这里的 dispatch 和 broadcast 方法名前不加 $。并且该方法可能在很多组件中都会使用,复用起见,我们封装在混合(mixins)里。那它的使用样例可能是这样的:

// 部分代码省略
import Emitter from '../mixins/emitter.js'export default {mixins: [ Emitter ],methods: {handleDispatch () {this.dispatch();  // ①},handleBroadcast () {this.broadcast();  // ②}}
}

上例中行 ① 和行 ② 的两个方法就是在导入的混合 emitter.js 中定义的,这个稍后我们再讲,先来分析这两个方法应该传入什么参数。一般来说,为了跟 Vue.js 1.x 的方法一致,第一个参数应当是自定义事件名,比如 “test”,第二个参数是传递的数据,比如 “Hello, Vue.js”,但在这里,有什么问题呢?只通过这两个参数,我们没办法知道要在哪个组件上触发事件,因为自行实现的这对方法,与 Vue.js 1.x 的原生方法机理上是有区别的。上文说到,实现这对方法的关键点在于准确地找到组件实例。那在寻找组件实例上,我们的“惯用伎俩”就是通过遍历来匹配组件的 name 选项,在独立组件(库)里,每个组件的 name 值应当是唯一的,name 主要用于递归组件,在后面小节会单独介绍。

先来看下 emitter.js 的代码:

function broadcast(componentName, eventName, params) {this.$children.forEach(child => {const name = child.$options.name;if (name === componentName) {child.$emit.apply(child, [eventName].concat(params));} else {broadcast.apply(child, [componentName, eventName].concat([params]));}});
}
export default {methods: {dispatch(componentName, eventName, params) {let parent = this.$parent || this.$root;let name = parent.$options.name;while (parent && (!name || name !== componentName)) {parent = parent.$parent;if (parent) {name = parent.$options.name;}}if (parent) {parent.$emit.apply(parent, [eventName].concat(params));}},broadcast(componentName, eventName, params) {broadcast.call(this, componentName, eventName, params);}}
};

因为是用作 mixins 导入,所以在 methods 里定义的 dispatch 和 broadcast 方法会被混合到组件里,自然就可以用 this.dispatch 和 this.broadcast 来使用。

这两个方法都接收了三个参数,第一个是组件的 name 值,用于向上或向下递归遍历来寻找对应的组件,第二个和第三个就是上文分析的自定义事件名称和要传递的数据。

可以看到,在 dispatch 里,通过 while 语句,不断向上遍历更新当前组件(即上下文为当前调用该方法的组件)的父组件实例(变量 parent 即为父组件实例),直到匹配到定义的 componentName 与某个上级组件的 name 选项一致时,结束循环,并在找到的组件实例上,调用 $emit 方法来触发自定义事件 eventName。broadcast 方法与之类似,只不过是向下遍历寻找。

来看一下具体的使用方法。有 A.vue 和 B.vue 两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:

<!-- A.vue -->
<template><button @click="handleClick">触发事件</button>
</template>
<script>import Emitter from '../mixins/emitter.js';export default {name: 'componentA',mixins: [ Emitter ],methods: {handleClick () {this.broadcast('componentB', 'on-message', 'Hello Vue.js');}}}
</script>
// B.vue
export default {name: 'componentB',created () {this.$on('on-message', this.showMessage);},methods: {showMessage (text) {window.alert(text);}}
}

同理,如果是 B 向 A 通信,在 B 中调用 dispatch 方法,在 A 中使用 $on 监听事件即可。

以上就是自行实现的 dispatch 和 broadcast 方法,相比 Vue.js 1.x,有以下不同:

  • 需要额外传入组件的 name 作为第一个参数;
  • 无冒泡机制;
  • 第三个参数传递的数据,只能是一个(较多时可以传入一个对象),而 Vue.js 1.x 可以传入多个参数,当然,你对 emitter.js 稍作修改,也能支持传入多个参数,只是一般场景传入一个对象足以。

结语

Vue.js 的组件通信到此还没完全结束,如果你想“趁热打铁”一口气看完,可以先阅读第 6 节组件的通信 3。亦或按顺序看下一节的实战,来进一步加深理解 provide / inject 和 dispatch / broadcast 这两对通信方法的使用场景。

注:本节部分代码参考 iView。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/812930.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Hello算法】 > 第 2 关 >数据结构 之 数组与链表

数据结构 之 数组与链表 1&#xff1a;Understanding data structures &#xff01;——了解数据结构——1.1&#xff1a;Classification-分类-1.2&#xff1a;Type-类型- 2&#xff1a;Arrays are the bricks that make up the wall of data structures *——数组是组成数据结…

django基于python的法院执法案件管理系统

本课题使用Python语言进行开发。代码层面的操作主要在PyCharm中进行&#xff0c;将系统所使用到的表以及数据存储到MySQL数据库中&#xff0c;方便对数据进行操作本课题基于WEB的开发平台&#xff0c;设计的基本思路是&#xff1a; 框架&#xff1a;django/flask 后端&#xff…

算法题解记录10+++缺失的第一个正数

题目描述&#xff1a; 给你一个未排序的整数数组 nums &#xff0c;请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,0] 输出&#xff1a;3 解释&#xff1a;范围 […

Spring与Spring Boot的区别:从框架设计到应用开发

这是我自己开发的一款小程序&#xff0c;感兴趣的可以体验一下&#xff1a; 进入正题&#xff1a; 在Java开发领域&#xff0c;Spring和Spring Boot都是备受推崇的框架&#xff0c;它们为开发人员提供了丰富的功能和便捷的开发体验。然而&#xff0c;许多人对它们之间的区别仍…

MySQL的基础操作(二)

目录 一.数据库约束 1.主键约束 (Primary Key) 2.唯一约束 (Unique) 3.外键约束 (Foreign Key): 4.检查约束(Check) 5.默认约束 (Default) 二.聚合查询 1.简单聚合函数 2.GROUP BY子句 3.HAVING子句 三.联合查询 1.内连接 2.左连接 3.右连接 4.子查询 5.合并查询…

大数据实训进行时:数据标注项目

数据标注项目 培训目的 让同学们先熟悉理论知识&#xff0c;如&#xff1a;识别障碍物是否满足拉框的要求&#xff0c;如何进行拉框&#xff1b;熟悉标注操作&#xff0c;培养出能够进入正式项目的人员 培训地点 理论&#xff1a;学术报告厅、阶梯教室 实操&#xff1a;1实…

【WPF应用42】WPF中的 GroupBox 控件详解

在 Windows Presentation Foundation (WPF) 中&#xff0c;控件是构建用户界面 (UI) 的基础。WPF 提供了丰富的控件库&#xff0c;其中包括 GroupBox 控件&#xff0c;它用于将相关的 UI 元素组织到逻辑分组中。在本博客文章中&#xff0c;我们将详细介绍 GroupBox 控件的功能、…

自己整理的ICT云计算题库四

14. 【多选题】 CIFS 支持的认证方式是以下哪些选项&#xff1f; A: A 全局认证 B: B LADP 域 C: C 本地认证 D: D AD 域 答案 正确答案&#xff1a;ACD 解释 全局认证为先本地&#xff0c;后AD&#xff0c;再LADP 15. 【单选题】 华为 oceanstor v3 smarterase 在使用时…

基于linux进一步理解核间通讯

芯片架构分为同构和异构: 如下图TC397: 如下图TDA4: 如下图STM32MP157: 非对称多处理结构(AMP): AMP 结构是指每个内核运行自己的 OS 或同一 OS 的独立实例&#

MTK Android13 霸屏实现

一、背景 在台式POS场景下&#xff0c;经常有应用会需要获取霸屏的权限&#xff0c;隐藏状态栏或者导航栏&#xff0c;且不能被划出&#xff0c;其实系统已经系统了隐藏状态栏也导航栏的接口&#xff0c;但是无法做到禁止滑出。 View decorView ((Activity) context).getWin…

Day53 动态规划 part14

Day53 动态规划 part14 1143.最长公共子序列 我的思路&#xff1a; 模仿昨天的最大重复子序列长度的思路&#xff0c;可以列出如下状态转移方程 对着状态转移方程写代码即可&#xff0c;还是需要注意&#xff0c;i, j是从1开始的&#xff0c;比较的时候是str1[i -1]和str2[j…

【PG-1】PostgreSQL体系结构概述

1. PostgreSQL体系结构概述 代码结构 其中&#xff0c;backend是后端核心代码&#xff0c;包括右边的几个dir: access&#xff1a;处理数据访问方法和索引的代码。 bootstrap&#xff1a;数据库初始化相关的代码。 catalog&#xff1a;系统目录&#xff08;如表和索引的元数据…

数据结构4:基于单链表的通讯录项目

文章目录 头文件SList.hContact.h 实现文件SList.cContact.c 测试代码 头文件 SList.h #pragma once#include<stdio.h> #include<stdlib.h> #include<assert.h> #include"Contact.h"//typedef int SLDataType; typedef PersonInfo SLDataType;//…

2024年第十四届MathorCup数学应用挑战赛B题解题思路

B题https://mbd.pub/o/bread/ZZ6Wm5dx 问题1:对于附件I(Pre_test文件夹)给定的三张甲骨文原始拓片图 片进行图像预处理&#xff0c;提取图像特征&#xff0c;建立甲骨文图像预处理模型&#xff0c;实现对 甲骨文图像干扰元素的初步判别和处理。 针对问题1&#xff0c;对于附件…

【研发效能·创享大会-嗨享技术轰趴】-IDCF五周年专场

一、这是一场创新分享局&#xff01; 来吧&#xff0c;朋友们! 参加一场包含AIGC、BizDevOps、ToB产品管理、B端产品运营、平台工程、研发效能、研发度量、职业画布、DevOps国标解读的研发效能创享大会&#xff0c;会有哪些收益呢&#xff1f; 知识更新与技能提升&#xff1a;…

在线批量生成URL HTML单页网页程序

输入前缀、开始数字、结束数字、后缀 即可快速生成 几万、十万、百万 条链接。 支持 一键复制、 一键导出本地 txt 文件。 源码免费下载地址抄笔记 (chaobiji.cn)

Conda 常用命令总结

创建虚拟环境 conda create -n name python[your_version] 激活环境 conda activate name 退出环境 conda deactivate 查看虚拟环境 conda info --envs 删除虚拟环境 conda remove -n name --all 删除所有的安装包及cache(索引缓存、锁定文件、未使用过的包和tar包) …

java的jar包jakarta.jakartaee-web-api和jakarta.servlet-api有什么区别

jakarta.jakartaee-web-api和jakarta.servlet-api都是Java EE&#xff08;现在是 Jakarta EE&#xff09;中的一部分&#xff0c;用于开发基于Java EE平台的Web应用程序。它们之间的区别在于以下几点&#xff1a; 命名空间&#xff1a; jakarta.servlet-api是Java EE 8之前版本…

linux中常用命令(未完待续)

linux中常用命令&#xff08;未完待续&#xff09; 复制/移动文件夹&#xff1a;在Linux中移动/复制文件夹到另一个目录中 # *********************删除文件夹↓********************************* # 要删除一个文件夹&#xff0c;最常见的方法是使用rm命令。 # PS&#xff1…

CSS3 常用样式

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 ✍CSS3 常用样式&#x1f48e;1 CSS3 新增选择器&#x1f339;1.1 属性选择器…