vue-demi你真的会用吗
背景介绍
接到了一个需求,在多个系统上添加同一个功能,这多个系统中包含了 Vue2
和 Vue3
,为了多系统之间的复用,决定开发一个 Vue2
和 Vue3
都可以集成的插件
vue-demi
插件天生就是为了帮助完成这件事情的。
在开发中遇到了很多问题,看了很多介绍 vue-demi
的使用的文章,但是都没有问题的解决方案,所以在这里记录一下我踩过的坑,希望大家之后少踩点坑。
网上已经有了很多 vue-demi
的使用文章,所以这里不介绍使用的方法了,主要记录一下踩的坑和解决的方案。
主要问题
在开发过程中,主要遇到了以下几个问题
- 开发过程中怎么能在
vue2
和vue3
环境下做快速的切换 - 组件使用
template
模版写,在vue2
环境中报错 - 设置
img
标签的src
属性,在vue2
中没有展示 - 设置元素的事件,在
vue2
中没有生效 - 通过
ref
获取 DOM元素或者组件实例的时候,在vue2
中获取的是undefined
- 调用
js
方法渲染组件的时候,报错 - 在
vue2
环境中使用组件时,composition-api
没有生效
解决方案
坑1 开发过程中怎么能在 vue2
和 vue3
环境下做快速的切换
解决方法是:在 node_module
下安装两个版本的 vue
,分别命名为 vue
和 vue2
,在需要切换版本的时候,修改 node_modules
中的文件夹名,下面以 vite
项目中的解决
根目录下新建 scripts
文件夹,创建 script/swap-vue.js
文件
/* eslint-disable camelcase */
const fs = require('fs')
const path = require('path')
const Vue2 = path.join(__dirname, '../node_modules/vue2')
const DefaultVue = path.join(__dirname, '../node_modules/vue')
const Vue3 = path.join(__dirname, '../node_modules/vue3')
const vueTemplateCompiler = path.join(__dirname, '../node_modules/vue-template-compiler')
const vueTemplateCompiler2_6 = path.join(__dirname, '../node_modules/vue-template-compiler2.6')
const version = Number(process.argv[2]) || 3
useVueVersion(version)
function useVueVersion (version) {
if (version === 3 && fs.existsSync(Vue3)) {
resetPackageNames()
rename(Vue3, DefaultVue)
useTemplateCompilerVersion(3)
} else if (version === 2 && fs.existsSync(Vue2)) {
resetPackageNames()
rename(Vue2, DefaultVue)
useTemplateCompilerVersion(2)
} else {
console.log(`Vue ${version} is already in use`)
}
}
function resetPackageNames () {
if (!fs.existsSync(Vue3)) {
rename(DefaultVue, Vue3)
} else if (!fs.existsSync(Vue2)) {
rename(DefaultVue, Vue2)
} else {
console.error('Unable to reset package names')
}
}
function useTemplateCompilerVersion (version) {
if (!fs.existsSync(vueTemplateCompiler)) {
console.log('There is no default vue-template-compiler version, finding it')
rename(vueTemplateCompiler2_6, vueTemplateCompiler)
console.log('Renamed "vue-template-compliler2.6" to "vue-template-compliler"')
}
if (version === 3 && fs.existsSync(vueTemplateCompiler)) {
rename(vueTemplateCompiler, vueTemplateCompiler2_6)
}
}
function rename (fromPath, toPath) {
if (!fs.existsSync(fromPath)) return
try {
fs.renameSync(fromPath, toPath)
console.log(`Successfully renamed ${fromPath} to ${toPath} .`)
} catch (err) {
console.log(err)
}
}
package.json
添加下面的执行命令
{
"scripts": {
"use-vue:2": "node scripts/swap-vue.js 2 && vue-demi-switch 2",
"use-vue:3": "node scripts/swap-vue.js 3 && vue-demi-switch 3",
"dev:v2": "pnpm run use-vue:2 && VUE_VERSION=2 vite",
"dev:v3": "pnpm run use-vue:3 && VUE_VERSION=3 vite"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.1.0",
"vite": "^4.3.2",
"vite-plugin-vue2": "^2.0.3",
"vue": "^3.2.47",
"vue-template-compiler2.6": "npm:vue-template-compiler@2.6.11",
"vue2": "npm:vue@2.6.11"
},
"dependencies": {
"vue-demi": "^0.14.5"
}
}
这样在之后执行 pnpm run dev:v2
就是 vue2
环境,执行 pnpm run dev:v3
就是 vue3
环境
该方案参考 :GitHub - vuelidate/vuelidate
因为 vite
在编译 vue2
和 vue3
的时候,需要使用到不同的插件,所以还需要在 vite.config.js
中做判断。
vite
编译 vue2
用到插件 vite-plugin-vue2
,下载
pnpm add vite-plugin-vue2 -D
修改 vite.config.js
// 通过 package.json 中设置的环境变量判断是不是 vue2 环境
const isVue2 = +(process.env.VUE_VERSION) === 2;
export default defineConfig(async () => {
return ({
plugins: [
// 如果不是 vue2 环境引入 vite-plugin-vue2 插件会有报错,所以这里做判断之后在引入
isVue2 ? (await import("vite-plugin-vue2")).createVuePlugin() : vue(),
]
})
})
坑2 组件使用 template
模版写,在 vue2
环境中报错
因为 vue2
和 vue3
对 template
模版生成的 render
函数不一样,所以不能使用 template
写,做不了兼容。
解决方案:用 render
函数或 setup
中返回 render
函数,示例使用 setup
返回一个 render
函数
import { h } from "vue-demi";
const Toast = {
props: {
text: {
type: String,
default: ""
}
},
setup(props) {
return () =>
h("div", [
h("section", { class: 'toast-container' }, [
h("div", { class: 'toast' }, [
h("slot", `${props.text}`)
])
])
])
}
}
坑3 设置 img
标签的 src
属性,在 vue2
中没有展示
因为 vue2
中的 vnode
和 vue3
中不一样,在 vue3
中设置 img
的 src
可以直接通过 src
设置,在 vue2
中则要通过 attrs.src
设置。
// vue3
h("img", {src: "xxx"})
// vue2
h("img", { attrs: { src: "xxx" } })
坑4 设置元素的事件,在 vue2
中没有生效
和设置 src
属性一样,在vue3
中设置元素事件是直接通过 on${事件}
设置的,在 vue2
中需要通过 on.${事件}
设置
// 设置 click 事件
// vue3
h("div", {onClick: () => {})
// vue2
h("div", { on: { click: () => {} } })
这里为了不需要在每个h
函数的调用中处理这些问题,所以写一个函数统一处理了
import { isVue2 } from 'vue-demi'
const attrsNames = ['src'];
export function transformVNodeProps(props) {
if (!isVue2) { return props }
const on = {};
const attrs = {};
const events = Object.keys(props)
.filter(event => /^on[A-Z]/.test(event))
.forEach(event => {
const eventName = event[2].toLowerCase() + event.substring(3);
on[eventName] = props[event];
})
props.on = Object.assign({}, on, props.on || {});
attrsNames
.filter(name => props[name] !== undefined)
.forEach(name => {
attrs[name] = props[name]
})
props.attrs = Object.assign({}, attrs, props.attrs || {})
return props;
}
之后在调用h
函数的时候,传入的 props
都用 vue3
的方式写就可以了。
// vue2 / vue3
h("div", transformVNodeProps({ src: "xxx", onClick: () => {} }))
坑5 通过 ref
获取 DOM元素或者组件实例的时候,在 vue2
中获取的是undefined
这个是因为 composition-api
导致的
参考:GitHub - vuejs/composition-api: Composition API plugin for Vue 2
解决方案:在onMounted
生命周期中通过 setupContext.refs
获取
{
setup(props, setupContext) {
const container = ref()
const refs = setupContext.refs;
if (isVue2) {
onMounted(() => { container.value = refs.container })
}
return () => h("div", { ref: isVue2 ? "container", container })
}
}
坑6 调用 js
方法渲染组件的时候,报错
在需要通过 js
方法渲染组建的时候,如果可以使用createApp
完成需求,就尽量不要使用 vue2
的 Vue.extend
或者 vue3
的 render
函数,避免处理复杂的兼容性问题。
import { createApp, isVue2 } from 'vue-demi'
import TestComponent from './test-component'
let instance;
let app;
let container;
async function jsRender() {
if (instance) { return }
return new Promise((resolve) => {
const remove = () => {
app.unmount();
document.body.removeChild(isVue2 ? instance.$el : container)
app = null;
instance = null;
container = null;
}
app = createApp(TestComponent)
container = document.createElement('div');
document.body.appendChild(container)
instance = app.mount(container)
})
}
坑7 在vue2
环境中使用组件时,composition-api
没有生效
在 vue2
环境中,要使用 composition-api
需要通过 vue.use(VueCompositionAPI)
函数注册插件之后,才能使用。
在 vue-demi
中导出了 install
函数就是完成这个操作的。
vue-demi
会默认执行一次 install
函数,但是这个函数并没有把 VueCompositionAPI
挂载到我们 vue2
项目中使用的 Vue
上,而是挂载在它自己引入的 Vue
上。
为了 VueCompositionAPI
能正确的挂载,需要在我们插件导出的 install
中手动执行一次 install
函数。
impprt { install } from 'vue-demi'
export default (_vue) => {
install(_vue)
})
总结
以上内容就是在这次 vue-demi
开发插件中遇到的问题了,解决问题的过程也是学习的过程,看了很多优秀的开源项目是怎么解决这些遇到的。
大家如果还有遇到的问题和解决方案的,欢迎留言补充。
转载自:https://juejin.cn/post/7236184868480663589