技术分享 | 如何编写同时兼容 Vue2 和 Vue3 的代码?

news2025/1/10 16:49:04

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.mjs
function 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

  • attrsprops 一样,只需写在最外层;
  • onClick: ()=> {}
  • slot 写在 createElement 函数的第三个参数中。
 h(LayoutComponent, {
        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
        ]);
        // 这里提取出了组件的 props
        const 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 }; // 排除 scopedSlots

      const slots = adaptScopedSlotsV3(scopedSlots); // 处理 slots
      if (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 中 refreactive 是状态,而 computed 不是状态。

2.1 什么是「状态」?

状态是可以由系统内部行为更改的数据,而状态大小是状态所有可能的值的集合的大小,记作 size(State)。而代码复杂度 = States.reduce((acc, cur) => acc * size(cur),1)

2.1.1 常见数据类型的状态大小

一些常见的数据类型,比如 unit 的状态大小是 1,在前端里可以是 null、undefined;所有的常量、非状态的大小也是 1。而 Boolean 的状态大小是 2。

NumberString 一类有多个或无限个值的数据类型,在计算状态大小时需明确一点,我们只关心状态在业务逻辑中的意义,而不是其具体值,因此区分会影响业务逻辑的状态值即可。

例如,一个接口返回的数据是一个数字,但我们只关心这个数字是正数还是负数,那么这个数字的状态大小就是 2。

2.1.2 复合类型的状态大小

复合类型分为和类型与积类型两种。

和类型状态大小的计算公式为 size(C) = size(A) + size(B),而积类型状态大小的计算公式为 size(C) = size(A) * size(B)

了解完代码优化标准后,我们通过一个案例说明如何利用代数数据类型,精简代码。

2.2 案例:评论编辑器的显示控制

在 LigaAI 中,每个评论都有两个编辑器,一个用来编辑评论,一个用来回复评论;且同一时间最多只允许存在一个活动的编辑器。

2.2.1 优化前的做法

为回复组件定义两个布尔变量 IsShowReplyIsShowEdit ,通过 v-if 控制是否显示编辑器。点击「回复」按钮时,逻辑如下:

(1) 判断自己的 IsShowReply 是否为 true,如果是,直接返回;

(2) 判断自己的 IsshowEdit,如果为 true 则修改为 false,关闭编辑评论;

(3) 依次设置所有其他评论组件的 IsShowReplyIsShowEdit 为 false;

(4) 修改自己的 IsShowReply 为 true。

当有 10 个评论组件时,代码复杂度是多少?

size(CommentComponent) = size(Boolean) * size(Boolean) = 2 * 2 = 4
size(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@CSDN。

LigaAI-新一代智能研发协作平台 助力开发者扬帆远航,欢迎申请试用我们的产品,期待与你一路同行!

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

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

相关文章

Shell脚本攻略:文本三剑客之grep

目录 一、理论 1.grep 2.sort 3.uniq 4.tr 5.cut 6.split 7.paste 二、实验 1. grep 2.sort 3.uniq 4.tr 5.cut 6.split 7.统计当前主机的连接状态 8.统计当前连接主机数 一、理论 1.grep &#xff08;1&#xff09;概念 grep是Linux中最常用的”文本处理工…

Redis-主从复制以及哨兵

前言 ​ 要避免单点故障&#xff0c;即保证高可用&#xff0c;便需要冗余&#xff08;副本&#xff09;方式提供集群服务。而Redis 提供了主从库模式&#xff0c;以保证数据副本的一致&#xff0c;主从库之间采用的是读写分离的方式。在 Redis 主从集群中&#xff0c;哨兵机制…

共享打印机出现文件无法打印|0X00000709错误的解决方案|win7 win11

现在办公室通常会使用打印机共享功能&#xff0c;在使用打印机共享中会出现在[文件无法打印&#xff0c;因为在“\ip\打印机名 在Ne02&#xff1a;”上发生错误] 这时&#xff0c;我们输入相应IP重新共聚共享端打印机&#xff0c;就会出现“0X00000709”的错误。 回到共享端…

Rust in Action笔记 第三章 复合数据类型

通过#[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZI50wIh1-1685693144796)(null)]&#xff09;&#xff0c;不带感叹号的#[allow]仅为下一行代码提供属性&#xff0c;属性有多种分类&#xff0c;文中出现的allow属性称为诊断属性&#xff08;Diag…

僵尸网络检测数据集CTU-13介绍及使用

首先感谢爱学习的大白菜爱学习的大白菜的博客_CSDN博客-刷题,ACM之路,机器学习领域博主对CTU-13数据集的描述的翻译&#xff0c;这使得后面对这份数据集的理解更加容易。 参考CTU-13数据集_爱学习的大白菜的博客-CSDN博客 由于要做僵尸网络检测的工作&#xff0c;但是没有相关…

权限维持-HOOK-DLL加载

前言 继续来研究关于权限维持的方法&#xff0c;这里希望与各位一起加油。 这里的HOOK其实指的就是Hook PasswordChangeNotify 其中PasswordChangeNotify是一个函数&#xff0c;如果修改的密码符合要求&#xff0c;那么PasswordChangeNotify在系统上同步更新密码&#xff0c;然…

网讯1860网卡网络流控导致网络异常问题

1、背景介绍 目前使用的飞腾平台中采用国产化WX1860千兆网卡芯片&#xff0c;交换芯片采用盛科的CTC8096。示意图如下&#xff1a; 当其中一块飞腾系统异常时&#xff0c;发现整个平台内所有的千兆网均出现异常&#xff0c;任意两个飞腾千兆网均无法ping通。 2、问题排查 一…

什么是边缘计算网关以及什么是边缘计算?

边缘计算 边缘计算是一种分布式计算范例&#xff0c;使计算任务从数据中心或云端移向离数据源更近的地方&#xff0c;即网络的"边缘"。这样做的目的是降低网络延迟&#xff0c;提高数据处理速度&#xff0c;同时减少不必要的数据传输&#xff0c;节省带宽和存储资源。…

如何实现折起来的效果

如何实现范围裁剪和旋转效果。 日常 我还是希望自己的博客能够存在一些温度&#xff0c;而不是冷冰冰的技术分享&#xff0c;我希望留下专属于我自己的个性&#xff0c;就像鸿洋和郭霖的公众号一样。他们会在自己的博客顶端随便写一下最近的新闻什么的&#xff0c;而我不同&a…

msvcr110.dll丢失的解决方法哪种好,推荐这个4种解决方法

Msvcr110.dll是Microsoft Visual Studio 2012的运行时组件之一。这个DLL文件包含一些用于Windows操作系统的C函数库。当程序需要这些函数时&#xff0c;它们会被加载到内存中&#xff0c;以便程序可以使用它们。 当计算机提示“msvcr110.dll丢失”时&#xff0c;可能出现以下影…

基于matlab使用接收机工作特征 (ROC) 曲线评估相干和非相干系统性能

一、前言 此示例说明如何使用接收机工作特征 &#xff08;ROC&#xff09; 曲线评估相干和非相干系统的性能。该示例假设检测器在加性复杂高斯白噪声环境中工作。 ROC曲线通常用于评估雷达或声纳探测器的性能。ROC 曲线是给定信噪比 &#xff08;SNR&#xff09; 的检测概率 &a…

介绍CSDN的阅览器

大家好&#xff01;今天给大家一款由csdn官方打造的一个界面阅览器 意思就是靠我们自带的阅览器改变他的外观方式&#xff0c;这就是我用了CSDN阅览器之后的效果图 然后我们也可以清晰看到我们整个界面&#xff0c;对于我们经常写csdn博客的小伙伴来说是很方便&#xff0c;因为…

配电室动环监控系统的功能与应用

来源&#xff1a;山东仁科www.rkckth.com 六月天的 【配电室】&#xff0c;表面 “ 风平浪静 ”&#xff0c;实则 “ 多面夹击 ”。 步入六月份的夏季由高温掌控&#xff0c;为营造舒适凉爽的生活环境&#xff0c;人们的用电量也进入“汛期”&#xff0c;急速上涨&#xff0c…

亚信前端面试真题

目录 1.如何设置一个元素不可见&#xff1f; 2.谷歌浏览器设置比12px还小的字体&#xff1f; 3.深拷贝实现方法&#xff1f;??????? 4.防抖节流实现原理???? 5.遍历循环数组方法&#xff1f;???? 6.vue的双向绑定&#xff1f;???? 7.compute和watche…

【MySQL高级篇笔记-InnoDB数据存储结构 (中) 】

此笔记为尚硅谷MySQL高级篇部分内容 目录 一、数据库的存储结构:页 1、磁盘与内存交互基本单位:页 2、页结构概述 3、页的大小 4、页的上层结构 二、页的内部结构 1、分三个部分看 2、从数据页角度看B 树如何查询 三、InnoDB行格式(或记录格式) 四、区、段与碎片区…

测试环境一键发布

背景 目前公司项目发布测试环境不够自动化&#xff0c;每次需要手动打包并且手动更新&#xff0c;影响开发效率 流程图 而且因为是本地手动发布&#xff0c;容易失误造成一些不必要的麻烦&#xff1a; 远端代码有更新&#xff0c;忘记拉取代码 快速发布&#xff0c;本地代码…

Redis7实战加面试题-高阶篇(Redis为什么快?高性能设计之epoll和IO多路复用深度解析)

多路复用要解决的问题 并发多客户端连接&#xff0c;在多路复用之前最简单和典型的方案&#xff1a;同步阻塞网络IO模型 这种模式的特点就是用一个进程来处理一个网络连接(一个用户请求)&#xff0c;比如一段典型的示例代码如下。 直接调用 recv 函数从一个 socket 上读取数据…

Nginx:Nginx优化与防盗链

Nginx&#xff1a;Nginx优化与防盗链 一、配置Nginx隐藏版本号二、修改Nginx用户与组三、配置Nginx网页缓存时间3.1 修改配置文件3.2 页面缓存验证 四、配置Nginx实现连接超时五、更改Nginx运行进程数六 、配置Nginx实现网页压缩功能七、配置Nginx实现防盗链 一、配置Nginx隐藏…

[数据集][目标检测]目标检测数据集蜜蜂4073张1类别VOC格式

出品方&#xff1a;未来自主研究中心(FIRC) 数据集格式&#xff1a;Pascal VOC格式(不包含分割路径的txt文件和yolo格式的txt文件&#xff0c;仅仅包含jpg图片和对应的xml) 图片数量(jpg文件个数)&#xff1a;7398 标注数量(xml文件个数)&#xff1a;7398 标注类别数&#xff…

如何利用宝塔面板快速搭建Wordpress网站?

本章教程&#xff0c;主要介绍一下&#xff0c;如何利用宝塔面板快速搭建Wordpress网站。 目录 一、 前置条件 二、 打开宝塔面板 三、解析域名 四、安装界面 五、主题安装 六、网站预览 一、 前置条件 需要准备一台Linux服务器&#xff0c;系统版本使用centos 7.X。 使用…