Press UI 支持 Vue3

1. 开始

Press UI 是一套基于 uni-app 的组件库,是项目内孵化的,但并不与任何业务绑定的底层组件库。

目前 Press UI 主要有三方面功能:

  • 基础组件,提供与 Vant 相同API的组件,比如 Button、Picker 等共60多个
  • 业务组件,在基础组件上搭建的、业务中沉淀的组件,目前有10多个
  • 核心逻辑,包含路由寻址、IM模块封装等

下图是示例二维码,分别为H5、微信小程序、QQ小程序,以及非uni-app环境的普通H5项目。

Press UI 是基于 Vue2 版本的 uni-app 搭建的,目前在使用的几个项目也都是 Vue2。但前端日新月异,Vue3 更快、性能更好,Press UI 如何兼容 Vue3 呢?

在兼容 Vue3 前,要先理解 uni-app 和 Vue 的关系。

uni-app是基于 vue 的,尽管有一些源码的魔改,但整体的响应机制、模版解析、语法都依赖 Vue。所以一个 Vue2 版本的组件库,要适配 Vue3,必然要修改一些语法,才可以达到兼容的目的。

2. 条件编译

在上一篇 Press UI 兼容普通 Vue 项目的时候,提到了条件编译是跨平台的核心。

其实兼容 Vue3 也可以用条件编译,且uni-app已经支持了。

条件编译比if else的运行时判断有更小的包体积,性能更好。

ts 复制代码
// #ifndef VUE3
console.log('Vue2')
// #endif

// #ifdef VUE3
console.log('Vue3')
// #endif

3. 构建工具

Vue3 版本的 uni-app 构建工具是 vite,速度更快。

Press UI 工程依赖一些自定义的 vue-cliwebpack)的插件,它们都要重新实现一下 vite 版本的,比如:

  • 转化v-lazy
  • 转化rem

目前这些插件也已经沉淀到了 uni-plugin-light 中。

4. script语法兼容

下面是兼容 Vue3 时的遇到的语法转化问题,这里记录下。

4.1. Vue语法转换

2.x 全局 API 3.x 实例 API (app)
Vue.config app.config
Vue.config.productionTip 移除
Vue.config.ignoredElements app.config.compilerOptions.isCustomElement
Vue.component app.component
Vue.directive app.directive
Vue.mixin app.mixin
Vue.use app.use
Vue.prototype app.config.globalProperties
Vue.extend 移除(Vue.createApp)

参考:v3-migration.vuejs.org/zh/breaking...

4.2. 移除$on$off等eventBus的api

press-ui中只有一处使用,且可以移除。

参考:v3-migration.vuejs.org/zh/breaking...

4.3. $set废弃

Vue3 废弃了$set,如果还使用会报错,所以需要兼容下。

ts 复制代码
dialog.set = (...args: any[]) => {
  if (typeof dialog.$set === 'function') {
    dialog.$set(dialog, ...args);
  } else {
    dialog[args[0]] = args[1];
  }
};

参考:blog.csdn.net/weixin_4425...

4.4. $children废弃

Vue3 中已废弃 $children,需要改成 $refs

Press UI 中可函数调用的组件,都是用的 $children,都需兼容使用 ref 的场景。

同时,使用 Press UI 的开发者,在预埋组件的时候,需要埋 ref,而不是 id。

之前:

html 复制代码
<press-popup
  :id="PRESS_PICKER_ID"
  mode="functional"
>
 xxx
</press-popup>

现在:

html 复制代码
<press-popup
  :ref="PRESS_PICKER_ID"
  mode="functional"
>
  xxx
</press-popup>

参考:v3-migration.vuejs.org/zh/breaking...

4.5. nextTick

Vue3 兼容如下:

ts 复制代码
export function nextTick(cb?: any) {
  // #ifndef VUE3
  Vue.nextTick(cb);
  // #endif

  // #ifdef VUE3
  vue3NextTick(cb);
  // #endif
}

4.6. provide、inject

Vue3 兼容如下:

ts 复制代码
export function toProvideThis(key) {
  return {
    // #ifndef VUE3
    provide() {
      return {
        [key]: this,
      };
    },
    // #endif

    // #ifdef VUE3
    setup() {
      const instance = getCurrentInstance() as any;
      provide(key, instance.ctx);
      return instance;
    },
    // #endif
  };
}


export function toInject(key) {
  return {
    // #ifndef VUE3
    inject: {
      [key]: {
        default: null,
      },
    },
    // #endif

    // #ifdef VUE3
    setup() {
      const value = inject(key);
      return { [key]: value };
    },
    // #endif
  };
}

4.7. emits

Vue3 现在提供了一个emits选项,类似于现有props选项,可用于定义组件可以向其父对象发出的事件。

强烈建议使用emits记录每个组件发出的所有事件。

注意,emits 选项会影响一个监听器被解析为组件事件监听器,还是原生 DOM 事件监听器。被声明为组件事件的监听器不会被透传到组件的根元素上,且将从组件的 $attrs 对象中移除。

简单来说就是,没在子组件emits中声明,但在父组件用到的监听器,就会把这些当作子组件根元素的原生事件监听器

同时,Vue3 也废弃了 .native 修饰符。

实际开发中,遇到一个案例。press-swipe-cell中如果没将clickemits暴露,父组件的@click事件有时会触发两次,多出来的那一次就是在根节点上的事件。

参考:

  1. cn.vuejs.org/api/options...
  2. zh.uniapp.dcloud.io/tutorial/mi...

4.8. 生命周期兼容

  • destroyed 修改为 unmounted
  • beforeDestroy 修改为 beforeUnmount

Press UI 采用的是两种写法共存。

5. template语法兼容

5.1. 空的template

不要用空的template,

  • 在 Vue.js 2.x 中,<template>没有特定指令的元素无效
  • 在 Vue.js 3.x 中,<template>没有特定指令的元素按<template>原样渲染元素
html 复制代码
<template>
  <!-- ✓ GOOD -->
  <template v-if="foo">...</template>
  <template v-else-if="bar">...</template>
  <template v-else>...</template>
  <template v-for="e in list">...</template>
  <template v-slot>...</template>

  <!-- ✗ BAD -->
  <template>...</template>
  <template />
</template>

还有一个偷懒的办法,加上v-if="true"

html 复制代码
<template v-if="true"></template>

参考:eslint.vuejs.org/rules/no-lo...

5.2. slot使用

Vue2 中的slot="xxx"语法,需要转成 v-slot:xxx,或者#xxx

比如:

html 复制代码
<PressIconPlus
  slot="button"
  name="setting-o"
  size="22px"
/>

在 Vue3 中需要改成:

html 复制代码
<template #button>
  <PressIconPlus
    name="setting-o"
    size="22px"
  />
</template>

这个 Vue2 也是支持的。

5.3. 注释不要作为template中第一个元素

在 Vue3 中注释也作为一种特殊的元素,如果第一个元素是注释,this.$el就会指向它,如果代码里用到了el的属性或方法,比如querySelector就会报错。

举例如下:

html 复制代码
<template>
  <!-- Some Comments Here -->
  <div>xxx</div>
</template>

此时在 Vue3 中打印下,就会看到#text的一个元素。可以改成:

html 复制代码
<template>
  <div>
    <!-- Some Comments Here -->
    xxx
  </div>
</template>

5.4. v-model

Vue3 的 v-model 相对 Vue2 来说 ,有了较大的改变。可以使用多 model,相应语法也有变化。

用于自定义组件时,Vue3 的 v-model prop 和事件默认名称已更改 props.value 修改为 props.modelValueevent.value 修改为 update:modelValue

Press UI 的适配方法是先引入通用适配器,然后少量改动组件。

ts 复制代码
export const vModelMixin = {
  props: {
    // #ifndef VUE3
    value: {
      type: [String, Boolean],
      default: '',
    },
    // #endif
    // #ifdef VUE3
    modelValue: {
      type: [String, Boolean],
      default: '',
    },
    // #endif
  },
  computed: {
    realModelValue() {
      let result = '';

      // #ifndef VUE3
      // @ts-ignore
      result = this.value;
      // #endif

      // #ifdef VUE3
      // @ts-ignore
      result = this.modelValue;
      // #endif
      return result;
    },
  },
  methods: {
    emitModelValue(this: any, value) {
      // #ifndef VUE3
      this.$emit('input', value);
      // #endif

      // #ifdef VUE3
      this.$emit('update:modelValue', value);
      // #endif
    },
  },

};

组件改动:

  • 使用 value 的地方改成 realModelValue
  • 抛出 input 事件改成 this.emitModelValue(value)

Press UI 中适配了 v-model 的的组件有:

  • press-list
  • press-message-board
  • press-field

参考:

  1. zh.uniapp.dcloud.io/tutorial/mi...
  2. v3-migration.vuejs.org/zh/breaking...

5.5. 移除.sync

Vue3 已经移除了 .sync 语法,可以直接用 v-model:title 的方式。Press UI 中如何兼容呢?

对于这种已经废弃的语法,Press UI 只能取二者的交集,也即是最普通的方式实现。

之前:

html 复制代码
<ComponentA
  :show.sync="showAddressPopup"
/>

现在:

html 复制代码
<ComponentA
  :show="showAddressPopup"
  @update:show="value => showAddressPopup = value"
/>aw

6. 工程适配

6.1. sass

不再默认支持sass,需要手动安装 sass-loader、sass

bash 复制代码
pnpm i sass-loader sass -D

参考:juejin.cn/post/721922...

6.2. 设置publicPath

vue2 设置路径:

  • manifest.json -> h5 -> publicPath

vue3 设置路径:

  • manifest.json -> h5 -> router -> base

6.3. importsNotUsedAsValues报错

Vue3 tsconfig.json 报错:

vbnet 复制代码
Option 'importsNotUsedAsValues' is deprecated and will stop functioning 
in TypeScript 5.5. Specify compilerOption '"ignoreDeprecations": "5.0"' 
to silence this error.

这个报错是因为 TypeScript 4.9 版本中引入了一个新的编译选项importsNotUsedAsValues,用于检查导入语句是否被使用。但是这个选项在 TypeScript 4.9 版本中只是一个实验性的特性,它的行为可能会发生变化或被移除。因此,在 TypeScript 4.9 版本中,这个选项默认是开启的,但是在 TypeScript 5.0 版本中,它已经被标记为过时,并且在 TypeScript 5.5 版本中将被移除。

解决办法,tsconfig.json中添加以下属性:

json 复制代码
{
  "compilerOptions": {
    "ignoreDeprecations": "5.0",
    ....
  }
}

参考:

  1. frontend.devrank.cn/traffic-inf...
  2. stackoverflow.com/questions/7...

6.4. tree-shaking

Vue3 在 H5 平台发行时,会默认开启 tree-shaking,仅打包明确使用的api,比如uni.request()这种使用方式,而动态调用的方式不识别。

ts 复制代码
const method='request';
uni[method]()`

如果要关闭 tree-shaking,可以在 manifest.json 中配置:

json 复制代码
"h5": {  
  "optimization": {  
      "treeShaking": {  
        "enable": false  
    }
  }
}

其实,个人感觉这种 tree-shaking 有很大隐患,太过隐晦,很容易踩坑。

参考: ask.dcloud.net.cn/question/14...

7. 效果

下图是适配 Vue3 的示例:

8. 总结

总结一下 Press UI 是如何支持 Vue3 的。

  • 将 Press UI 作为 submodule,搭建 Press UI V3 工程,进行调试和验证
  • 编写适配代码,同时兼容 Vue2 和 Vue3 语法
  • 对于某些相同API,但 Vue2 和 Vue3 表现不一致的,也进行兼容
  • 利用条件编译,减少代码冗余,减少代码体积
  • 编写 Vite 相关插件,支持 Press UI 工程

其实 Press UI 适配 Vue3 的大部分工作就是做一些适配器,从上面遇到的问题可以看出,Vue3 相对 Vue2,大部分 template 语法是向下兼容的,但 script 语法大部分是 breaking 的。

之前总有人问,可不可以实现一个兼容 Vue2 和 Vue3 的组件库,经过上面的实践证明,是可行的。

相关推荐
前后端杂货铺30 分钟前
uniapp利用生命周期函数实现后台常驻示例
android·前端·ios·微信小程序·uni-app
Jiaberrr11 小时前
uniapp 实现低功耗蓝牙连接并读写数据实战指南
java·前端·javascript·vue.js·struts·uni-app
林小白的日常11 小时前
uniapp打包apk如何实现版本更新
前端·javascript·uni-app
xixixin_11 小时前
【uniapp】在UniApp中检测手机是否安装了某个应用
uni-app
前后端杂货铺17 小时前
uniapp+vue3+ts 使用canvas实现安卓端、ios端及微信小程序端二维码生成及下载
android·前端·ios·微信小程序·uni-app·canavas·二维码海报生成
小梦想的博客1 天前
将uni-app前端项目发布到微信小程序体验版
前端·微信小程序·uni-app
低级前端1 天前
uniapp如何获取安卓原生的Intent对象
前端·uni-app·安卓·web app
Mr.app2 天前
uni-app(vue3)动态获取swiper的区域高度以及通过scroll-view实现区域滚动和scroll-view的置顶功能
uni-app
谢一歇_fn2 天前
如何在uni-app中自定义输入框placeholder的样式
前端·javascript·uni-app
车轮滚滚__2 天前
uniapp 小程序 安卓苹果 短视频解决方案
ios·小程序·uni-app·安卓·html5