vue3 升级步骤记录(vue-cli 相关)
本次 vue3 升级记录主要针对使用 vuecli 生成的项目主要针对:依赖包、vue.config.js 配置、.eslint.js、路由、store、国际化、main.js 等所涉及到的变更记录。
package.json
{
"dependencies": {
"element-plus": "^1.0.2-beta.32",
"vue": "^3.0.5",
"vue-i18n": "^9.0.0-rc.6",
"vue-router": "^4.0.1",
"vuex": "^4.0.0-rc.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.5.11",
"@vue/cli-service": "^4.5.11",
"@vue/compiler-sfc": "^3.0.5",
"eslint": "^7.0.0",
"eslint-plugin-vue": "^7.6.0",
"vue-loader-v16": "^16.0.0-beta.5.4"
}
}
删除 element-ui 替换成 element-plus , vue-loader-v16 需要重新添加, 其他的包只需要升级即可。
vue.config.js
vue-cli3.x vue.config.js 需要更改为如下:
{
// baseurl 修改为 publicPath
publicPath: isProduction ? './' : '/'
css: {
// modules 修改为 requireModuleExtension
requireModuleExtension: true
}
}
.eslintrc.js
eslint 所涉及到校验规则也需要进行升级
eslint-plugin-vue
{
extends: {
'standard',
'prettier',
'prettier/standard',
"plugin:vue/essential",
"plugin:vue/strongly-recommended",
'plugin:vue/recommended'
}
}
替换成
{
extends: {
'standard',
'prettier',
'prettier/standard',
'plugin:vue/vue3-essential',
'plugin:vue/vue3-strongly-recommended',
'plugin:vue/vue3-recommended'
}
}
路由
vue-router 所涉及到只需要将 api 更换下即可
初始化
src/router/index.js
vue2.0
import Vue from 'vue'
import VueRouter from 'vue-router'
//...
Vue.use(VueRouter)
export function initRouter() {
let router = new VueRouter({
scrollBehavior: () => ({
y: 0,
}),
routes: constantRouterMap,
})
//...
return router
}
vue3.0
import { createRouter, createWebHashHistory } from 'vue-router'
import routes from './routes'
import store from '@/store'
// 导出路由 在 main.js 里使用
const router = createRouter({
history: createWebHashHistory(),
scrollBehavior: () => ({ y: 0 }),
routes,
})
/**
* 路由拦截
* 权限验证
*/
router.beforeEach((to, from, next) => {
next()
})
router.afterEach((to, from) => {
store.dispatch('setPageTip', '')
})
export default router
页面引入方式(动态加载)
vue2.0
{
path: '/404',
component: resolve => require(['@/views/404'], resolve),
hidden: true
}
vue3.0
{
path: '/404',
component: () => import('@/views/404'),
hidden: true
}
默认页面配置
vue2.0
{ path: '*', redirect: '/404', hidden: true }
vue3.0
{
path: '/:pathMatch(.*)*',
redirect: '/404',
hidden: true
}
store
vue-router 所涉及到只需要将 api 更换下即可
src/store/index.js
vue2.0
import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app/index'
// ...
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
app,
// ...
},
getters,
})
if (process.env.NODE_ENV === 'development') {
Object.assign(store, {
plugins: [createLogger()],
})
}
export default store
vue3.0
import { createStore } from 'vuex'
// modules
import app from './modules/app'
import demo from './modules/demo'
// getters
import getters from './getters'
const storeTree = {
modules: {
app,
demo,
},
getters,
}
const store = createStore(Object.assign({}, storeTree))
export default store
main.js
src/main.js
vue2.0
import Vue from 'vue'
import moment from 'moment'
import axios from 'axios'
import App from './App'
import router from './router'
import store from './store'
import ElementUI from 'element-ui'
import '@/framework/styles/index.scss'
import { getI18n } from './lang'
import utils from '@/framework/utils/common'
import apis from '@/apis'
import '@/framework/components'
Object.defineProperty(Vue.prototype, '$moment', { value: moment })
Object.defineProperty(Vue.prototype, '$utils', { value: utils })
Object.defineProperty(Vue.prototype, '$apis', { value: apis })
const isProduction = process.env.NODE_ENV === 'production'
//...
Vue.config.productionTip = false
const glbalFilePath = isProduction
? 'static/global-config.json'
: 'static/global-config-dev.json'
axios.get(glbalFilePath).then((res) => {
let i18n = getI18n(res.data['LANGUAGE'])
window.ajaxBaseUrl = res.data['BASE_URL']
//...
Vue.use(ElementUI, {
size: 'medium',
i18n: (key, value) => i18n.t(key, value),
})
Vue.prototype.g_Config = res.data
axios.setConfig(Vue.prototype.g_Config)
/* eslint-disable no-new */
new Vue({
router,
store,
i18n,
render: (h) => h(App),
}).$mount('#app')
})
vue3.0
// 1.
import { createApp } from 'vue'
import moment from 'moment'
import axios from 'axios'
// 2.
import ElementPlus from 'element-plus'
import router from './router'
import store from './store'
import App from './App'
import '@/framework/styles/index.scss'
import { getI18n } from './lang'
import utils from '@/framework/utils/common'
import apis from '@/apis'
import initGlobalComponents from '@/framework/components'
import defaultConfig from '../public/static/global-config.json'
// 3.
const app = createApp(App)
const isProduction = process.env.NODE_ENV === 'production'
// 4.
app.config.globalProperties.$moment = moment
app.config.globalProperties.$utils = utils
app.config.globalProperties.$apis = apis
// 5.
// router
app.use(router)
// vuex
app.use(store)
// 6.
// 全局组件
initGlobalComponents(app)
// 设置 baseURL
axios.setConfig = function (config) {
axios.defaults.baseURL = config.BASE_URL
axios.defaults.timeout = config.AJAX_TIMEOUT
}
const glbalFilePath = isProduction
? 'static/global-config.json'
: 'static/global-config-dev.json'
axios.get(glbalFilePath).then((res) => {
res.data = Object.assign(defaultConfig, res.data)
//...
let i18n = getI18n(res.data['LANGUAGE'])
window.ajaxBaseUrl = res.data['BASE_URL']
app.use(i18n)
app.use(ElementPlus, {
size: 'medium',
i18n: i18n.global.t,
})
app.config.globalProperties.g_Config = res.data
axios.setConfig(res.data)
// 7.
app.mount('#app')
})
vue 语法修改
组件注册
// import { createApp } from 'vue'
// const app = createApp(App)
import TablePopover from './TablePopover'
import Textbox from './Textbox'
const initLocalComponents = (app) => {
app.component('table-popover', TablePopover)
app.component('text-box', Textbox)
}
export default initLocalComponents
组件实例内部方法
vue2.0
Object.defineProperty(Vue.prototype, '$moment', { value: moment })
vue3.0
app.config.globalProperties.$moment = moment
eventBus
在 2.x 中,Vue 实例可以用事件 API($on
、$off
和 $once
)强制附加的处理代码逻辑, 但是在 3.x 中这些 api 已经废弃, 项目升级时需要官方推荐的是第三方库
src/framework/eventBus.js
vue2.0
import Vue from 'vue'
export default new Vue()
vue3.0
官方推荐的库:
mitt
tiny-emitter
import mitt from 'mitt'
const emitter = mitt()
emitter['$on'] = emitter.on
emitter['$emit'] = emitter.emit
emitter['$off'] = emitter.off
export default emitter
element-plus 语法修改
styles
涉及到 element-ui 的引用均要替换成 element-plus
styles/index.scss
vue2.0
// ...
/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
/* element默认主题 */
@import '~element-ui/packages/theme-chalk/src/index';
// ...
vue3.0
// ...
/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-plus/lib/theme-chalk/fonts';
/* element默认主题 */
@import '~element-plus/packages/theme-chalk/src/index';
// ...
js 内部引入 element-plus 组件
src/utils/http.js
vue2.0
import axios from 'axios'
import { Message } from 'element-ui'
// ...
vue3.0
import axios from 'axios'
import { ElMessage } from 'element-plus'
// ...
lang/index.js(国际化)
vue2.0
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import Cookies from 'js-cookie'
import elementEnLocale from 'element-ui/lib/locale/lang/en' // element-ui lang
import elementZhLocale from 'element-ui/lib/locale/lang/zh-CN' // element-ui lang
import enLocale from './en'
import zhLocale from './zh'
Vue.use(VueI18n)
const messages = {
en: {
...enLocale,
...elementEnLocale,
},
zh: {
...zhLocale,
...elementZhLocale,
},
}
let i18n = {}
export function getI18n(locale) {
const vueI18n = new VueI18n({
locale: locale || Cookies.get('lang') || 'zh',
messages,
})
/*eslint-disable*/
i18n.__proto__ = vueI18n.__proto__
i18n = Object.assign(i18n, vueI18n)
return i18n
}
export default i18n
vue3.0
// 1
import { createI18n } from 'vue-i18n'
import Cookies from 'js-cookie'
// 2
import elementEnLocale from 'element-plus/lib/locale/lang/en'
import elementZhLocale from 'element-plus/lib/locale/lang/zh-cn'
import enLocale from './en'
import zhLocale from './zh'
const messages = {
en: {
...enLocale,
...elementEnLocale,
},
zh: {
...zhLocale,
...elementZhLocale,
},
}
let i18n = {}
export function getI18n(locale) {
// 3
const vueI18n = createI18n({
locale: locale || Cookies.get('language') || 'zh',
messages,
})
/*eslint-disable*/
i18n.__proto__ = vueI18n.__proto__
i18n = Object.assign(i18n, vueI18n)
return i18n
}
export default messages
/deep/ 语法
vue2 中 覆盖子组件的方法有 /deep/ 、»> 、::v-deep, 其中 ::v-deep 用法
.parent {
::v-deep .child {
/* ... */
}
}
但是在 vue3 写法是不同的
.parent ::v-deep(.child) {
/* ... */
}
styles 文件内部全局 css
scope style
<style lang="scss" scoped>
</style>
问题记录
问题 1
/deep/ 与 »> 已经废弃 需要是用 :deep(代替)
参考文档
问题 2
问题 3
vue2.0
{ path: '*', redirect: '/404', hidden: true }
vue3.0
{
path: '/:pathMatch(.*)*',
redirect: '/404',
hidden: true
}
问题 4
问题 5
修正后
问题 6
slot => #slotName 或 v-slot=”slotName”
问题 7
error ’.native’ modifier on ‘v-on’ directive is deprecated
问题 8
error The beforeDestroy
lifecycle hook is deprecated. Use beforeUnmount
instead
问题 9
warning The “tab:change” event has been triggered but not declared on emits
option vue/require-explicit-emits
https://v3.cn.vuejs.org/guide/migration/emits-option.html#_2-x-behavior