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,如果之前我的项目中须要实现过滤性能,能够通过 computedmethods 实现。

如果须要应用全局过滤器,能够借助 globalProperties 来注册全局过滤器。

// lib/format.jsexport default {  formatCooperateStatus (status) {    const map = {      applied: '待声援',      assigning: '指派中',      partialRefuse: '从新指派',      process: '合作中',      refuse: '已退回',      tested: '合作实现'    }    return map[status] || ''  }}// main.js// Vue 2import Format from './lib/format.js'Vue.prototype.$format = Format// Vue 3import 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@latestcd xxxnpm installnpm 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 2import 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 = FormatVue.config.productionTip = falseVue.use(ElementUI)Vue.use(Vuex)new Vue({  router,  store,  render: h => h(App)}).$mount('#app')// Vue 3import { 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 = Formatfor (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,别离将 propevent 命名更改为 modelValueupdate: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-ifv-for 在同一个元素身上应用时的优先级产生了变动。

在 Vue 3 中,如果在一个元素上同时应用 v-ifv-forv-if 的优先级高于 v-for (不举荐这样应用)。

functional

在 Vue 3 中,{ functional: true } 选项已从通过函数创立的组件中移除,移除配置中 { functional: true } 和模板中 <template functional>functional

defineAsyncComponent 辅助函数

在 Vue 3 中异步组件通过 defineAsyncComponent 办法创立。

// Vue 2const asyncComponent = () => import('./async-component.vue')// Vue 3const asyncComponent = defineAsyncComponent(() => import('./async-component.vue'))

defineAsyncComponentcomponent 选项更名为 loader ,同时 loader 函数移除了 resolvereject 参数,必须手动返回 Promise

// Vue 2const asyncComponent = (resolve, reject) => {  ...}// Vue 3const 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 将蕴含传递给组件的所有属性,包含 classstyle 属性 。

$set() 和 $delete()

在 Vue 2 中,批改某一些数据,视图是不能及时从新渲染的,因而提供了一些变异的办法,比方 $set$delete

在 Vue 3 中,移除了 $set$delete,基于代理的变化检测曾经不再须要它们了。

<el-input v-model="form.no" placeholder="请输出编号"></el-input>// Vue 2export default {  data() {    return{      form:{        no:'11'      }    }  }  mounted () {    this.form.no = 'CaseNo' // 视图不变    this.$set(this.form, 'no', 'CaseNo') // 视图更新  }}// Vue3export default {  data() {    return{      form:{        no:'11'      }    }  }  mounted() {    this.form.no = 'CaseNo' // 视图更新  }}

Events API

在 Vue 3 中,移除了 $on$off$once 这三个事件相干的API,不再反对事件发射器接口,能够应用内部库来实现事件总线,例如 mitt

// @/lib/bus.jsimport 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 中,当组件的 datamixinextendsdata 进行合并时,只进行浅拷贝。

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 官网文档

五、生命周期

vue2vue3 (Options)vue3 (Composition)
beforeCreatebeforeCreatesetup()外部
createdcreatedsetup()外部
beforeMountbeforeMountonBeforeMount
mountedmountedonMounted
beforeUpdatebeforeUpdateonBeforeUpdate
updatedupdatedonUpdated
beforeDestroybeforeUnmountonBeforeUnmount
destroyedunmountedonUnmounted

六、路由

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

// v3import Router from 'vue-router'Vue.use(Router)const router = new Router({  mode: 'history',  routes})export default router// v4import { 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.jsimport 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 按钮

sizemedium / small / mini --> large / default / small

texttype="text" --> text: boolean

// Element UI<el-button type="text" disabled>文字按钮</el-button>// Element Plus<el-button text disabled>文字按钮</el-button>
Message 音讯提醒
// Element UIthis.$message.success('增加胜利')// Element Plus// 引入import { ElMessage } from "element-plus"// 应用ElMessage.success('增加胜利') // 应用
Message Box 弹框
// Element UIthis.$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 并不会打包源码,而是在浏览器申请路由时才会进行打包,而且也仅仅打包以后路由的源码,故当某个子页面出错时可能我的项目启动、运行时都失常。

同时也须要查看代码中的业务是否正确,降级过后可能会对原有代码逻辑产生影响,例如表单校验等性能,业务逻辑须要逐个比对和测试。