前言
为保证vue2项目更高效地迁到vue3上,因此本次的升级,vue2的选项式API和js写法保持不变。只升级框架、组件和一系列插件,对升级的这部分与vue2原来项目不兼容的写法做出修改,来保证项目在vue3框架上正常运行。
由于Vue3 的组件可以按两种不同的风格书写:选项式 API 和组合式 API。而Vue2是按照选项式 API 写法来写的。为了更快更准确的将vue2项目升级至vue3,因此在本次的升级中代码结构保持不变,后续新写的页面再可以采用组合式API的写法。
本次框架升级还加了typeScript,但由于之前vue2项目还是用js写的,因此这一部分也保持不变,后续新写的页面再可以用ts。
于是乎:
本次升级的目的就是为了将原来的vue2+webpack+element-ui升级替换为vue3+vite+element-plus+ts,做整体框架的升级优化,然后项目可以正常跑起来,不报错。
了解完之后就开始干吧~~~
第一步:升级node、vue等
1、查看node、vue版本,vue3要求node版本至少要在v18.3

关于node升级,大家可以去看这篇文章,安装nvm可以针对不同版本的项目选择对应的node版本,这样就会方便很多:nvm安装可切换不同版本nodejs_windows电脑nvm安装多版本nodejs-CSDN博客
第二步:创建项目框架
基础的项目框架搭建,可以先去这个链接:
vue3+vite+typeScript+vueRouter+vuex+axios+element-plus搭建项目框架_vue3搭建框架-CSDN博客
npm init vite@latest

1、按照命令进入项目文件中,安装包
cd vue3-vite-ts2
cnpm install
2、安装原先项目所有的插件
这里把原来的element-ui换成element-plus
//axios
cnpm install axios qs vue-router vuex --save
//sass
cnpm install sass --save-dev
//echarts element-plus @element-plus/icons moment nprogress normalize.css qs
cnpm install echarts element-plus @element-plus/icons moment nprogress normalize.css qs --save
第三步:修改配置文件及vue全局api
1、配置打包环境,最终package.json文件:
{
"name": "vue3-vite-ts2",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --mode development",
"test": "vite --mode test",
"prod": "vite --mode production",
"build:dev": "vite build --mode development",
"build:test": "vite build --mode test",
"build:prod": "vite build --mode production",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons": "^0.0.11",
"axios": "^1.4.0",
"echarts": "^5.4.3",
"element-plus": "^2.3.9",
"moment": "^2.29.4",
"normalize.css": "^8.0.1",
"nprogress": "^0.2.0",
"qs": "^6.11.2",
"vue": "^3.3.4",
"vue-router": "^4.2.4",
"vuex": "^4.1.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.2.3",
"sass": "^1.74.1",
"typescript": "^5.0.2",
"vite": "^4.4.5",
"vue-tsc": "^1.8.5"
}
}
创建三个文件,分别为开发(development)、测试(test)、正式(production)三个打包环境文件,与原来vue2写法是不一样的,vue3写法:

注意:获取环境配置的方法也变了,记得全局搜索,全局替换:
process.env.VITE_APP_baseUrl 换成 import.meta.env.VITE_APP_baseUrl
2、修改 vite.config.ts 配置文件:
添加配置别名@,添加请求接口代理,添加打包优化
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: { // 配置别名
'@': path.resolve(__dirname, './src')
}
},
server: {
open: true,
host: 'localhost',
port: 9000,
hmr: { overlay: false },
proxy: {
'/basePath': {
target: '你的ip',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/basePath/, ''),
}
},
},
build: {
rollupOptions: {
output: {
chunkFileNames: 'js/[name]-[hash].js', // 引入文件名的名称
entryFileNames: 'js/[name]-[hash].js', // 包的入口文件名称
assetFileNames: '[ext]/[name]-[hash].[ext]', // 资源文件像 字体,图片等
// 最小化拆分包 node_modules的包逐个打包 将需要分离的包单独的打包出来
manualChunks(id) {
if (id.includes('node_modules')) {
return id.toString().split('node_modules/')[1].split('/')[0].toString();
}
}
}
}
}
})
//tsconfig.json文件里:
"compilerOptions": {
...
"baseUrl": ".",
"paths": {
"@/*":["src/*"],
},
}
3、开始修改文件啦,把之前的代码都移植过来,包括src里面的直接拷过来
1)main.ts文件:
Vue2使用全局API来注册插件、混入、指令等,即通过Vue.use、Vue.mixin、Vue.directive等方法。这种方式有一些问题,比如可能造成命名冲突,也不利于模块化和按需加载。
Vue3使用局部API来注册插件、混入、指令等,即通过createApp方法创建一个应用实例,然后通过app.use、app.mixin、app.directive等方法。这种方式可以避免全局污染,也更符合模块化的思想。
按照下面这个对全局的api修改:

Vue.use 替换为:app.use
Vue.prototype 替换为: app.config.globalProperties
Vue.component 替换为: app.component
一通改。。。
有些需要特别改动的比较费事的先注掉,比如自定义指令,自定义组件等,等项目先跑起来,后面再慢慢改。
2)、有引用 element-ui 的替换成 element-plus
import { message, messageBox } from 'element-ui' 替换为
import { ElMessage, ElMessageBox } from 'element-plus'
改完后的main.ts文件:
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
//
import 'normalize.css/normalize.css' // A modern alternative to CSS resets
import '../theme/index.css'
import './fonts/iconfont.css'
//ElementPlus
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
//el-icon
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
app.use(ElementPlus)
//router
import router from './router'
app.use(router)
//store
import store from './vuex/index'
app.use(store)
// axios
import axios from './axios/api'
import '@/axios/permission'
// websocket
import wsConnection from '@/axios/websocket.js'
app.config.globalProperties.$setWs = wsConnection
// //按钮权限
// import './utils/directives/install'
// import { hasBtnPermission } from './utils/directives/button/bthIsShowOrElse' // button permission
// app.config.globalProperties.hasPerm = hasBtnPermission
//loading 全局加载
import loading from './components/common/loading/loading.js' // 引入loading
app.use(loading) // 全局使用loading
// message 全局提示框
import message from './components/common/message/message.js'
app.config.globalProperties.$messageBlock = message
// moment 全局时间处理
import moment from 'moment'
app.config.globalProperties.$moment = moment
// myTooltip 全局文字溢出隐藏 hover显示文字提示
import myTooltip from '@/components/common/myTooltip/index.vue'
app.component('myTooltip', myTooltip) //全局自定义组件
app.mount('#app')
3)、vuex引入的index.js 也需要改
import { createStore } from 'vuex'
//
import getters from './getters'
import user from './modules/user'
import sidebar from './modules/sidebar'
//
const store = createStore({
modules: {
user,
sidebar,
},
getters,
})
export default store
4)、router文件中的index.js也需要改
import { defineAsyncComponent } from 'vue';
import { createRouter, createWebHistory } from 'vue-router'
const index = defineAsyncComponent(() => import('@/pages/index.vue'))
// login
const superLogin = defineAsyncComponent(() => import('@/pages/login/superLogin.vue'))
//404
const notfonud404 = defineAsyncComponent(() => import('@/pages/error/404.vue'))
const routes = [
{
name: "superLogin",
path: "/login",
component: superLogin
},
{
name: "404",
path: '/404',
component: notfonud404
},
{
path: '/index',
name: '首页',
redirect: '',
component: index,
children: [
]
},
{
// 匹配所有路径 vue2使用* vue3使用/: pathMatch(.*)* 或 /: pathMatch(.*) 或 /:
catchAll(.*)
path: '/:catchAll(.*)*',
redirect: '/404',
},
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router
5)、app.vue文件 替换成你原来的文件 就行
6)、ok , npm run dev 启动项目吧,算是启起来了
接下来,开始更艰难的一步:一个页面一个页面的看着改吧:
第四步:一个页面一个页面过,一个一个问题改
1、插槽及图标修改
//vue2 element-ui写法:
<el-input name="mobile">
<i slot="prefix" class="el-input__icon el-icon-mobile"></i>
</el-input>
<template slot="error" slot-scope="{ error }">
//
</template>
//vue3 element-plus写法:
<el-input name="mobile" >
<template #prefix>
<el-icon :size="16"><Iphone /></el-icon>
</template>
</el-input>
<template #error="{ error }">
//
</template>
类似于这样的:
slot="prefix" 改成 #prefix
slot="error" slot-scope="{ error }" 改成 #error="{ error }"
全局所有的插槽要注意啦:
原来是可以直接在标签上写slot,现在不行了,必须写在 <template>标签上
(1)全局搜索 slot-scope="scope" 直接全局替换 成 #default="scope"
(2)全局搜索 slot="content" 逐个改成 <template #content>的写法
(3)全局搜索 slot="append" 逐个检查 替换成 <template #append>的写法
(4)全局搜索 slot="footer" 逐个检查 替换成 <template #footer>的写法
等等。。。一通改
2、el-menu菜单修改:
elsub-menu 改成 el-sub-menu,包括样式里classname中的
3、图片引入require报错:
换成import引入方式
4、主题色修改:
原来:element-ui采用主题色更换的方式是在线定制主题色工具,直接下载下来引入项目中

现在:element-plus没找到在线定制主题色工具,因此需要修改下:

然后在 vite.config.ts:全局引入sass定义的所有变量

preprocessorOptions: {
// 全局sass变量引入
scss:{
additionalData: '@use "./src/assets/css/commonCss/globalVariate/index.scss" as *;',
}
},
至此主题色生效
项目中样式我分为两大类,一类是公共样式,公共样式里分为全局变量和公共需要的样式,一类是页面样式。
5、各种组件样式修改:
1)全局配置中文

2) el-dialog修改
element-ui写法:

element-plus写法:

还有头部、脚部的插槽方式修改
custom-class 也要弃用了,换成class:全局搜索,全局修改

3) el-pagination修改:
element-ui写法:

element-plus写法:

4) el-descriptions修改:
element-ui写法:

element-plus写法:

5) el-date-picker修改:
element-ui写法:

以前这种写法,日期选择是可以正确显示的,但是现在老是只能显示选择的时间的1号,后来才发现是格式写的不对,全局改下
element-plus写法:

6) el-radio修改:
element-ui写法:


element-plus写法:

6、$set报错:
直接改成正常的赋值写法就行 ,不用$set,vue3已经废弃$set啦
7、生命周期钩子函数修改:
Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名了,需要修改下:
beforeDestroy 改为 beforeUnmount
destroyed 改为 unmounted
8、过度类名的更改:
// vue2写法:
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
//vue3写法:
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
9、全局样式因为组件结构有变动,一个个修改
第五步:各种报错修改记录
1、报错:Invalid VNode type: undefined (undefined)
在vue3中组件被多次注册就会报Invalid VNode type: undefined (undefined)的错,导致页面空白。 解决方式:引入 defineAsyncComponent 异步加载组件
import {defineAsyncComponent} from 'vue';
components: {
you:defineAsyncComponent(()=>import('@/views/you.vue'))
},
全局搜索,全局修改
2、报错:[Element Warn][TableColumn]Comparing to render-header, scoped-slot header is easier to use. We recommend users to use scoped-slot header.
render-header改成scoped-slot
全局搜索,全局修改
3、报错:type.text is about to be deprecated in version 3.0.0, please use link instead.
button按钮的type="text"要废弃,改成link
全局搜索,全局修改
4、报错:Vue Router warn The “next“ callback was called more than once in one navigation guard

出现这个问题,检查next方法是不是写了两次 ,执行了两次,去掉一个

第六步:整理不兼容/新增特性等修改清单
官网vue3迁移地址:Vue 3 迁移指南

两者作用于同一个元素上时,v-if 会拥有比 v-for 更高的优先级。
v-on 的 .native 修饰符已被移除。
//Vue3
import { defineAsyncComponent } from 'vue'
const asyncModal = defineAsyncComponent(() => import('./Modal.vue'))
destroyed生命周期选项被重命名为unmountedbeforeDestroy生命周期选项被重命名为beforeUnmount
过渡类名 v-enter 修改为 v-enter-from、过渡类名 v-leave 修改为 v-leave-from。
被移除的 API
- keyCode 作为 v-on 修饰符的支持
- $on、$off 和 $once 实例方法
- 过滤器 (filter)
- 内联模板 attribute
- $children 实例 property
- propsData 选项
$destroy实例方法。用户不应该再手动管理单个 Vue 组件的生命周期。- 全局函数
set和delete以及实例方法$set和$delete。基于代理的变化检测已经不再需要它们了。
这里放一个别人写的修改清单,感觉清晰明了,觉得后面用的到:
至此就结束啦,上面这些就是做升级做的一个笔记记录,最后就剩下一个页面一个页面测试检查,有问题再细改,到这里基本页面及操作都能正常,不会报错,就是还有一些警告,关于echarts的一些api废弃警告之类的,后面继续再改,有啥问题再继续记录,over~~~
2024-9-12记录:
好长时间没打开了,今天启动这个项目,直接报错:
Cannot find module @rollup/rollup-win32-x64-msvc. npm has a bug related to optional dependencies (https://github.com/npm/cli/issues/4828). Please try `npm i` again after
removing both package-lock.json and node_modules directory.
于是就按照提示 删除package-lock.json and node_modules,但是无用,最后还是看了这个博客,才解决问题。就是从新设置了下淘宝镜像源,不知道啥原因。记录下吧~~~
本文指导如何将基于vue2、webpack和element-ui的项目升级到vue3、vite、element-plus和typescript,涉及版本更新、配置修改、API调整和组件替换等内容,确保项目在Vue3框架上正常运行。
3750

被折叠的 条评论
为什么被折叠?



