Vue2 降级 Vue3 实际
一、降级前筹备
在正式降级前,能够提前解决一些已兼容的小批改以及被移除的 API。
deep 款式穿透
历史所有的 >>>
|| /deep/
|| ::v-deep
款式穿透,更改为 :deep()
。
在 Vue 2.7 中,运行我的项目会提醒 deep
相干问题,尽管我的项目还能失常启动,但会在控制台报正告信息。
// 历史写法
/deep/ .el-card__body {padding: 20px 20px 0;}
// 新版写法
:deep(.el-card__body){padding: 20px 20px 0;}
inline-template 属性
移除 inline-template
标识,在 Vue 3 中,inline-tempalte
属性将会被移除,不再反对该用法了,如果必须应用可用 <script>
或者默认 Slot
代替。
slot 插槽
Vue 3 中引入了一个新的指令 v-slot
,用来示意具名插槽和默认插槽(Vue2.6 中已反对)。如果在我的项目中依然应用废除的具名 / 作用域插槽语法,请先将其更新至最新的语法。
v-bind
在 Vue 2 中,如果一个元素同时定义了 v-bind="object"
和一个雷同的独自的属性,那么这个独自的属性总是会笼罩 object
中的绑定。
在 Vue 3 中,v-bind=“object"
是程序敏感的,申明绑定的程序决定了它们如何合并,需确保 v-bind
先定义,再定义各个属性。
// Vue 2
<div id="aaa" v-bind="{id:'bbb'}"></div>
<div id="bbb"></div> // 渲染后果
// Vue 3
<div id="aaa" v-bind="{id:'bbb'}"></div>
<div id="bbb"></div> // 渲染后果
<div v-bind="{id:'bbb'}" id="aaa"></div>
<div id="aaa"></div> // 渲染后果
v-for
筛查所有 v-for
中应用 ref
的中央,将 ref
绑定为一个函数。
在 Vue 2 中,在 v-for
语句中应用 ref
属性时,会生成 refs
数组插入 $refs
属性中。
在 Vue 3 中,在 v-for
语句中应用 ref
属性时,将不再会主动在 $refs
中创立数组。而是将 ref
绑定到一个 function
中,在 function
中能够灵活处理 ref
。
keyCodes
在 Vue 2 中,应用数字 (即键码) 作为 v-on
的修饰符。
在 Vue 3 中,弃用了 keyCodes,能够更改为别名作为 v-on
的修饰符(Web 规范中 KeyboardEvent.keyCode 已被废除)。
<input v-on:keyup.13="submit" /> // 已弃用
<input v-on:keyup.enter="submit" />
Data 选项
在 Vue 2 中,申明 data
反对对象模式 || 函数模式。
在 Vue 3 中,对 data
的申明进行了标准化,只反对函数模式申明。
filters 过滤器
在 Vue 3 中,移除且不再反对 filters
,如果之前我的项目中须要实现过滤性能,能够通过 computed
或 methods
实现。
如果须要应用全局过滤器,能够借助 globalProperties
来注册全局过滤器。
// lib/format.js
export default {formatCooperateStatus (status) {
const map = {
applied: '待声援',
assigning: '指派中',
partialRefuse: '从新指派',
process: '合作中',
refuse: '已退回',
tested: '合作实现'
}
return map[status] || ''
}
}
// main.js
// Vue 2
import Format from './lib/format.js'
Vue.prototype.$format = Format
// Vue 3
import Format from './lib/format.js'
const app = createApp(App)
app.config.globalProperties.$format = Format
watch 监听器
筛查所有 watch
监听,如果监听参数为数组,须要设置 deep: true
。
在 Vue 3 中,只有当数组被替换时,回调才会触发,如果想要数组在产生扭转时被 Vue 辨认到,则必须指定 deep
选项。
watch: {
checkedMethod: {handler (curValue) {this.onChange(curValue)
},
deep: true,
immediate: true
}
}
$children
在 Vue 3 中,移除了 $children
实例,如果须要拜访子组件实例可用 $refs
实现。
$destroy
在 Vue 3 中,移除了 $destroy
实例,不应该手动治理单个 Vue
组件的生命周期。
二、构建工具
从 Vue CLI 迁徙为 Vite,Vue 3 举荐应用 Vite
作为构建工具,Vite
缩短了开发服务器的启动工夫,它实现了按需编译,不再须要期待整个利用编译实现。
PS: Vite 须要 Node.js 版本 >= 12.0.0
迁徙过程
1. 装置
npm i vite -g
2. 构建新的 vite 我的项目
npm create vite@latest
cd xxx
npm install
npm run dev
构建的初始我的项目构造如下:
用原始我的项目的 src
文件替换新构建我的项目的 src
目录,而后再持续接下来的重构操作。
3. 迁徙 package 文件及配置项
依据我的项目需要迁徙 package.json 文件
,并移除 Vue CLI
相干依赖项。
初始文件如下,Vite
默认构建的我的项目为 Vue 3 我的项目。
批改 vite.config.js
文件,vue.config.js –> vite.config.js
初始文件配置如下
批改文件配置,例:
import {resolve} from 'path'
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({plugins: [vue()],
resolve: {
alias: { // 配置门路别名
'@': resolve('src')
},
},
server: {
open: true,
host: XXX,
port: XXX,
https: false,
proxy: {
'/api': {
target: XXX,
secure: false,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
'/casefiles': {
target: XXX,
secure: false,
changeOrigin: true
}
})
4. 更新环境变量
process.env
–> import.meta.env
Vite
在一个非凡的 import.meta.env
对象上裸露环境变量。
5. 文件引入
@vue/cli
中反对无扩展名的 vue 文件导入,加不加 .vue
后缀都会被正确辨认。
在 Vite
的设计中,import xxx from "./xxx.vue"
能力正确导入,必须确保单个文件组件的所有导入都以扩展名结尾。
6. 批改 CommonJS 语法
Vite
应用 ES Modules
作为模块化计划,因而不反对应用 require
形式来导入模块。
三、HTML 文件与入口文件
1. index.html
./public/index.html
–> ./index.html
Vite
我的项目的 HTML 文件
是放在我的项目根目录下的,同时在 Vite
中,JavaScript
应用程序不再是主动注入的,main.js
文件须要手动引入。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
2. main.js
创立并挂载根实例
创立实例,new Vue
–> createApp()
挂载形式,$mount()
–> mount()
增加全局属性和办法,Vue.prototype
–> app.config.globalProperties
// Vue 2
import Vue from 'vue'
import 'element-ui/lib/theme-chalk/index.css'
import ElementUI from 'element-ui'
import App from './App'
import router from './router'
import Vuex from 'vuex'
import store from './store'
import '@/assets/styles/common.scss'
import '@/assets/styles/main.scss'
import '@/assets/fonts/iconfont.css'
import './lib/filters.js'
import Format from './lib/format.js'
Vue.prototype.$format = Format
Vue.config.productionTip = false
Vue.use(ElementUI)
Vue.use(Vuex)
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
// Vue 3
import {createApp} from 'vue'
import App from './App.vue'
import 'element-plus/dist/index.css'
import ElementPlus from 'element-plus'
import locale from 'element-plus/lib/locale/lang/zh-cn'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import router from './router'
import store from './store'
import '@/assets/styles/common.scss'
import '@/assets/styles/main.scss'
import '@/assets/fonts/iconfont.css'
import Format from './lib/format.js'
const app = createApp(App)
app.config.globalProperties.$format = Format
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component)
}
app.use(ElementPlus, { locale} ).use(router).use(store)
app.mount('#app')
四、语法和 API
以下只是简略例举了几个罕用的变动,对于语法和 API 的具体变更能够参见 Vue 官网文档:Vue2 迁徙
v-model
排查没有修饰符的 v-model
,别离将 prop
和 event
命名更改为 modelValue
和 update:modelValue
。
v-bind
的 .sync
修饰符和组件的 model
选项已移除,更改为 v-model:value
。
<template\>
移除没有指令的 <template>
,Vue 3 对组件的写法做了调整,反对多个根节点。
在 Vue 2 中,<template>
标签不能增加 key
属性,而是在其子元素上增加 key
。
在 Vue 3 中,key
应该被设置在 <template>
标签上。
// Vue 2
<template v-for="item in list">
<span :key="item.value">...</span>
</template>
// Vue 3
<template v-for="item in list" :key="item.id">
<span>...</span>
</template>
key
在 Vue 2 中,倡议在 v-if
/ v-else
/ v-else-if
上增加 key
。
在 Vue 3 中,Vue 会主动生成惟一 key
(仍旧承受历史写法,然而不再被举荐应用)。
// Vue 2
<div v-if="isAdmin" key="aaa"> aaa </div>
<div v-else key="bbb"> bbb </div>
// Vue 3
<div v-if="isAdmin"> aaa </div>
<div v-else> bbb </div>
v-if && v-for
v-if
和 v-for
在同一个元素身上应用时的优先级产生了变动。
在 Vue 3 中,如果在一个元素上同时应用 v-if
和 v-for
,v-if
的优先级高于 v-for
(不举荐这样应用)。
functional
在 Vue 3 中,{functional: true}
选项已从通过函数创立的组件中移除,移除配置中 {functional: true}
和模板中 <template functional>
的 functional
。
defineAsyncComponent 辅助函数
在 Vue 3 中异步组件通过 defineAsyncComponent
办法创立。
// Vue 2
const asyncComponent = () => import('./async-component.vue')
// Vue 3
const asyncComponent = defineAsyncComponent(() => import('./async-component.vue'))
defineAsyncComponent
中 component
选项更名为 loader
,同时 loader
函数移除了 resolve
和 reject
参数,必须手动返回 Promise
。
// Vue 2
const asyncComponent = (resolve, reject) => {...}
// Vue 3
const asyncComponent = defineAsyncComponent(() =>
new Promise((resolve, reject) => {...})
)
.native 修饰符
在 Vue 3 中,移除了 v-on: event.native
修饰符。(PS: Vue 3 中新增 emits
选项,所有未在组件 emits
选项中定义的事件作为原生事件增加到子组件的根元素中,除非子组件选项中设置了 inheritAttrs: false
)
// Vue 2
<my-component
v-on:close="handleComponentEvent"
v-on:click.native="handleNativeClickEvent"
/>
// Vue 3
<my-component
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
/>
// MyComponent.vue
<script>
export default {emits: ['close']
}
</script>
$scopedSlots
在 Vue 3 中,移除了 $scopedSlots
,对立了 $scopedSlots
和 $slots
,所有插槽都通过 $slots
作为函数裸露。
$listeners
在 Vue 3 中,曾经弃用 $listeners
对象,将事件监听器并入 $attrs
,作为 $attrs
的一部分。
$attrs
在 Vue 3 中,$attrs
将蕴含传递给组件的所有属性,包含 class
和 style
属性。
$set() 和 $delete()
在 Vue 2 中,批改某一些数据,视图是不能及时从新渲染的,因而提供了一些变异的办法,比方 $set
、$delete
。
在 Vue 3 中,移除了 $set
和 $delete
,基于代理的变化检测曾经不再须要它们了。
<el-input v-model="form.no" placeholder="请输出编号"></el-input>
// Vue 2
export default {data() {
return{
form:{no:'11'}
}
}
mounted () {
this.form.no = 'CaseNo' // 视图不变
this.$set(this.form, 'no', 'CaseNo') // 视图更新
}
}
// Vue3
export default {data() {
return{
form:{no:'11'}
}
}
mounted() {this.form.no = 'CaseNo' // 视图更新}
}
Events API
在 Vue 3 中,移除了 $on
、$off
和 $once
这三个事件相干的 API,不再反对事件发射器接口,能够应用内部库来实现事件总线,例如 mitt
。
// @/lib/bus.js
import mitt from 'mitt'
const bus = new mitt()
export default bus
// 应用
import Bus from '@/lib/bus'
export default {mounted () {Bus.off('loadMore')
Bus.on('loadMore', this.initActivity)
}
}
Mixin
在 Vue 2 中,data
的合并是深拷贝模式。
在 Vue 3 中,当组件的 data
与 mixin
或 extends
的 data
进行合并时,只进行浅拷贝。
watch 监听器
在 Vue 3 中,watch
不再反对点分隔字符串门路,能够将监听的参数更改为 computed
。
Composition API
Options API
–> Composition API
在 Vue 2 中,应用的是 Options API
,代码逻辑比拟扩散,可读性差,可维护性差。
在 Vue 3 中,应用的是 Composition API
,代码逻辑明显,可维护性更高。
因为波及到所有页面和组件,批改起来变更过大,同时目前 Vue 3 也是兼容了 Options
写法,所以这部分代码构造能够先不必更改。之后新增的页面和组件能够依照 Vue 3 新增的 composition API
构造来写,其余内容能够前期逐渐批改。
详情请参考 Vue 3 官网文档
五、生命周期
vue2 | vue3 (Options) | vue3 (Composition) |
---|---|---|
beforeCreate | beforeCreate | setup() 外部 |
created | created | setup() 外部 |
beforeMount | beforeMount | onBeforeMount |
mounted | mounted | onMounted |
beforeUpdate | beforeUpdate | onBeforeUpdate |
updated | updated | onUpdated |
beforeDestroy | beforeUnmount | onBeforeUnmount |
destroyed | unmounted | onUnmounted |
六、路由
Vue 2 应用的是 router3.x
的 API,换成 Vue3 须要用 router4.x
的 API。
Vue Router 从 v3 到 v4 在迁徙的过程中可能改变中央比拟多,这里只是简略例举了几个常见的变动,详情能够参考 官网文档。
1. 装置
npm install vue-router@4
2. new Router 变成 createRouter
new Router()
–> createRouter 函数
3. 破除了 mode 选项配置
history
–> createWebHistory
hash
–> createWebHashHistory
abstract
–> createMemoryHistory
// v3
import Router from 'vue-router'
Vue.use(Router)
const router = new Router({
mode: 'history',
routes
})
export default router
// v4
import {createRouter, createWebHistory} from 'vue-router'
const router = createRouter({history: createWebHistory(),
routes
})
export default router
4. 勾销(*)通配符路由
// v3
{
path: '*',
name: '404',
component: NotFound,
meta: {title: '404'}
}
// v4
{path: '/:pathMatch(.*)*',
name: '404',
component: NotFound,
meta: {title: '404'}
}
5. 新增 useRoute、useRouter
路由信息,this.$route
–> useRoute()
操作路由,this.$router
–> useRouter()
// Options 写法
this.$router.push({path: this.$route.path, query: query})
// Composition 写法
import {useRoute, useRouter} from 'vue-router'
export default defineComponent ({setup(props, ctx) {const route = useRoute()
const router = useRouter()
Router.push({path: Route.path, query: query})
}
})
七、状态治理
1. 装置
npm install vuex@next
2. 创立 store 对象并导出 store
new Vuex.Store()
–> createStore()
// store/index.js
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({state: {},
mutations: { },
actions: {}})
import {createStore} from 'vuex'
export default createStore({state: {},
mutations: { },
actions: {}})
// main.js
...
import store from './store'
...
app.use(store)
3. 获取 store 实例对象
this.$store
–> useStore()
// Options 写法
const user = this.$store.user
// Composition 写法
import {useStore} from 'vuex'
export default defineComponent ({setup(props, ctx) {const Store = useStore()
const user = Store.state.user
}
})
八、我的项目依赖
将我的项目中所应用到的 UI 框架 和 第三方插件 切换成对应的 Vue 3 版本,相应用法可能也须要变更。
Element UI 降级为 Element Plus
PS: Element UI 降级为 Element Plus 后,可能会产生很多破坏性的更改,界面款式须要从新调整。
以下简略例举了几个常见的变动,详情能够参考 官网文档。
Icon 图标
// Element UI
<i class="el-icon-plus"></i>
// Element Plus
<el-icon>
<Plus />
</el-icon>
Dialog 对话框
:visible.sync
–> v-model
// Element UI
<el-dialog title="提醒" :visible.sync="dialogVisible" :before-close="handleClose">
...
</el-dialog>
// Element Plus
<el-dialog v-model="dialogVisible" title="提醒" :before-close="handleClose">
...
</el-dialog>
Pagination 分页
:current-page.sync
–> v-model:currentPage
// Element UI
<el-pagination layout="total, sizes, prev, pager, next, jumper" :page-sizes="[10, 20, 50, 100]" @size-change="handleSizeChange" :total="total" :page-size="limit" :current-page.sync="filter.page" @current-change="changePage">
<el-pagination>
// Element Plus
<el-pagination layout="total, sizes, prev, pager, next, jumper" :page-sizes="[10, 20, 50, 100]" @size-change="handleSizeChange" :total="total" :page-size="filter.limit" v-model:currentPage="filter.page" @current-change="changePage">
<el-pagination>
Button 按钮
size:medium / small / mini
–> large / default / small
text:type="text"
–> text: boolean
// Element UI
<el-button type="text" disabled> 文字按钮 </el-button>
// Element Plus
<el-button text disabled> 文字按钮 </el-button>
Message 音讯提醒
// Element UI
this.$message.success('增加胜利')
// Element Plus
// 引入
import {ElMessage} from "element-plus"
// 应用
ElMessage.success('增加胜利') // 应用
Message Box 弹框
// Element UI
this.$confirm('是否确认删除该文件?', '提醒', {
confirmButtonText: '确定',
cancelButtonText: '勾销',
type: 'warning'
}).then(async () => {...})
// Element Plus
// 引入
import {ElMessageBox} from "element-plus"
// 应用
ElMessageBox.confirm('是否确认删除该文件?', '提醒', {
confirmButtonText: '确定',
cancelButtonText: '勾销',
type: 'warning'
}).then(async () => {...})
九、VS Code 扩大
Vetur
–> Volar
。
十、其余
我的项目代码编译无误胜利启动后,需点击 所有界面 进行测试,在启动的时候,Vite
并不会打包源码,而是在浏览器申请路由时才会进行打包,而且也仅仅打包以后路由的源码,故当某个子页面出错时可能我的项目启动、运行时都失常。
同时也须要查看代码中的业务是否正确,降级过后可能会对原有代码逻辑产生影响,例如表单校验等性能,业务逻辑须要逐个比对和测试。