vue2升级vue3:Vue Demi打通vue2与vue3壁垒,构建通用组件
Author:zhoulujun Date:
如果你的vue2代码之前是使用vue-class-component 类组件模式写的。选择可以使用 https://github.com/facing-dev/vue-facing-decorator 来进行低成本的升级,但是升级难度还是蛮大的。
如果你之前的vue2 版本使用的是 @vue/composition-api,那么 Vue Demi 以后可以无缝升级vue3.
Vue Demi概述
Vue Demi 是一个很棒的包,具有很多潜力和实用性。
根据创建者 Anthony Fu 的说法
Vue Demi 是一个开发实用程序,允许你为 Vue 2 和 3 编写通用 Vue 库。而无需担心用户安装的版本。
作者Antfu的介绍博客: https://antfu.me/posts/make-libraries-working-with-vue-2-and-3
vue-demi库:https://github.com/vueuse/vue-demi
以前,要创建支持两个目标版本的 Vue 库,我们会使用不同的分支来分离对每个版本的支持。对于现有库来说,这是一个很好的方法,因为它们的代码库通常更稳定。
缺点是,你需要维护两个代码库,这让你的工作量翻倍。对于想要支持Vue的两个目标版本的新Vue库来说,我不推荐这种方法。实施两次功能请求和错误修复根本就不理想。
这就是 Vue Demi 的用武之地。Vue Demi 通过为两个目标版本提供通用支持来解决这个问题,这意味着您只需构建一次即可获得两个目标版本的所有优点,从而获得两全其美的优势。
通用Vue库。意味着这大多数情况下不是业务开发者会直接使用到的库,主要面向库开发者(vue组件库/vue插件 等)
开发工具。供库开发者使用的工具,也就是说业务开发者不实际感知到它;
在 Vue 2 中,Composition API 作为插件提供,在使用它之前需要安装在 Vue 实例上:
import Vue from 'vue'
import VueCompositionAPI from '@vue/composition-api'
Vue.use(VueCompositionAPI)
Vue Demi 会尝试自动安装它,但是对于您想要确保插件安装正确的情况,提供了 install() API 来帮助您。
Vue Demi使用了NPM钩子postinstall。当用户安装所有包后,脚本将开始检查已安装的Vue版本,并根据Vue版本返回对应的代码。在使用Vue 2时,如果没有安装@vue/composition-api,它也会自动安装.
它作为 Vue.use(VueCompositionAPI) 的安全版本公开:
import { install } from 'vue-demi'
install()
在实际的代码中,直接用就好
Vue Demi改造
当你使用vue Api时,请从vue-demi里导入,它会自动根据用户使用的环境,而被重定向到vue@3.x或者vue@2.x + @vue/composition-api。
当用户要创建一个Vue插件/库时,只需将vue-demi安装为依赖项并将其导入,然后像之前一样发布你的插件/库,用户的软件包就会变得通用。
import {defineComponent, PropType, h, isVue2} from "vue-demi"
export default defineComponent({
// ...
})
但是还是的修改你package 代码:
{
"peerDependencies": {
"@vue/composition-api": "^1.4.3",
"vue": "^2.6.12 || >=3.2.37"
},
"peerDependenciesMeta": {
"@vue/composition-api": {
"optional": true
}
}
}
以及打包配置,需要增加optimizeDeps: {exclude: ['vue-demi']},具体配置如下:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
import dts from 'vite-plugin-dts'
import vueJsx from '@vitejs/plugin-vue-jsx'
export default defineConfig({
plugins: [dts(), vue(), vueJsx()],
optimizeDeps: {
exclude: ['vue-demi']
},
build: {
outDir: 'dist',
minify: 'terser',
sourcemap:true,
terserOptions: {
compress: {
//生产环境时移除console
drop_console: false,
drop_debugger: false,
},
},
lib: {
entry: resolve(__dirname, '../packages/index.ts'),
name: 'v3-grid-layout'
},
rollupOptions: {
// 确保外部化处理那些你不想打包进库的依赖
external: ['vue'],
output: {
// 在 UMD 构建模式下为这些外部化的依赖提供一个全局变量
globals: {
vue: 'Vue'
}
}
}
}
})
vue3优雅降级,把from ’vue‘ 替换为 from 'vue-demi';
vue2/vue3兼容性问题
普通组件,替换完composition api,完全没有啥问题。但是,也遇到一些兼容性问题,记录如下:
插槽slots兼容
比如:Error in render: "TypeError: n.call is not a function"
vue3 插件使用:获取默认插槽需要将 this.$slots.default 作为方法调用 this.$slots.default()。
而vue2是这用使用的
render(h) {
//TODO
const slot = this.$slots.default; // 默认插槽
return h('div', null, slot); // 将传入的默认插槽内容使用 div 包裹
}
所以为了兼容性,首先封装一个渲染slot的函数,更加vue版本视情况渲染插槽。
import { h, isVue2 } from 'vue-demi';
export function renderSlots(slots: any, props: string, h2: any) {
const slot = slots[props]
if (!slot) {
return
}
// vue2
if (h2 && typeof h2 === 'function') {
// if (isVue2) {
return h2('div', null, slot);
}
// vue3
return h('div', null, slot?.());
}
渲染组件
{
name: 'GridItem',
//TODO
render(h: any) {
const renderContent = () => {
if (this.loading && this.$slots.lading) {
if (typeof this.$slots.lading) {
return renderSlots(this.$slots, 'lading', h);
}
}
{
}
return renderSlots(this.$slots, 'default', h);
};
return (
<div
ref="itemContainer"
class={this.classObj}
style={this.style.data}>
{renderContent()}
{this.resizableAndNotStatic ? (
<span ref="handle" class={this.resizableHandleClass}></span>
) : ''}
</div>
);
},
});
这用就没有问题了。我的代码都是tsx写的,目前没有遇到问题
跨组件通信
Vue2 中的 $emit 和 $on 来实现事件总线(EventBus),vue3由于移除了 $on,实现事件总线已经没办法使用 Vue 自身的 API 了。
我们需要借助第三方库来完成,例如 mitt 或 tiny-emitter。这里我选择了 mitt
我这里我强调,尽量避免bus 通信。很容易造成消息满天飞。
Vue 插件兼容
我们看TDesign,vue2:https://github.com/Tencent/tdesign-vue
import Vue, { VueConstructor, PluginObject } from 'vue';
import capitalize from 'lodash/capitalize';
export function withInstall<T>(comp: T, dep?: PluginObject<any>) {
const c = comp as any;
const name = c?.options?.name || c.name;
c.install = function (Vue: VueConstructor, config?: object) {
const defaults = { prefix: 't' };
const installConfig = { ...defaults, ...config };
/// 为保证组件名称简洁,前缀保持为一个单词,首字母大写
const defaultPrefix = capitalize(defaults.prefix);
// mapprops component is original component
let componentName = name.replace(defaultPrefix, '').replace('-mapprops', '');
componentName = capitalize(installConfig.prefix) + componentName;
Vue.component(componentName, comp);
};
if (dep && Vue?._installedPlugins?.indexOf(dep) === -1) {
Vue.use(dep);
}
return comp as T & PluginObject<T>;
}
export default withInstall;
再看vue3的代码:https://github.com/Tencent/tdesign-vue-next
import { App, Plugin, Component, Directive } from 'vue';
function withInstall<T>(comp: T, alias?: string, directive?: { name: string; comp: Directive<T> }): T & Plugin {
const componentPlugin = comp as T & Component & Plugin;
componentPlugin.install = (app: App, name?: string) => {
app.component(alias || name || componentPlugin.name, comp);
directive && app.directive(directive.name, directive.comp);
};
return componentPlugin as T & Plugin;
}
export default withInstall;
由于 Vue3 中插件的 install 方法传入的不再是 Vue 构造函数,而是 app 实例,这里只需要调整形参名即可:Vue -> app。
参考文章:
开发一个同时支持vue2和vue3的组件库能做到吗? - https://www.zhihu.com/question/475451857/answer/2377600057
使用 Vue Demi 构建通用的 Vue 组件库 https://developer.51cto.com/article/700797.html
一库】vue-demi: 一拳打穿vue2和3的版本次元壁 https://juejin.cn/post/7032860019880099847
Vue Demi https://madewith.cn/502
转载本站文章《vue2升级vue3:Vue Demi打通vue2与vue3壁垒,构建通用组件》,
请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/vue3/8866.html
延伸阅读:
- vue2升级vue3:组合式 API之Setup(props,context)—Vue2.x到Vue3注意
- vue2升级vue3:Vue2/3插槽——vue3的jsx组件插槽slot怎么处理
- vue2升级vue3:Vue3时jsx组件绑定自定义的事件、v-model、sync修
- vue2升级vue3:vue2 vue-i18n 升级到vue3搭配VueI18n v9
- vue2升级vue3: 全局变量挂载与类型声明
- vue2升级vue3: Event Bus 替代方案—— mitt
- vue2升级vue3:TypeScript下vuex-module-decorators/vuex-class to vuex4.x
- vue2升级vue3:class component的遗憾
- vue2升级vue3:this.$createElement is not a function—动态组件升级
- Uncaught (in promise) TypeError: 'get' on proxy: property X is a read-on
- vue2升级vue3:composition api中监听路由参数改变
- vue2升级vue3:vue3创建全局属性和方法
-
vue2升级vue3:Vue Router报错,directly inside
or - vue2升级vue3:vue3比vue2究竟好在哪里?
- vue2升级vue3:vue3 hooks库选用
- vue2升级vue3:provide与inject 使用注意事项
- vue2升级vue3:单文件组件概述 及 defineExpos/expose
- VUE3/TS/TSX入门手册指北
- vue2升级vue3:vue3中的watch和watchEffect细讲
- vue2升级vue3:vue3真的需要vuex或者Pinia吗?hooks全有了
- vue2升级vue3:vue2与vue3里vue-router如何实现跳转时打开新页面
- vue2升级vue3:vue-i18n国际化异步按需加载
- vue3函数式与函数式组件的太监文
- vue2升级vue3:vue router使用需要注意的事项随笔