当你点进来,这个问题就有了答案,可以的! 本文将介绍如何实现一个注册全局指令的插件,可以自动判断vue版本,并进行差异消除的处理。
本文示例源码:gitee 、github、npm地址,欢迎start哦。
前置介绍-出发点
最近写了个vue3的自定义指令,用来拖动元素。为了不影响元素的布局,选用了transform:translate()
变换,来实现拖动(感觉比烂大街的修改top和left更合适有木有)。实现并不难,思路大概是:
- 监听元素mousedown事件,点击时记录开始坐标。并监听元素mousemove事件。
- 当元素触发mousemove,实时获取鼠标坐标,并减去第一步记录的开始坐标,得到移动的距离。
- 得到了移动距离,实时更新到translate即可。
- 监听mouseup、mouseleave事件,移除mousemove还有自己的监听,收尾!
指令实现起来挺简单,考虑到目前vue3还没火到完全取代vue2,我想能不能让它在vue2也正常运行呢?
具体实现可跳转github查看哦,这里主要介绍如何实现兼容。
行动
实现一个兼容vue2、vue3的指令
准备:通过官网介绍,对比vue2和vue3实现自定义指令的异同:
- vue2、vue3中的自定义指令都是一个对象,对象里是各种钩子函数,形式上一致。
- 每个钩子接收的参数个数、位置,vue2、vue3几乎没有区别,只是第二个参数(一个对象)里有几个不常用属性的进行了破坏性更新,常用的没变。
- vue3中的钩子更多更精细,且覆盖了vue2的所有钩子,只是命名不一样(vue2的那几个也够用了)。
结论:
完全具有可行性!
只要将定义指令的对象,定义为一个模块,内部实现的逻辑只需要一份,只是导出vue3的指令对象的同时,再适配vue2,把钩子名换成vue2的命名方式,再导出一个对象即可。
基本实现:
-
先实现一下定义指令的对象:
js// lite-move.js // 拖动处理函数 function moveAction(e) { /* e为事件对象,也可以通过this来访问dom元素 */ } // 元素插入dom时的钩子函数 const mounted = (el, binding) => { // moveAction内部要访问dom时,最好用this,而不是钩子提供的el,性能更好,耦合度低 el.addEventListener('mousedown', binding.value = moveAction) } // 父组件销毁时的钩子函数 const unmounted = (el, binding) => { el.removeEventListener('mousedown', binding.value) } // vue3中定义指令的对象 export const vMove = { mounted, unmounted } // vue2中定义指令的对象,改一下属性名即可 export const vMoveFor2 = { inserted: mounted, unbind: unmounted }
-
在vue3中使用:
html<template> <div ref="moveEle" v-move> <span>移动</span> </div> </template> <script setup> import { moveDirective } from 'lite-move'; // 导入符合vue3语法的指令对象 // 注册局部指令 const vMove = moveDirective; </script>
-
在vue2中使用:
html<template> <div v-move>移动</div> </template> <script> import { moveDirectiveFor2 } from 'lite-move'; export default { directives: { // 注册局部指令 move: moveDirectiveFor2 } } </script>
通过插件实现自动判断vue版本
上面这种注册指令的方式还是有点麻烦了,还需要导入对应的对象才行。为了能实现一种写法,可以在两种环境中运行,还需要一个中间过程:vue插件。
为什么时vue插件呢
-
vue3和vue2的插件形式、注册方式都相同。
vue3、vue2都是通过use来注册插件,并且都会调用插件的install方法。
js/* 在vue2中,注册插件的对象是Vue构造函数 在vue3中,注册插件的对象是createApp返回的对象 用伪代码演示: if vue2: import app from 'vue'; if vue3: import {createApp} from 'vue'; const app = createApp(); */ import { moveDirectivePlugin } from 'move-plugin' app.use(moveDirectivePlugin)
-
(关键)vue3和vue2调用use来注册插件时,传递给install方法的第一个参数,虽然有本质的区别,但有共同的属性:
- 都可以获取第一个参数上的version属性,取得当前vue版本。
- 都可以通过第一个参数上的directive方法,注册全局指令,并且参数一样。
实现
-
插件定义
jsimport { moveDirectiveFor2, moveDirective } from 'lite-move'; export const moveDirectivePlugin = { install(app) { // 如果是vue2,version值为'2';如果是vue3,则为'3' const version = app.version.charAt(0); // 根据vue版本作不同的处理,例如使用不同的指令对象,注册指令 if (version === '2') { // @ts-ignore app.directive('move', moveDirectiveFor2) } if (version === '3') { app.directive('move', moveDirective) } } }
-
注册插件
js/* 这里用伪代码,代不同环境 if vue2: import app from 'vue'; if vue3: import {createApp} from 'vue'; const app = createApp(); */ import { moveDirectivePlugin } from 'move-plugin'; app.use(moveDirectivePlugin);
-
最后就可以在全局访问上面注册的
v-move
指令了,如果想测试一下,可安装以下依赖来玩一下:安装:
shellnpm i lite-move -S
注册:
js/* 这里用伪代码,代不同环境 if vue2: import app from 'vue'; if vue3: import {createApp} from 'vue'; const app = createApp(); */ import { moveDirectivePlugin } from 'lite-move'; app.use(moveDirectivePlugin);
使用:
html<template> <div v-move>可用鼠标拖动</div> </template>
结论
vue3虽然对vue2进行了很多更新,包括很多破坏性更新,但有不少功能的设计还是有vue2的影子。正因为这点,让设计一个兼容vue2的vue3插件具备可行性,一些个人实践心得:
-
由于vue2的影响力,vue3也将会是趋势,即使目前很多公司还没用起来,自己在社区多捣鼓一下是有必要的。
-
写一些兼容二者的插件、指令,可进可退。
-
实现一个兼容性好的插件,必须考虑到耦合性。
例如上面的情景中:在监听拖动的事件函数内部,它可以获取外部作用域的el来获取dom,也可以使用自身this来获取,通过自身this来获取耦合性明显更低。
-
实现思路主要是,先考虑该功能依赖了vue的哪些api,这些api在vue2和vue3中有哪些不同。优先使用一样的api,其次是只是名字不同,但功能一样的api。最后,如果实在不同,就需要考虑可不可以换成其它方式来消除差异。
-
可以通过注册vue插件方式来隐藏版本之间差异,如判断版本,进行不同的处理,保证使用上的一致性。
-
虽然答案是可以,但还是有一定局限性的,像注册全局指令、在vue实例上挂载公共方法等,通过注册插件来实现还是很简单的。但如果是注册组件,限制就非常多了。