Vue 传参踩坑之旅——事件总线与 props

Vue 传参踩坑之旅------事件总线与 props

缘由

今天突然发现项目出现了一个 bug,这里简单描述一下。

这里有 A、B、C、D 四个组件,关系为 A - 祖先、B - 父、C - 子、D - 叔(实际业务组件关系复杂很多)。

  • A - 祖先

    • B - 父

      • C - 子
    • D - 叔

现在 C 组件要用到 D 组件中的一个方法,由于组件关系比较复杂,我为了省事直接使用 mitt 进行自定义事件的触发,但是 D 组件又可能有多个(通过 v-for 生成的),此时就出现了问题,当存在多个 D 组件时,在 C 组件中调用了 D 中的方法会同时触发所有 D 组件的该事件。

有的小伙伴看完就知道问题所在了,没明白的也没关系,下面我会结合代码进行详细说明。

代码详细说明

先说明一下目录结构:

css 复制代码
 - App.vue
 - A.vue
 - B.vue
 - C.vue
 - D.vue
 - mitt.js
xml 复制代码
 <!-- App.vue -->
 <template>
   <A v-for="item of 2" :key="item"></A>
 </template>
 ​
 <script setup>
 import A from "./A.vue";
 </script>
xml 复制代码
 <!-- A.vue -->
 <template>
   <B></B>
   <D></D>
 </template>
 ​
 <script setup>
 import B from "./B.vue";
 import D from "./D.vue";
 </script>
xml 复制代码
 <!-- B.vue -->
 <template>
   <C></C>
 </template>
 ​
 <script setup>
 import C from "./C.vue";
 </script>
xml 复制代码
 <!-- C.vue -->
 <template>
   <button @click="handleDFunction">点击触发D组件方法</button>
 </template>
 ​
 <script setup>
 import mitt from "./mitt";
 ​
 function handleDFunction() {
   mitt.emit("handled");
 }
 </script>
xml 复制代码
 <!-- D.vue -->
 <template></template>
 ​
 <script setup>
 import mitt from "./mitt";
 ​
 function handled() {
   console.log("D组件方法被触发");
 }
 mitt.on("handled", handled);
 </script>
javascript 复制代码
 import mitt from 'mitt'
 ​
 export default new mitt()

以上就是精简后的示例代码,可以简单理解为兄弟组件传参。

效果展示

此时页面会显示两个 <button> 按钮。

当随便点击了一个后会输出两次 "D组件方法被触发"。

我们想要的效果是只会触发当前兄弟组件的事件,而此时触发了所有 D 组件的该事件。

问题分析

这个 bug 的原因其实也很简单,这是由于 mitt.on 注册同名事件会在数组后面追加,而非覆盖或者其他思路,简单通过代码说明一下它的源码实现:

scss 复制代码
 function mitt() {
   const eventMap = new Map(); // 事件表
   return {
     // 事件注册
     on(key, callback) {
       // 获取当前名称的事件列表
       const curEventList = eventMap.get(key)
       
       // 不是第一次注册该名称的事件,追加到数组最后
       if (curEventList) {
         curEventList.push(callback)
       }
       // 第一次注册该名称的事件,事件表中添加该事件列表
       else {
         eventMap.set(key, [callback])
       }
     },
     // 事件派发
     emit(key, ...args) {
       // 获取当前名称的事件列表
       const curEventList = eventList.get(key)
       
       // 存在该事件,则遍历数组依次执行
       if (curEventList) {
         curEventList.forEach((curEvent) => {
           curEvent(args)
         })
       }
     },
   }
 }

现在就能知道为什么会出现这种问题了,因为每一个 D 组件都对该事件名称进行了注册,因此实际上该事件名称注册了 n 个该事件,所以当 emit 派发时就会依次执行。

解决的化只需要将 emit 替换为 props / emits 就解决了。

总结

这个 bug 其实给了我蛮多启发的,也能理解之前看到别人说业务组件中尽量使用 props / emits 了,事件总线的原理我其实是知道的,但是还是会产生这种 bug,因为随着业务的发展,新功能的产生以及老功能的优化等都可能对原来的数据结构或组件结构进行更改,在我们写业务代码时是比较难精准的预料到未来的变化的,此时就需要我们的代码有足够高的容错性,诸如 provide / inject、事件总线等组件通信方式固然很方便,但是在代码重构或其他一些比较重大改动时项目的数据流又会变得难以预测。

props / emits 的缺点:

  • 代码量会变得更多、冗余

props / emits 的优点:

  • 组件数据流变得清晰
  • 当项目结构发生变化时重构会更简单
  • 不容易产生奇怪的问题
相关推荐
八荒启·交互动画34 分钟前
Web特效06——WebGL深挖:顶点着色器和片元着色器到底在干什么
前端·网页特效·八荒启-交互动画·八荒启
这锅我不背哈1 小时前
前端权限控制实践:基于 RBAC 的完整落地方案
前端
星空1 小时前
Map<String, String>`Map`是接口,不能直接 new
java·前端·算法
开开心心就好1 小时前
低年级识字练笔顺工具,电脑和安卓端都能用
前端·javascript·网络·安全·scala·erlang·语音识别
杨利杰YJlio2 小时前
ITSK PE 26U5 测试版解读:组件完善、VMD 修复与服务器支持边界
前端·javascript·后端
志尊宝2 小时前
Vue3 零基础每日笔记(021):class 与 style 动态绑定——对象、数组、三元表达式写法汇总
前端·vue.js·笔记·前端框架·html5
夏幻灵2 小时前
Vue / React 列表渲染中的 key:为什么没有 key,Checkbox 可能“对号跑错”?
前端·javascript·vue.js
YWL2 小时前
vue-time-axis-plus进阶:双进度条+跨空白续播+播放指针,打造专业级监控回放体验
前端·javascript·vue·时间轴
mmsx2 小时前
MapLibre 实战 09|Bug 单写着"地图被风刮走了":一个瓦片源工厂,是三个线上事故换来的
android·前端·开源
不羁的木木2 小时前
给鸿蒙 App 增加打开外部网页能力 —— flutter_web_browser 的鸿蒙使用指南
前端·flutter·harmonyos