概述
之前搞了vue3+vite2
的实战,踩了不少坑,既然vite2
也支持vue2
并且原生支持ts,想尝试能否将vue2比较平滑过渡到vue3,看看两者之间的差异,尽量采用之前写vue3的方式来写vue2,踩踩坑,前两天给vite-plugin-components
提了两个pr支持了vue2的组件库view-ui
和element-ui
,恰巧看看真实项目中使用会不会有什么问题
项目搭建
vite2支持vue2
安装依赖:
"vite-plugin-vue2": "^1.7.2",
配置vite.config:
import { createVuePlugin } from "vite-plugin-vue2";
export default defineConfig({
plugins: [createVuePlugin()]
})
安装模板编译器
用于解析vue2文件,版本和vue2对应
"vue-template-compiler": "^2.6.14",
支持jsx/tsx
插件支持
只需要在createVuePlugin
的插件中配置一下jsx开启
createVuePlugin({
jsx: true,
})
使用时声明
.vue
文件中使用的时候要声明为<script lang="tsx">
添加声明文件
在src目录下 新建一个shim-tsx.d.ts,
// file: shim-tsx.d.ts
import Vue, { VNode } from 'vue';
import { ComponentRenderProxy } from '@vue/composition-api';
declare global {
namespace JSX {
interface Element extends VNode {}
interface ElementClass extends ComponentRenderProxy {}
interface ElementAttributesProperty {
$props: any; // specify the property name to use
}
interface IntrinsicElements {
[elem: string]: any;
}
}
}
支持ts
安装依赖
安装typescript依赖和vue编译器
"typescript": "^4.1.3",
"vue-tsc": "^0.1.7"
文件名替换ts
更改入口文件js为ts,index.html
入口文件为main.ts
<script type="module" src="/src/main.ts"></script>
添加类型声明文件
- 添加
.vue
文件ts
声明,和main.ts
同级目录新建shims-vue.d.ts,
declare module "*.vue" {
import Vue from "vue";
// eslint-disable-next-line prettier/prettier
export default Vue;
}
支持vue3特性(引入compositionApi)
安装依赖
"@vue/composition-api": "^1.0.3",
主函数引入并使用
import VueCompositionAPI from "@vue/composition-api";
Vue.use(VueCompositionAPI);
遇到的问题
这个api需要在最前面引入,因为有的依赖库也使用了compositionApi如果顺序颠倒则会报错
配置组件库
"view-design": "^4.6.1",
"element-ui": "^2.15.3",
额外依赖
由于view-ui
中有用到commonjs
的语法require,vite无法识别,需要安装一个插件解决
"@originjs/vite-plugin-commonjs": "^1.0.0-beta6",
import { viteCommonjs } from "@originjs/vite-plugin-commonjs";
export default defineConfig({
plugins: [
viteCommonjs(),
]
})
配置vite
import ViteComponents, {
ElementUiResolver,
ViewUiResolver,
} from "vite-plugin-components";
export default defineConfig({
plugins: [
ViteComponents({
dirs: ["src/views"],//扫描自定义组件的路径,可配置为相对路径 ./
customComponentResolvers: [
ViewUiResolver(),
ElementUiResolver(),
]
})
]
})
设置alias别名
配置vite.config
import path from "path";
export default defineConfig({
// 配置别名
resolve: {
alias: [
{
find: "@",
replacement: path.resolve(__dirname, "src"),
},
],
},
})
更改tsconfig.json
配置添加如下两个配置,避免ts文件中引入口提示如下错误
Cannot find module '@/hooks/useForm' or its corresponding type declarations.Vetur(2307)
{
"compilerOptions": {
//...略
"baseUrl": "src",
"paths": {
"@/*": ["./*"]
},
},
//...略
}
遇到的问题
开始配置好上述两项依旧不起作用,还是报错,重启vscode才好
vue-router
vue2只能使用vue-router3
- 安装依赖
"vue-router": "^3.5.2",
- 配置使用 和以前使用一样
import Vue from 'vue'
Vue.use(Router);
export default new Router({
// mode:'history',
routes: [
{
path: '/',
component: ()=> import("@/views/index.vue"),
},
],
});
使用pinia
安装依赖:
vue2版本只能使用pinia1,官网是这样说的:
pinia@next
install Pinia v2 for Vue 3. If your app is using Vue 2, you need to install Pinia v1:pinia@latest
and@vue/composition-api
. If you are using Nuxt, you should follow these instructions.
但是我使用了pinia@latest
这个让我选择版本,没有找到1.0的版本,应该指的就是0.x的版本吧,如果使用2.x版本就会报错
"pinia": "^0.5.2",
按需加载和引入组件库
使用vite-plugin-components
实现按需加载,在0.13.0这个版本支持了view-ui
和element-ui
,比较主流的两个组件库
安装依赖:
"vite-plugin-components": "^0.13.0",
项目中使用
//main.ts
import { createPinia, PiniaPlugin } from 'pinia'
Vue.use(PiniaPlugin)
const store = createPinia();
new Vue({
el: "#app",
router,
pinia:store,//使用了插件才会有这个属性
render: h => h(App),
});
遇到的问题
之前一般是新建一个store的文件夹然后新建一个index.ts导入后在main.ts中import后使用,但是总是提示没有引入compositionAPi,即使我在main.ts最上面已经引入了,还是报,无奈只能是将pinia也在main中引入了,不多也才一句话
项目开发中踩到的坑
tsx的vetur报错
描述: 在view-ui的table使用tsx动态渲染列的时候给一个button设置时候设置了size属性,结果提示报错如下:
Property 'size' does not exist on type 'ComponentOptions<Vue, DefaultData, DefaultMethods, DefaultComputed, PropsDefinition, DefaultProps>'.Vetur(2769)
但是运行没有任何的报错,看ts的定义size属性也是定义的非必须属性
解决: 今天更新了一下vscode发现就不报错了,挺坑的~
vue2在setup中定义tsx报错
描述: 报错信息如下:
Cannot read property '$createElement' of undefined 解决:发现render函数在setup中应该是注入不进来,查看了setup传入参数的定义没有发现render函数的定义,无奈只能写在data函数中,render函数可以成功注入进来
动态添加的属性不会立即生效
描述: 这是个老问题,只是vue3中解决了,退回到vue2就忽略了这个,向一个对象中添加一个之前没有的属性,不会响应变化
解决:使用$set
添加响应式, 例如: this.$set(item,'editing',!!item.editing)
在hooks中 使用共享数据不具备响应式
描述:类似const data = todoStore.total;
这种形式,然后再用data进行computed出其他的变量,此时在vue2中将失去响应式,但是vue3是正常的
//vue3
const data: UnwrapRef<DataItem[]> = todoStore.total;
const unfinish = computed(() =>
data.filter((item: DataItem) => item.finish != true)
);
const finishes = computed(() => data.filter((item: DataItem) => item.finish));
解决:就是直接使用store的state变量属性进行computed
//vue2
const unfinish = computed(() =>
todoStore.total.filter((item: DataItem) => item.finish != true)
);
const finishes = computed(() => todoStore.total.filter((item: DataItem) => item.finish));
pinia删除数据不触发拦截器
描述: 使用vue3的时候使用如下方式即可删除数组数据并且进入拦截器,但是在vue2的时候失效了
//vue3
const clearAll = () => {
todoStore.$state.total.length = 0; // 如果使用$reset 不触发拦截器
};
解决:经过测试发现只有调用splice才会触发pinia的拦截器 测试结果如下:
//vue2
const clearAll = () => {
// todoStore.$state.total=[]//可清空store 不走拦截器
// todoStore.$state.total.length = 0//没有作用
// Vue.set(todoStore.$state.total,'length',0)//没有作用
todoStore.$state.total.splice(0,todoStore.$state.total.length)//只能通过这种方式,触发全部删除,重置length为0 不管用
};
参考文档
代码地址
主项目 github: github.com/nabaonan/to…
vue2项目目录是:vue2-vite-ts
欢迎star~
结语
总的来说,vue2的ts版本如果配合compositionApi
和vue3
基本很接近了,vue2
的element
版本就是直接把vue3的代码粘贴过来,改动很少,由于使用了hooks
的方式,所以逻辑基本不用动,然后页面的话,element-plus和element-ui也都是一个东西,页面都能复用,只是有个小bug是popconfirm
中没有引入popover
的样式,这个在element-plus
中已经解决,element-ui
没人管了,只能自己引入一下样式解决,其他都还好。
之前听说vue2.7应该会内置compositionApi
,那样的话,vue3的代码直接复制过来连改都不用改了,但是vue2
的compositionApi
也有一些不被支持的功能,比如emit
函数就不支持,需要从外部将emit传入到hooks
函数中。
最近发现一个大佬在vue2里提了一个将vue2重构为ts的巨型pr,实在佩服这种十倍工程师的大佬,希望能早日并入,没准那时template
的ts语法提示能更进一步了~
----更新20220203-----
pinia2支持vue2了 升级依赖版本
"@vue/composition-api": "^1.4.5",
"pinia": "^2.0.11",
main函数也要做出微调,之前的PiniaPlugin
变了名称为PiniaVuePlugin
import { createPinia, PiniaVuePlugin } from 'pinia'
Vue.use(PiniaVuePlugin)