技术分享 | 如何编写同时兼容 Vue2 和 Vue3 的代码?
LigaAI 的评论编辑器、附件展示以及富文本编辑器都支持在 Vue2(Web)与 Vue3(VSCode、lDEA)中使用。这样不仅可以在不同 Vue 版本的工程中间共享代码,还能为后续升级 Vue3 减少一定阻碍。
那么,同时兼容 Vue2 与 Vue3 的代码该如何实现?业务实践中又有哪些代码精简和优化的小技巧?让我们先从兼容代码的工程化讲起。
1. 工程化:编写同时兼容 Vue2 与 Vue3 的代码
原理上,兼容工作由两部分完成:
- 编译阶段:负责根据使用的项目环境,自动选择使用 Vue2 或 Vue3 的 API。使用时,只需要从
Vue-Demi
里面 import 需要使用的 API,就会自动根据环境进行切换;可以分为在浏览器中运行(IIFE)和使用打包工具(cjs、umd、esm)两种情况。 - 运行阶段:转换
createElement
函数的参数,使 Vue2 与 Vue3 的参数格式一致。Vue2 和 Vue3 Composition API 的区别非常小,运行时 API 最大的区别在于createElement
函数的参数格式不一致,Vue3 换成了 React JSX 格式。
1.1 编译阶段 ——IIFE
在 window
中定义一个 VueDemi
变量,然后检查 window
中的 Vue 变量的版本,根据版本 reexport 对应的 API。
var VueDemi = (function (VueDemi, Vue, VueCompositionAPI) { // Vue 2.7 有不同,这里只列出 2.0 ~ 2.6 的版本if (Vue.version.slice(0, 2) === '2.') {for (var key in VueCompositionAPI) {VueDemi[key] = VueCompositionAPI[key] } VueDemi.isVue2 = true} else if (Vue.version.slice(0, 2) === '3.') {for (var key in Vue) {VueDemi[key] = Vue[key]} VueDemi.isVue3 = true} return VueDemi})(this.VueDemi,this.Vue,this.VueCompositionAPI)
1.2 编译阶段 —— 打包工具
利用 npm postinstall
的 hook,检查本地的 Vue 版本,然后根据版本 reexport 对应的 API。
const Vue = loadModule('vue') // 这里是检查本地的 vue 版本if (Vue.version.startsWith('2.')) {switchVersion(2)}else if (Vue.version.startsWith('3.')) {switchVersion(3)}function switchVersion(version, vue) {copy('index.cjs', version, vue)copy('index.mjs', version, vue)}// VueDemi 自己的 lib 目录下有 v2 v3 v2.7 三个文件夹,分别对应不同的 Vue 版本,Copy 函数的功能就是把需要的版本复制到 lib 目录下// 然后在 package.json 里面指向 lib/index.cjs 和 lib/index.mjsfunction copy(name, version, vue) {const src = path.join(dir, `v${version}`, name)const dest = path.join(dir, name)fs.write(dest, fs.read(src))}
1.3 运行阶段 createElement 函数的区别
1.3.1 Vue 2
- attrs 需要写在
attrs
属性中; on: { click=> {}}
- scopedSlots 写在
scopedSlots
属性中。
h(LayoutComponent, {staticClass: 'button',class: { 'is-outlined': isOutlined },staticStyle: { color: '#34495E' },style: { backgroundColor: buttonColor },attrs: { id: 'submit' },domProps: { innerHTML: '' },on: { click: submitForm },key: 'submit-button',// 这里只考虑 scopedSlots 的情况了// 之前的 slots 没必要考虑,全部用 scopedSlots 是一样的scopedSlots: { header: () => h('div', this.header),content: () => h('div', this.content),},});
1.3.2 Vue 3
attrs
和props
一样,只需写在最外层;onClick: ()=> {}
- slot 写在
createElement
函数的第三个参数中。
class: ['button', { 'is-outlined': isOutlined }],style: [{ color: '#34495E' }, { backgroundColor: buttonColor }],id: 'submit',innerHTML: '',onClick: submitForm,key: 'submit-button',}, {header: () => h('div', this.header),content: () => h('div', this.content),});
1.4 完整代码
import { h as hDemi, isVue2 } from 'vue-demi';// 我们使用的时候使用的 Vue2 的写法,但是 props 还是写在最外层,为了 ts 的智能提示export const h = (type: String | Record<any, any>,options: Options & any = {},children?: any,) => {if (isVue2) {const propOut = omit(options, ['props',// ... 省略了其他 Vue 2 的默认属性如 attrs、on、domProps、class、style]);// 这里提取出了组件的 propsconst props = defaults(propOut, options.props || {}); if ((type as Record<string, any>).props) {// 这里省略了一些过滤 attrs 和 props 的逻辑,不是很重要return hDemi(type, { ...options, props }, children);}return hDemi(type, { ...options, props }, children);}const { props, attrs, domProps, on, scopedSlots, ...extraOptions } = options;const ons = adaptOnsV3(on); // 处理事件const params = { ...extraOptions, ...props, ...attrs, ...domProps, ...ons }; // 排除 scopedSlotsconst slots = adaptScopedSlotsV3(scopedSlots); // 处理 slotsif (slots && Object.keys(slots).length) {return hDemi(type, params, {default: slots?.default || children,...slots,});}return hDemi(type, params, children);};const adaptOnsV3 = (ons: Object) => {if (!ons) return null;return Object.entries(ons).reduce((ret, [key, handler]) => {// 修饰符的转换if (key[0] === '!') {key = key.slice(1) + 'Capture';} else if (key[0] === '&') {key = key.slice(1) + 'Passive';} else if (key[0] === '~') {key = key.slice(1) + 'Once';}key = key.charAt(0).toUpperCase() + key.slice(1);key = `on${key}`;return { ...ret, [key]: handler };}, {});};const adaptScopedSlotsV3 = (scopedSlots: any) => {if (!scopedSlots) return null;return Object.entries(scopedSlots).reduce((ret, [key, slot]) => {if (isFunction(slot)) {return { ...ret, [key]: slot };}return ret;}, {} as Record<string, Function>);};
2. 编码技巧:利用代数数据类型精简代码
这里跟大家分享我自己总结的用于优化代码的理论工具。温馨提示,可能和书本上的原有概念有些不同。
于我而言,衡量一段代码复杂度的方法是看状态数量。状态越少,逻辑、代码就越简单;状态数量越多,逻辑、代码越复杂,越容易出错。因此,我认为「好代码」的特征之一就是,在完成业务需求的前提下,尽量减少状态的数量(即大小)。
那么,什么是状态?在 Vue 的场景下,可以这么理解:
- data 里面的变量就是状态,props、计算属性都不是状态。
- Composition API 中
ref
和reactive
是状态,而 computed 不是状态。
2.1 什么是「状态」?
状态是可以由系统内部行为更改的数据,而状态大小是状态所有可能的值的集合的大小,记作 size(State)
。而代码复杂度 = States.reduce((acc, cur) => acc * size(cur),1)
。
2.1.1 常见数据类型的状态大小
一些常见的数据类型,比如 unit
的状态大小是 1,在前端里可以是 null、undefined;所有的常量、非状态的大小也是 1。而 Boolean
的状态大小是 2。
Number
和 String
一类有多个或无限个值的数据类型,在计算状态大小时需明确一点,我们只关心状态在业务逻辑中的意义,而不是其具体值,因此区分会影响业务逻辑的状态值即可。
例如,一个接口返回的数据是一个数字,但我们只关心这个数字是正数还是负数,那么这个数字的状态大小就是 2。
2.1.2 复合类型的状态大小
复合类型分为和类型与积类型两种。
和类型状态大小的计算公式为 size(C) = size(A) + size(B)
,而积类型状态大小的计算公式为 size(C) = size(A) * size(B)
。
了解完代码优化标准后,我们通过一个案例说明如何利用代数数据类型,精简代码。
2.2 案例:评论编辑器的显示控制
在 LigaAI 中,每个评论都有两个编辑器,一个用来编辑评论,一个用来回复评论;且同一时间最多只允许存在一个活动的编辑器。
2.2.1 优化前的做法
为回复组件定义两个布尔变量 IsShowReply
和 IsShowEdit
,通过 v-if
控制是否显示编辑器。点击「回复」按钮时,逻辑如下:
(1) 判断自己的 IsShowReply
是否为 true,如果是,直接返回; (2) 判断自己的 IsshowEdit
,如果为 true 则修改为 false,关闭编辑评论; (3) 依次设置所有其他评论组件的 IsShowReply
和 IsShowEdit
为 false; (4) 修改自己的 IsShowReply
为 true。
当有 10 个评论组件时,代码复杂度是多少?
size(CommentComponent) = size(Boolean) * size(Boolean) = 2 * 2 = 4size(total) = size(CommentComponent) ^ count(CommentComponent) = 4 ^ 10 = 1048576
尽管逻辑上互斥,但这些组件在代码层面毫无关系,可以全部设置为 true。如果代码出现问题(包括写错),没处理好互斥,这种情况完全可能出现。处理互斥还涉及查找 dom 和组件,出问题的几率也会大大提高。
2.2.2 优化后的做法
在 store
中定义一个字符串变量 activeCommentEditor
,表示当前活动的评论组件及其类型。
type CommentId = number;type ActiveCommentStatus = `${'Edit' | 'Reply'}${CommentId}` | 'Close'; // TS 的模板字符串类型let activeCommentEditor: ActiveCommentStatus = 'Close';
除 'Close'
外,该变量还由两部分组成。第一部分说明当前是「编辑评论」还是「回复评论」,第二部分说明评论的 id。按钮的回调函数(如点击回复),只需要设置
activeCommentEditor = `Reply${id}`
组件使用时,可以这样
v-if="activeCommentEditor === `Edit${id}`"v-if="activeCommentEditor === `Reply${id}`"
就这么简单,没有判断,没有 dom,没有其他组件。虽然 id 是 number,但于前端而言只是一个常量,所以其大小为 1。那么当有 10 个评论组件时,这段代码的复杂度就是
size(total) = size('Reply''Edit') * count(Comment) * 1 + size('close') = 2 * 10 * 1 +1 = 21
在实际使用中,我们发现确实存在 21 种状态;在代码层面,我们也精准控制了这个值只能在这 21 种正确的状态中,所以出错的几率也大大降低(几乎不可能出错)。
相关文章:

技术分享 | 如何编写同时兼容 Vue2 和 Vue3 的代码?
LigaAI 的评论编辑器、附件展示以及富文本编辑器都支持在 Vue2(Web)与 Vue3(VSCode、lDEA)中使用。这样不仅可以在不同 Vue 版本的工程中间共享代码,还能为后续升级 Vue3 减少一定阻碍。 那么,同时兼容 Vue…...

基于ArcGis提取道路中心线
基于ArcGis提取道路中心线 文章目录 基于ArcGis提取道路中心线前言一、生成缓冲区二、导出栅格数据三、导入栅格数据四、新建中心线要素五、生成中心线总结 前言 最近遇到一个问题,根据道路SHP数据生成模型的时候由于下载的道路数据杂项数据很多,所以导…...

xcode14.3更新一系列问题
1. Missing file libarclite_iphoneos.a (Xcode 14.3) 解决方法 Xcode升级到14.3后编译失败,完整错误日志: File not found: /Applications/Xcode-beta.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc/libarclite_iphoneo…...
1U和2U的服务器怎么选择
企业建设网站的过程中,离不开租用服务器的环节,服务器在多种场景里面都可以发挥作用,服务器租用渠道有哪些?1U、2U选哪种服务器比较好?大家跟着壹基比小鑫一起来了解具体内容吧! 1U、2U选哪种服务器比较好&…...
【SA8295P 源码分析】05 - SA8295P QNX Host 上电开机过程 进一步梳理(结合代码)
【SA8295P 源码分析】05 - SA8295P QNX Host 上电开机过程 进一步梳理(结合代码) 一、APPS PBL(Application Primary Boot Loader):固化在CPU ROM中1.1 APPS PBL 加载 XBL Loader1.2 XBL Loader加载验证并运行SMSS进行自检,自检完成后触发Warm Reset1.3 WarmRest后,APPS…...

【数据结构与算法】迪杰斯特拉算法
迪杰斯特拉算法 介绍 迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。它的主要特点是以中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。 算法过程 设置…...

python爬虫-网页数据提取
import requests #headers 网页右键->Network->最下面的User-Agent复制。 headers {"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36"} #你想要的网址 url &q…...
ZigBee的Many-to-One和Source Routing
1. Many-to-One Routing Many-to-One Routing,是一种简单的路由机制,使得整个网络中的路由设备拥有回到中心节点的路由。 在这种机制下,中心节点周期性发送Many-to-One route discovery广播(协议栈默认设置为60s,可以…...

七夕节 Chinese Valentine‘s Day 的由来
农历七月初七是七夕节。Qixi Festival falls on the seventh day of the seventh lunar month. 以前有一个牛郎,和他的哥哥和嫂子住在一起。他放的一头牛曾经是天庭的一个神仙,但他违反天庭的戒律,变成牛放到了人间。As the story goes,once …...

掌握JDK21全新结构化并发编程,轻松提升开发效率!
1 概要 通过引入结构化并发编程的API,简化并发编程。结构化并发将在不同线程中运行的相关任务组视为单个工作单元,从而简化错误处理和取消操作,提高可靠性,并增强可观察性。这是一个预览版的API。 2 历史 结构化并发是由JEP 42…...
【SA8295P 源码分析】00 - 系列文章链接汇总 - 持续更新中
【SA8295P 源码分析】00 - 系列文章链接汇总 - 持续更新中 一、分区、下载、GPIO等杂项相关二、开机启动流程代码分析二、OpenWFD 显示屏模块三、Touch Panel 触摸屏模块四、QUPv3 及 QNX Host透传配置五、Camera 摄像头模块(当前正在更新中...)六、网络…...

TCP拥塞控制详解 | 6. 主动队列管理
网络传输问题本质上是对网络资源的共享和复用问题,因此拥塞控制是网络工程领域的核心问题之一,并且随着互联网和数据中心流量的爆炸式增长,相关算法和机制出现了很多创新,本系列是免费电子书《TCP Congestion Control: A Systems …...
前端学习清单
顺序不分先后。 技术名称技术描述技术链接HTML5HTML5是下一代的HTML标准,是一种用于结构化内容的标记语言。MDN|HTMLCSS3CSS3是CSS技术的升级版本,它的最大好处就是可以让网页设计师更加方便的为网页添加各种各样的样式,而不用再局限于文字、…...
go atomic原子操作详细解读
文章目录 概要1、基本知识1.1 原子操作是什么1.2 CPU怎么实现原子操作的? 2、atomic包2.1、 Add函数2.2、CompareAndSwap函数2.3、Swap函数2.4、Load函数2.5、Store函数 3、atomic.Value值 概要 atomic包是golang通过对底层系统支持的原子操作进行封装,…...

Vue用JSEncrypt对长文本json加密以及发现解密失败
哈喽 大家好啊,最近发现进行加密后 超长文本后端解密失败,经过看其他博主修改 JSEncrypt原生代码如下: // 分段加密,支持中文JSEncrypt.prototype.encryptUnicodeLong function (string) {var k this.getKey();//根据key所能编…...

Excel/PowerPoint折线图从Y轴开始(两侧不留空隙)
默认Excel/PowerPoint折线图是这个样子的: 左右两侧都留了大块空白,很难看 解决方案 点击横坐标,双击,然后按下图顺序点击 效果...
C++的类成员对齐
这是个小语法点,之前我们的对齐方式都是使用#pragma pack,这个方式实际是依赖编译器,且粒度粗(如果#pragma pack(1)之后没有#pragma pack(),那就作用整个进程了)。在C11之后引入关键字alignas,以此来实现对齐更加便利,…...
敏感挂载userhelper容器逃逸复现
目录 前言 分析 实验 前言 分析 实验 # Creates a payload cat "#!/bin/sh" > /evil-helper cat "ps > /output" >> /evil-helper chmod x /evil-helper # Finds path of OverlayFS mount for container # Unless the configuration ex…...
深度解读Promise.prototype.finally
由一个问题引发的血案: 手写源码实现Promise.prototype.finally。 我们知道,对于promise来讲,当状态敲定,无论状态兑现或拒绝时都需要调用的函数,可以使用Promise.prototype.finally的回调来实现。那么如何手写实现Pro…...

如何实现24/7客户服务自动化?建设智能客服知识库
客户自助服务是指用户通过企业或者第三方建立的网络平台或者终端,实现相关的自定义处理。实现客户服务自动化,对提高客户满意度、维持客户关系至关重要。客户服务自动化可以帮助企业以更快的速度和更高的效率来满足客户的售后服务要求,以进一…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...
Java 8 Stream API 入门到实践详解
一、告别 for 循环! 传统痛点: Java 8 之前,集合操作离不开冗长的 for 循环和匿名类。例如,过滤列表中的偶数: List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...
macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用
文章目录 问题现象问题原因解决办法 问题现象 macOS启动台(Launchpad)多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显,都是Google家的办公全家桶。这些应用并不是通过独立安装的…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...

C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
06 Deep learning神经网络编程基础 激活函数 --吴恩达
深度学习激活函数详解 一、核心作用 引入非线性:使神经网络可学习复杂模式控制输出范围:如Sigmoid将输出限制在(0,1)梯度传递:影响反向传播的稳定性二、常见类型及数学表达 Sigmoid σ ( x ) = 1 1 +...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...