起因
有个 Vue 项目用到 ECharts,组内几个前端菜鸟集成得乱七八糟的,于是考虑使用 ECharts 的包装组件。在研究了几个热门的封装组件后,发现存在依赖内置、过度封装、使用繁琐、不支持 Vue3 等问题,于是乎自己动手造轮子(>>成品传送门<<)。
Vue 3 的变化
Vue 3 并非完全颠覆性的版本,它向前兼容 Vue 2 的大部分写法。本文只对变更部分稍作介绍,详细内容请阅读官方文档。
-
- 新增组合 API(Composition API),利于拆分代码、聚合逻辑关注点;
-
- 全局 API 改由 ES 模块进行命名导出,有助于 Tree shaking 消除死代码;
import { createApp, nextTick } from 'vue';
-
render
函数不再接收h
函数,改由全局 API 获取;
import { h } from 'vue';
-
- VNode props 改为扁平结构;
// Vue 2
export default {
render: () => h('div', {
attrs: { id: 'wrapper' },
domProps: { innerText: 'Hello World' },
on: { click() {} },
}),
}
// Vue 3
export default {
render: () => h('div', {
id: 'wrapper',
innerText: 'Hello World',
onClick() {},
}),
}
-
- 生命周期选项
destroyed
与beforeDestroy
分别被重命名为unmounted
和beforeUnmount
;
- 生命周期选项
组件主体设计
我们通常采用直观的单文件组件形式进行设计,如下所示:
<template>
<div ref="chart" v-bind="$attrs" />
</template>
<script>
import echarts from 'echarts';
export default {
mounted() {
const inst = echarts.init(this.$refs.chart);
inst.setOption({ /* ... */ });
},
}
</script>
然而,单文件组件无法在运行时直接使用,需经由编译器翻译成 Vue 可识别的代码。由于 Vue 3 和 Vue 2 的差异,各自的编译器编译后的代码无法互相兼容,因此改用渲染函数替代 template,也便于进行兼容处理。
// 变量 isVue3、h 见下文“依赖内置问题”章节
export default {
render: isVue3 ? getVue3Render(h) : vue2Render,
mounted() {
const inst = echarts.init(this.$el);
inst.setOption({ /* ... */ });
},
}
function vue2Render(h) {
return h('div', {
attrs: this.$attrs,
});
}
function getVue3Render(h) {
return function () {
return h('div', {
...this.$attrs,
});
};
}
生命周期的处理
由于destroyed
与beforeDestroy
分别被unmounted
和beforeUnmount
替代,因此直接判断 Vue 版本,更改狗子名称即可。
const hooks = {
mounted() {},
beforeUnmount() {},
};
if (!isVue3) {
hooks.beforeDestroy = hooks.beforeUnmount;
delete hooks.beforeUnmount;
}
依赖内置问题
以vue-echarts
为例,源码中通过 import 导入依赖,并在 package.json 的peerDependencies
中声明依赖关系。
import echarts from 'echarts/lib/echarts'
import debounce from 'lodash/debounce'
import { addListener, removeListener } from 'resize-detector'
在某些环境中,默认情况下node_modules
中的文件会被排除在编译或打包范围之外,所以vue-echarts
包中的依赖不能正确导入,需要额外配置使其能够正常工作。如下:
// "vue.config.js"
module.exports = {
transpileDependencies: [
'vue-echarts',
'resize-detector',
],
}
当然,需要额外配置只是个小问题,比较麻烦的是ECharts 有完全版、常用版、精简版,还支持按需引入,而源码中已经把引入的版本定死了。为此,我采用依赖外置的方式,并创建函数createComponent
用于生成组件的定义对象,将外置的依赖作为函数的参数传入,保持函数的纯粹性。
/**
* Create a component
* @param {object} options
* @param {echarts} options.echarts
* @param {function} [options.h] `createElement`, required for Vue 3
* @returns {object} definition
*/
export function createComponent({ echarts, h }) {
const isVue3 = typeof h === 'function';
const hooks = { /* ... */ };
if (!isVue3) {
hooks.beforeDestroy = hooks.beforeUnmount;
delete hooks.beforeUnmount;
}
return {
render: isVue3 ? getVue3Render(h) : vue2Render,
...hooks,
};
}
测试
由于涉及同一个包的不同版本,为了能在同个项目完成测试,可通过 npm 别名来实现。(npm 版本不得小于 6.9)
# npm i <alias>@npm:<packageName>@<version>
npm i vue2@npm:vue@2
npm i vue3@npm:vue@3
然后,设置 vue 的别名为 vue2 或 vue3
// Webpack
module.exports = {
resolve: {
alias: {
vue: 'vue2',
},
},
}
// Vite
export default {
alias: {
vue: 'vue3',
},
}
注意:如果编译失败,检查node_modules
下是否存在vue
目录。当存在vue
目录时,别名不起作用,移除即可。
最后
轮子已发布 echarts-for-vue,欢迎评论、留言。
多多点赞,会变好看 👍