乐趣区

关于前端:Vue2升级Vue3实践

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.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,别离将 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 2
const asyncComponent = () => import('./async-component.vue')

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

defineAsyncComponentcomponent 选项更名为 loader,同时 loader 函数移除了 resolvereject 参数,必须手动返回 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 将蕴含传递给组件的所有属性,包含 classstyle 属性。

$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 中,当组件的 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 官网文档

五、生命周期

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 按钮

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 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 并不会打包源码,而是在浏览器申请路由时才会进行打包,而且也仅仅打包以后路由的源码,故当某个子页面出错时可能我的项目启动、运行时都失常。

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

退出移动版