乐趣区

关于前端:Vue2-代码转换为-Vue3-原理剖析-eventHub篇

前言

几天前咱们公布了《阿里妈妈又做了新工具,帮你把 Vue2 代码改成 Vue3 的》,这篇文章分享一下其中一个转换规则:eventHub(或称为 eventBus)的转换的思路。

Vue 官网的迁徙计划:https://v3.cn.vuejs.org/guide/migration/events-api.html

1. 先介绍下 eventHub

1.1 Vue2 的 eventHub

eventHub 是组件间共用的事件核心,在 Vue 中用来作为组件沟通的桥梁,向 eventHub 发送音讯,其它模块通过订阅这个 eventHub 来获取相应的数据。

官网介绍:

Vue 实例可用于触发由事件触发 API 通过指令式形式增加的处理函数 ($on,$off 和 $once)。
event hub,用来创立在整个应用程序中可用的全局事件监听器的实现:

// eventHub.js
const eventHub = new Vue()
export default eventHub
// ChildComponent.vue
import eventHub from './eventHub'

export default {mounted() {
    // 增加 eventHub 监听器
    eventHub.$on('custom-event', () => {console.log('Custom event triggered!')
    })
  },
  beforeDestroy() {
    // 移除 eventHub 监听器
    eventHub.$off('custom-event')
  }
}

1.2 Vue3 中的 eventHub

然而在 Vue 3 中,官网移除 $on、$off 和 $once 办法

咱们从实例中齐全移除了 $on、$off 和 $once 办法。$emit 依然蕴含于现有的 API 中,因为它用于触发由父组件申明式增加的事件处理函数。
在 Vue 3 中,曾经不可能应用这些 API 从组件外部监听组件本人收回的事件了,该用例暂没有迁徙的办法。

2. 迁徙计划

2.1 eventHub 三方库的抉择

Vue 官网提供了代替的计划:应用第三方库,“eventHub 模式能够被替换为实现了事件触发器接口的内部库,例如 mitt 或 tiny-emitter”。钻研了 mitt 和 tiny-emitter,mitt 尽管 star 数更高,然而它不反对 $once 办法,须要应用其它办法组合实现。为了升高迁徙老本,我抉择应用 tiny-emitter。

A tiny (less than 1k) event emitter library.

2.2 迁徙工具的抉择

面对 N 多个我的项目,百八十个文件,各种 eventHub 的实现形式,手工替换必定不是个可实现的解决方案。
更优计划是基于 AST(形象语法树)解构代码,依据既定规定,批量批改而后输入文件。

在此举荐 GoGoCode 这个解决 AST 强力好动手的工具

GoGoCode 提供了相似 JQuery 的 API,用以操作 AST 对象。升高了应用 AST 的门槛,帮忙开发者从繁琐的 AST 操作中解放出来

正是代码迁徙的最无力助手。

另外安利一个做 AST 剖析离不开的工具:https://astexplorer.net,应用它咱们能够很不便的查看某段代码的 AST 语法树结构

3. 代码迁徙

3.1 写一个待转换的 Demo

<template>
  <div>
    A:{{num}}
  </div>
</template>
<script>
import ehb from './EventHub';
export default {
  name: 'B',
  mounted() {
    // 增加 eventHub 监听器
    ehb.$on('inc', () => {this.num += 1;});
    // 增加 eventHub 监听器
    ehb.$once('inc', () => {this.num * 100;});
  },
  beforeUnmount() {ehb.$off('inc');
  },
};
</script>

3.2 须要做的事件

  1. 定位 script 标签内的 $on $off $once $emit 办法,提取他们的对象名
  2. 对象名,找到申明它的代码段
  3. 用 tiny_emitter 对象笼罩掉之前 eventHub 提供的几个废除办法
  4. 加 tiny_emitter 援用
  5. 文件输入

转换成果如下:

3.3 动工

装置 GoGoCode

npm install gogocode

初始化 Vue 文件的 AST 对象

// 读取文件内容为 ast 对象
let ast = $(` 待转换 vue 文件的内容 `,{ parseOptions: { language: 'vue'} })

// 定位 ast 对象的 script 节点
let scriptAST = ast.find('<script></script>') 

转换逻辑:实现“3.2 须要做的事件”

// 1. 遍历 script 内容 $on、$off、$once 和 $emit
scriptAST.find([`$_$1.$on($_$2)`, `$_$1.$off($_$2)`, `$_$1.$once($_$2)`, `$_$1.$emit($_$2)`]).each(hubAST => {// 1.1 应用 hubAST.attr('callee.object.name')API, 提取 ehb.$xxx(...) 的对象名
    if (hubAST.attr('callee.object.name')) {
        // 2. 找到 eventHub 申明地位
        let definitions = scriptAST.find(`import ${hubAST.attr('callee.object.name')} from '$_$'`)
        const eventHub = `Object.assign(${ hubAST.attr('callee.object.name') } ,{$on: (...args) => tiny_emitter.on(...args),
          $once: (...args) => tiny_emitter.once(...args),
          $off: (...args) => tiny_emitter.off(...args),
          $emit: (...args) => tiny_emitter.emit(...args),
          });
          `
        // 3. 遍历申明地位, 用 after API 在申明之后笼罩之前的 eventHub 对象
        definitions.each(def => {if (!scriptAST.has(eventHub)) {def.after(eventHub)
            }
        })
    }
    // 4. 增加 tiny_emitter 的援用
    if (!scriptAST.has(`import tiny_emitter from 'tiny-emitter/instance'`)) {scriptAST.prepend(`import tiny_emitter from 'tiny-emitter/instance';\n`)
    }
})

最初输入 AST 对象为字符串:

return ast.generate()

具体实现代码:playground

4. 最初总结下

这些代码用到 GoGoCode 实现的 AST 操作:

  1. &dollar;.find API 用来定位 $on $off $once $emit,输入 AST 对象
  2. xxx.attr 用来提取 AST 对象的属性,例如这段代码里获取 ehb.$on(......) 对象名 ehb
  3. 应用 has API 来判断 AST 中是否曾经存在某个节点
  4. xxx.after 向相应 AST 后增加一个 AST 对象
  5. xxx.prepend 向相应 AST 前增加一个 AST 对象

都是很相熟的操作有没有!

这里分享了一些 eventHub 转换实现的思路和 GoGoCode 的用法,心愿可能失去大家的意见建议,如果发现了代码的问题,欢送给咱们提 issue https://github.com/thx/gogocode/issues

感谢您的浏览,祝你有美妙的一天!

退出移动版