项目vue2升级vue3

Vue2迁移vue3操作指南

一、前言

有个自动化迁移工具gogocode-cli,尝试后发现不好用且得不偿失,就放弃了,感兴趣的可以去了解一下,本指南选择手动迁移

迁移开始之前,我们先来梳理下思路:

现在有一个vue2的项目,
首先我们升级框架,
得到了一个vue3的框架,但是上面放着vue2的代码,跑不起来,
然后我们安装并配置迁移构建版本(@vue/compat),
通过它能够在vue3的框架上跑绝大多数vue2的代码(兼容与否可自行配置)
接下来我们逐个迁移,将vue2的代码替换成vue3的写法,
如果项目中有必须依赖vue2的东西,就只能通过迁移构建版本兼容,
如果能完全vue3化,则迁移完毕后,删除迁移构建版本

注意,如果跟着本文档一步步迁移,则中途可以先不运行项目,将已知的迁移步骤完成后,再运行项目,解决剩余没有提及到的可能存在的问题

 

二、迁移过程

1.升级依赖

修改package.json文件(仅供参考,如果你项目用了不同的依赖配置,升级成适配vue3的就行了)

  • 升级vue、vuex、vue-router、@vue/cli-service
  • 新增@vue/compat
  • 替换element-ui 为 element-plus
  • 替换vue-template-compiler 为 @vue/compiler-sfc
  • 删除@babel/plugin-transform-runtime (因为@vue/cli-plugin-babel里本就包含了它)

然后删除node_modules,删除lock文件,重新执行npm install

这里可以先暂时不考虑其他依赖的升级适配,放到后面进行

参考代码:

"dependencies": {
  "@vue/compat": "^3.2.33",
  "element-plus": "^2.2.2",
  "vue": "^3.2.33",
  "vue-router": "^4.0.15",
  "vuex": "^4.0.2"
},
"devDependencies": {
  "@vue/cli-plugin-babel": "~5.0.4", // cli相关工具升级
  "@vue/cli-plugin-eslint": "~5.0.4",
  "@vue/cli-service": "~5.0.4", // 
  "@vue/compiler-sfc": "^3.2.33", // "vue-template-compiler": "^2.6.10"
  "eslint": "^7.32.0",
  "eslint-plugin-vue": "^7.0.0" // 适配vue3的话要升级到7以上
}

 

2.切换迁移构建版本

首先在vue.config.js文件中配置迁移构建版本

MODE值为3时,默认所有兼容配置关闭(即false),有要兼容vue2的,需将对应配置项open in new window定义为true
MODE值为2时,默认所有兼容配置开启(即true),不需要兼容vue2的,需将对应配置项定义为false

注意,迁移构建版本只是兼容大多数vue2写法,但还是有些地方无法兼容(如过滤器、插槽),必须适配vue3写法

参考代码:

// 对vue cli内部的webpack配置
chainWebpack: config => {
  // 别名
  config.resolve.alias.set('vue', '@vue/compat')
  // 配置模式
  config.module
    .rule('vue')
    .use('vue-loader')
    .tap(options => {
      return {
        ...options,
        compilerOptions: {
          // 此处的配置是特定于编译器的,
          // 如果您使用的是完整版本(使用浏览器内编译器),则可以在运行时配置它们。
          // 但是,如果使用构建设置,则必须通过compilerOptions构建配置中的来配置它们
          compatConfig: {
            MODE: 3 // MODE: 2
          }
        }
      }
    })
}

然后新增兼容配置文件configureCompat.js并在入口文件中引入

参考代码:

import { configureCompat } from 'vue'

configureCompat({
  MODE: 3           // MODE: 2 
})

 

3.Eslint调整

同样这里仅作参考,如果你项目用了别的eslint依赖,升级成适配vue3的就行了

项目中有使用 eslint-plugin-vue 的,在vue3中需升级到7以上
如下图源码所示,可以看见,只有7以上版本才适配了vue3的规则

所以请先确保插件 eslint-plugin-vue 的版本在7以上,然后根据需要选择上图右侧configs里合适的配置项
这里我们将.eslintrc.js文件中的 “plugin:vue/essential” 替换为 “plugin:vue/vue3-essential”

 

此外,建议将vscode的vetur插件替换成volar插件,以支持vue3语法

如果说vue2的官配是vetur,那么vue3的官配就是volar, 请对使用vue3的项目局部禁用vetur、局部启用volar,使之不影响vue2项目的使用

 

4.babel补充说明

module.exports = {
  presets: [
    '@vue/cli-plugin-babel/preset'
    // 可以用 '@vue/app' 它是 @vue/babel-preset-app 的缩写
    // 或者用 '@babel/preset-env' 不过就要搭配 @babel/plugin-transform-runtime
  ]
  // plugins: ['@babel/plugin-transform-runtime']
}

// @vue/cli-plugin-babel 是 vue-cli 的 babel 插件,
// 它默认使用 Babel 7 + babel-loader + @vue/babel-preset-app

// @vue/babel-preset-app 是 vue-cli 默认的 babel 预设,
// 它包含 @babel/preset-env 和 @babel/plugin-transform-runtime

// @babel/preset-env 的作用是根据浏览器目标自动确定要应用的转换和 polyfill ,
// 它的 targets 默认为 .browserslistrc 文件内的配置

// @babel/plugin-transform-runtime 会将 helper 和 polyfill 都改为从一个统一的地方引入,
// 并且引入的对象和全局变量是完全隔离的,避免了对全局变量及其原型的污染

 

5.Webpack5相关调整

webpack5 移除了disabledHostCheck属性
在vue.config.js文件里,将 “disabledHostCheck: true” 替换为 allowedHosts: 'all'

 

6.全局API调整

使用createApp 创建应用实例app,然后根据如下图所示的API,依次全局搜索并替换

 

上图右侧的“app.$mount”写错了,在vue3中挂载实例写法应为“app.mount”

 

7.调整vuex、vue-router

【vuex】

改用“createStore”方法创建实例,然后在bootstrap.js中引入并传递给use

【vue-router】

① 改用“createRouter”方法创建实例,然后在bootstrap.js中引入并传递给use

use方法要放在mount挂载之前

② 原本的“mode”被“history”配置取代,它的值可为:

  • createWebHistory() // 对应“history”
  • createWebHashHistory() // 对应“hash”
  • createMemoryHistory() // 对应“abstract”

③ 移动了“base”,现作为上述3个方法的第一个参数,如createWebHistory(‘XXX’)

④ 删除通配符路由,请将路由中的“”改为“:pathMatch(.)*”

⑤ <transition>和<keep-alive>必须通过v-slot才能在<router-view>内使用

全局搜“<router-view”,如果有如下写法:

<keep-alive>  // 或 <transition>   
  <router-view>
    <p>xxx</p>
  </router-view>
</keep-alive>  // 或 </transition>

请改造成:

<router-view v-slot='{Component}'> 
  <keep-alive>  // 或 <transition>     
    <component :is='Component'>
      <p>xxx</p>
    </component>
  </keep-alive>
</router-view>  // 或 </transition>

举例如下:

⑥ 全局搜索“history.pushState”,有的话

//将  
history.pushState(XXX, '', url)   
//替换成   
await router.push(url)  
history.replaceState({ ...history.state, ...XXX}, '')

⑦ 全局搜索“history.replaceState”,有的话

//将  
history.replaceState({}, '', url)  
//替换成  
history.replaceState(history.state, '', url)

⑧ 删除<router-link>中的“append”、“event”、“tag”、“exact”属性

全局搜索“<router-link”,依次查看
有用到“append”的,删除,并将“to”中的路径改成完整路径
有用到“exact”的,直接删除
有用到“event”、“tag”的,使用v-slot改写

例如:

<!-- 将 -->
<router-link to="/about" tag="span" event="click">About Us</router-link>
<!-- 改写成 -->
<router-link to="/about" custom v-slot="{ navigate }">
  	<span @click="navigate">About Us</span>
</router-link>

⑨ 带有空 path 的命名子路由不再添加斜线,这会影响子集的redirect ,举个例子:

{
  Path: '/XXX',
  Children:[
    // vue2中重定向到“/XXX/yyy”, vue3中重定向到“/yyy”
    { path: '' , name: 'test', redirect: 'yyy'} ,
    { path: 'yyy' } 
  ]
}

⑩ 其他

  • “pathToRegexpOptions” 属性被 createRouter() 中的 “sensitive” 取代
  • “caseSensitive” 属性被 createRouter() 中的 “strict” 取代
  • 将 “router.onReady(onSuccess, onError)” 改为 “isReady().then(onSuccess).catch(onError)” 的写法
  • 将 “scrollBehavior” 中返回对象的 “x” 改为 “left”,“y” 改为 “top”
  • 忽略 mixins 中的导航守卫
  • 删除 “fallback” 属性,因为现在vue支持的浏览器都支持history模式
  • 删除 “router.match”,合并到 “router.resolve” 中
  • 删除 “router.getMatchedComponents()”
  • 删除 “router.app”
  • 删除路由地址中的 “parent” 属性
  • 所有的导航现在都是异步的
  • 取消了path-to-regexp,所以不再支持未命名的参数
  • 跳转或解析不存在的命名路由或缺少params参数会报错(以前是会导航到'/')

 

8.element-ui 替换成 element-plus

① 全局搜索 “element-ui”,替换插件,改造如下

② 主题文件路径更换

将“~element-ui/packages/theme-chalk/src/index” 替换为 “~element-plus/theme-chalk/src/index”

将“element-ui/lib/theme-chalk/index.css” 替换为 “element-plus/theme-chalk/index.css”

③ 所有标签的size属性的值从“medium / small / mini”变更为“large / default /small”

全局搜索“size="medium"”,替换为“size="large"”
全局搜索“size="small"”,替换为“size="default"”
全局搜索“size="mini"”,替换为“size="small"”

④ <el-button>移除了“type=”text””,想维持vue2的样式,可用link属性代替

全局搜索“type="text"”,依次将<el-button>上的它替换为“type="primary" link”

不要将例如<input type=”text”>这样的改跑了

 

⑤ icon图标调整

element-ui是通过类名使用,如对于搜索图标是class=”el-icon-search”
而element-plus是当做组件使用,如对于搜索图标是<el-icon><Search/></el-icon>
使用之前,需要下载依赖
分支目录下命令行输入“npm install @element-plus/icons-vue”
然后全局引入,配置如下:

import * as ElementPlusIconsVue from '@element-plus/icons-vue'

Object.keys(ElementPlusIconsVue).forEach(key => {
  app.component(key, ElementPlusIconsVue[key])
})

全局搜索“el-icon”,然后根据使用方法做出相应调整,举例如下:

  • 对于loading里的spinner参数,如非必要,建议删除

因为(截止到目前)官网说使用类名,但类名已经废了,所以没找到改法

 

  • 对于通过class使用的,改成组件的形式,具体名称对比两者官网

大概率是el-icon-后面的名称的大驼峰写法
原有属性请放到<el-icon>中,icon组件会渲染成<svg>标签
项目中有可能针对“el-icon-xxx”类名设置了样式,所以建议保留原类名

 

 

  • 对于<el-input>、<el-button>上的做如下修改

 

⑥ <el-row>的type属性删除(element-plus使用flex布局,不用专门设置)

全局搜索“<el-row”或“type=”flex””

⑦ Date Picker / Time Picker / DateTime Picker 日期时间选择器改动

  • “first-day-of-week”属性删除(官方建议用dayjs里的方法代替,具体请自查)
  • “picker-options”属性删除,新增属性shortcuts、disabled-date、cell-class-name

 

 

⑧ 名称改动

全局搜索“open-delay”,替换成“show-after”
全局搜索“close-delay”,替换成“hide-after”

全局搜索“ElSubmenu”,替换成“ElSubMenu”
全局搜索“el-submenu”,替换成“el-sub-menu”

⑨ 对话框改动

全局搜索“<el-dialog”,
将它上面的“:visible.sync”改写成“v-model”,
将它上面的“:visible”改写成“:model-value”

⑩ 表单校验的调整(主要针对required)

如果表单配置了rules或required(值为null也算),就一定要有prop
除非将<el-form-item>内表单组件的validate-event属性设为false,单独关闭校验

至于为什么说required值为null也算,我们看下element-plus的源码
因为void 0 === undefined ,所以,
可以看到,当required的值不为这两个时,会赋值成false放进rules里

全局搜索“required”
首先将<FormItem>这种公共组件内部的required默认值定义为undefined或void 0
因为required: Boolean这种写法,不传值进来的话,默认值是false

然后找出所有<el-form-item>或<FormItem>标签上用了required但没有prop的地方
也就是没有prop说明没有校验的需求,仅为了显示 *
对于这种情况,这里选择将required删了,添加自定义类名,自己实现 *

9.异步组件调整

这里说的异步组件,不包括vue-router的懒加载,不用改它们

用了异步组件写法的,先看下是否真的有必要作为异步组件使用,如果只是为了直接写在components里方便,建议改成普通组件写法

举个例子,使用模块联邦的远程模块需异步加载,
但如果是在vue.config.js文件中用“remotes”配置的,那么它本就是异步加载,
使用时,像普通组件一样使用就行,
没用“remotes”选项配置的,才需要自己写异步加载

如下图所示,该项目使用了“remotes”

故无需再用异步引入,修改如下:

对于真的使用异步组件的,则需包裹“defineAsyncComponent”方法,如果使用了带选项的异步组件,选项里的component改名为loader

参考代码:

// vue2
components: {
  XXX: () => import('AAAA/BBB'),
  XXX2: () => ({component: import('AAAA/BBB')}), 
}
// vue3
import { defineAsyncComponent } from 'vue'
components: {
  XXX: defineAsyncComponent( () => import('AAAA/BBB') ),
  XXX2: defineAsyncComponent( {loader: () => import('AAAA/BBB')} )
}

 

10.$attrs、$listeners、片段

$attrs现在也包含了class和style,$listeners移除,事件监听器变成$attrs的一部分
片段允许组件有多个根节点,但是需要显示的定义v-bind="$attrs"

首先全局搜索“$listeners”,
有v-on="$listeners" 的删除,有this.$listeners 的改写成this.$attrs.onXxxx

然后全局搜索“inheritAttrs”,看下有没有inheritAttrs: false
有则表示该组件的根元素不会继承$attrs,需要显示绑定,如果绑定在子元素上,
那么看一下使用该组件时,组件标签上是否定义了class和style,
在vue2它们是落在根节点上的,而在vue3会落在绑定了$attrs的那个元素上,
所以有的话请改造,确保样式等不会受到影响

最后全局搜索“v-bind="$attrs"”
如果它写在根节点,且只有一个根节点,且又无inheritAttrs: false的设置,
则可以删除,因为默认就在根节点,
如果不在根节点上,则看下有没有受到class和style的影响

 

11.部分实例属性移除

$set、$delete、$children、$on、$off、$once
全局搜索它们,该删删 ,该改改

有用$children的话可以用$refs改造取代

$on、$off废弃的话Bus也就废了,有用到的话请用别的通信方式替代

Vue3用proxy代替Object.defineProperty,所以不再需要$set,请改为普通赋值写法

也可以通过自定义的全局属性重写这些方法实现原有的效果,就不用挨个修改了

顺带提一嘴.prop修饰符也移除了,用了的自行搜索解决方法

 

12.is属性变动

Vue3中is的渲染替换效果只在<component>标签上有用,
其他标签上的需要从“is=’xxx’”改成“is=’vue:xxx’”,否则is会被当成一个普通属性(即自定义内置元素)

全局搜索“is=”,如果有写在其他标签上的,请按上述方式改造

 

13.data选项变动

data现在只接收函数,并且mixins、extends的data现在变成浅合并

官方建议用组合式API代替mixins、extends

全局搜“mixins”和“extends”,看是否有data选项,有的话分析浅合并带来的改变并调整

 

14.filter过滤器移除

Vue3为了遵循大括号内只是js的原则,删除了过滤器,所以需要用方法或计算属性替换

为减少工作量,建议用方法替换

首先全局搜索“Vue.filter”,如果使用了全局过滤器,官方推荐通过全局属性来改造

这里我们定义一个全局属性$filters,里面存放原本的全局过滤器
使用的时候,通过$filters调用里面的过滤器方法即可

参考代码:

app.config.globalProperties.$filters = {}

Object.keys(filters).forEach(key => {
  app.config.globalProperties.$filters[key] = filters[key]
})

然后全局搜索“ | ”(注意前后都有空格),定位到使用了过滤器的地方

万一有哪处没这样写,搜索时漏掉了,就以后run时报错了再改吧

文件内依次搜索过滤器名称
如果该过滤器在filters选项里定义了,说明它是局部过滤器,只需改成方法调用
否则为全局过滤器,方法前面要加上之前定义的全局属性$filters,才能调用的到

然后再将filters选项中的局部过滤器移动到methods中,并删掉filters选项

最后全局搜索“filters:”,删除filters选项

可能存在使用了filters选项但是没内容或者未使用的情况

 

15.渲染函数调整

render() 的作用是渲染,h() 的作用是创建vnodes,h函数现在是全局导入

vue2中是作为参数,且全名是createElement

vue3中由于VNode是上下文无关的,不能再用字符串隐式查找注册的组件
所以需要使用resolveComponent方法进行包裹

注意这里说的是参数为字符串的情况
如果h的第一个传参接收的是字符串,例如vue2的写法为“render: h => h(‘XXX’)”
那么就需要引入resolveComponent方法,改成“render: () => h(resolveComponent(‘XXX’))”
截图示例中h的第一个参数是对象,所以无需使用resolveComponent

首先全局搜索“render”,找到使用渲染函数的地方,引入h方法,然后改造

全局搜索“$createElement”,有的话,两种改法

  • 第一种,引入h函数,替换“this.$createElement”

 

 

  • 第二种,自定义“this.$createElement”,其余地方不变

推荐这种,改起来快,且能适配到某些插件

 

 

16.函数式组件调整

functional移除
Vue3建议使用有状态的组件,因为函数组件的优势已经可以忽略不不计
函数组件现在只能由接收props和context(slots、attrs、emit)的普通函数创建

全局搜索“functional”,看看有没有functional: true 或 <template functional>
下图为使用functional: true的改造示例,<template functional>的很少用,不做示范

 

17.key调整

如果在v-if、v-else、v-else-if上使用了key,则每个分支的key必须唯一
如果在<template>标签上使用了v-for,
在vue2中,<template>不能有key,解决方法是在其子节点上设置key
但是在vue3中,key应该被写在使用了v-for的这个<template>上

全局搜“<template”,看是否有在该标签上使用v-for的
有的话参考下图调整

 

18.按键修饰符调整

keyCode废弃,不再支持使用数字 (即键码) 作为 v-on 的修饰符

全局搜索“@keyup”或“v-on:keyup”,有的话请查看官网自行改造

 

19.propsData选项移除

Vue2中propsData用于在创建vue实例时传入prop,vue3移除了它,可用createApp的第二个参数代替

全局搜索“propsData”,有的话请自行改造,参考如下:

 

 

20.过渡类名修改

全局搜索“v-enter”,有的话替换为“v-enter-from”
全局搜索“v-leave”,有的话替换为“v-leave-from”

 

21.<transition>

当使用 <transition> 作为根结点的组件从外部被切换时将不再触发过渡效果
<transition-group> 不再默认渲染根元素,但仍然可以用 tag attribute 创建根元素

全局搜索“<transition”,有的话请查看官网自行改造

 

22.v-bind合并行为

Vue3中v-bind 的绑定顺序会影响渲染结果

全局搜索“v-bind="{”,如果有,请参照下图修改

 

 

23.生命周期调整

beforeDestroy 和 destroyed 已经分别被重命名为 beforeUnmount 和 unmounted

全局搜索“beforeDestroy”,替换为“beforeUnmount”
全局搜索“destroyed”,替换为“unmounted”
全局搜索“hook:”,替换成“vnode-”

 

24.v-model和.sync调整

默认prop从“value”变成“modelValue”
默认event从“input”变成“update:modelValue”

.sync修饰符移除
model选项移除

同一个组件可以有多个v-model绑定
可以自定义v-model修饰符

【使用默认v-model的写法】

  • vue2 中
// 子
export default {
  props: {
      value: String,
  },
  methods:{
    fun() {
      this.$emit('input', 'XXX')
    }
  }
}
<!-- 父 -->
<Child v-model='pageTitle'>
<!--可能存在 <Child v-model='pageTitle' @input='YYY' @success='ZZZ'> -->
  • vue3 中
// 子
export default {
  props: {
      modelValue: String,
  },
  methods:{
    fun() {
      this.$emit('update:modelValue', 'XXX')
      // this.$emit('input', 'XXX')
      // this.$emit('update:modelValue', 'XXX')
    }
  }
}
<!-- 父 -->
<Child v-model='pageTitle'>
<!-- // 替换 <Child v-model='pageTitle' @update:modelValue='YYY' @success='ZZZ'> -->
<!-- // 或共存 <Child v-model='pageTitle' @input='YYY' @success='ZZZ'> -->

 

【使用v-model搭配model选项的写法】

  • vue2 中
// 子
export default {
  model: {          
    prop: 'title',
    event: 'change'
  },
  props: {
      title: String,
  },
  methods:{
    fun() {
      this.$emit('change', 'XXX')
    }
  }
}
<!-- 父 -->
<Child v-model='pageTitle'>
<!-- 可能存在 <Child v-model='pageTitle' @change='YYY' @success='ZZZ'> -->

 

  • vue3 中
// 子
export default {
  // 删除model选项
  props: {
      title: String,
  },
  methods:{
    fun() {
      this.$emit('update:title', 'XXX')
      // this.$emit('change', 'XXX')
      // this.$emit('update:title', 'XXX')
    }
  }
}
<!-- 父 -->
<Child v-model:title='pageTitle'>
<!-- 替换 <Child v-model:title='pageTitle' @update:title='YYY' @success='ZZZ'> -->
<!-- 或共存 <Child v-model:title='pageTitle' @change='YYY' @success='ZZZ'> -->

 

【使用.sync的写法】

  • vue2 中
// 子
export default {
  props: {
      title: String,
  },
  methods:{
    fun() {
      this.$emit('update:title', 'XXX')
    }
  }
}
<!-- 父 -->
<Child :title.sync='pageTitle'>

 

  • vue3 中
// 子
export default {
  props: {
      title: String,
  },
  methods:{
    fun() {
      this.$emit('update:title', 'XXX')
    }
  }
}
<!-- 父 -->
<Child v-model:title='pageTitle'>

 

先明确一点,对于v-model,我们要改造的是自定义组件(项目里自己写的)上的

首先全局搜索“$emit(‘input”和“$emit(“input”

单双引号、空格等情况要考虑到 如有,且组件内还用到了“value”这个prop,再根据组件名找到所以使用它的地方,看下是否用的默认v-model,是就改造

 

然后全局搜索“model: {”,找出所有使用model选项的自定义组件
再分别找出所有使用了它们的地方,确定它们用了v-model,然后改造

举例个特殊情况,如下图

 

可以看到,change事件监听器执行了2个方法,v-model自带的和额外定义的
两种改法,一种是维持1个监听器的效果:

另一种是变成2个监听器,原有的change事件保持,在它旁边增加新的监听器

最后全局搜索“.sync”,有的话,将“:XXX.sync”改写成“v-model:XXX”

 

25.新增emits选项,移除.native

emits用来定义子组件向其父组件触发的事件(写法类似props)
官方建议使用emits记录组件所触发的所有事件

任何未在emits记录的事件都会算入该组件的$attrs,
即作为原生事件添加到子组件的根元素(或定义了v-bind=’$attrs’ 的元素)中

也就是说,以前需要用.native实现的效果(如click事件)
现在只要不在emits里加同名事件(如click)就能实现

这里可以看下官方举的例子,便于理解:

这里有一点需要注意,
虽然官方只是建议用emits记录所有事件,不记录也会被放进$attrs作为原生事件用,

但是当自定义事件的名称与原生事件重名时,例如:$emit(‘click’)
不记录的话,事件可能会被触发两次,或者意料之外的状况,

所以,要么自定义事件命名时避开原生事件名,要么,就在emits中记录

首先全局搜索“.native”,直接点击替换,删除.native

然后全局搜索以及文件内搜索“this.$emit(”,增加emits选项,记录所有自定义事件

 

26.插槽调整

vue2中,$slots用来访问插槽分发的内容,$scopedSlots用来访问作用域插槽
Vue3中,移除了$scopedSlots,将插槽作为函数公开(以前是对象)

【具名插槽】

  • vue2 中
<!-- 子 -->
<slot name='XXX'>

<!-- 父 -->
<template slot='XXX'>
  • vue3 中
<!-- 子 -->
<slot name='XXX'>
  
<!-- 父 -->
<template v-slot:XXX>  
<!-- 可缩写为 <template #XXX> -->

 

【作用域插槽】

  • vue2 中
<!-- 子 -->
<slot :YYY='yyy'>

<!-- 父 -->
<template slot-scope='scope'> {{scope.YYY}}
  • vue3 中
<!-- 子 -->
<slot :YYY='yyy' name='XXX'>

<!-- 父 -->
<template v-slot:XXX='scope'> {{scope.YYY}} 

注意,v-slot只能写在<template>中,除非是独占默认插槽(能写在组件标签上)

 

首先全局搜索 “$scopedSlots”,将 “$scopedSlots” 替换为 “$slots”
再全局搜索 “$slots”,将 “$slots.XXX” 替换为 “$slots.XXX()”

请分开搜索避免遗漏,因为在vue2中他们是两个东西,只不过在vue3中统一了写法

然后全局和文件内搜索 “slot=”, 依次将 “slot='XXX'” 改成 “#XXX” 或者 “v-slot:XXX”
对于非<template>标签上的slot, 先在它的外层包裹上<template>标签,然后将slot挪到<template>上

最后全局搜索“slot-scope=”,替换成“#default=”或者“v-slot:default=”

 

27.穿透样式调整

样式中使用“/deep/”、“>>> ”、“::v-deep”的会报错
全局搜索它们,然后换成“:deep()”的格式

 

28.升级其余依赖(仅供参考)

对剩下的依赖(第1步没处理的)进行升级或替换,适配vue3,
注意需要调整的肯定是插件内部有使用到vue相关功能的, 如果有依赖只能用vue2版本的话,请记录下来,后续只能继续使用迁移构建版本

举个例子,如果使用了“@riophae/vue-treeselect”插件
分支目录下命令行输入“$ npm uninstall @riophae/vue-treeselect”
然后输入“npm install --save vue3-treeselect”
全局搜索“@riophae/vue-treeselect”
先将“@riophae/vue-treeselect/dist/vue-treeselect.css”
修改为“vue3-treeselect/dist/vue3-treeselect.css”
再将“@riophae/vue-treeselect”替换为“vue3-treeselect”

 

29.微前端适配

首先去除Vue的引入,然后改造singleSpaVue()的传参,
router和store已经通过use引入,所以appOptions里不用再写

将“Vue”换成“createApp”,插件会自己创建一个vue实例,
然后将实例作为参数调用handleInstance的方法(有的话),之后会将实例挂载

请在appOptions里传入el,否则插件会自己在body创一个容器,
这样的话,不管将实例挂载在哪,都会多出一个容器,影响页面展示,详情请看源码

现在我们有两个app实例了(自己创的用于单独运行项目的,以及微前端里插件创的),
我们将之前定义的一堆东西封装成一个方法,然后让两个实例都调用它

顺带一说,vue-router v4.x中的router.history已经移除了,所以这里删掉它, 可以分别打印一下v3.x和v4.x的router对比一下,也可参考官网深入解读

具体改动可参考下图:

 

30.其余调整

以下改动项目里会用到的概率低,可自行检查,也可作为错误排查的参考:

  • v-for上有ref的,ref不再自动创建
  • props默认值不能再访问this
  • v-if和v-for优先级变动,在vue2中v-for高,vue3中v-if高
  • attribute 强制行为改动(值为false的处理方式有改动,具体官网自查)
  • 挂载有template的应用时,从vue2的替换元素变成vue3的作为子元素插入
  • 没有特殊指令的 <template> 现在会被渲染为原生元素

到这里,常规的迁移工作就完成了, 接下来请尝试 lint 一下或直接运行项目, 如果还有报错或警告,请逐一检查,需要修复的就修复(第32步有举例可参考), 修复完成后,如果项目中没有必须依赖vue2的地方, 就可以去掉迁移构建版本,切换成vue3版本,否则只能继续使用迁移构建版本, 之后,请对系统功能做测试验证,测试所有组件及交互, 可能还会有错误或警告,再做修复

 

31.切换vue3版本

确定项目没有依赖vue2的地方后,我们开始尝试去掉迁移构建版本

建议先注释掉,后续验证完系统功能,确定vue3迁移成功后,再删除

 

32.错误整理(仅供参考)

① 命令行可能会发现报错Unexpected mutation of "XXX" prop

这是eslint对单向数据流的检测,子组件直接修改了prop就会报这个错

如果是个新项目,那么以它为约束进行开发是很好的, 但是如果现有的项目已经有写成这样不规范的且运行正常,就不要轻易改动了, 代价大且容易产生新的风险

这里我们在.eslintrc.js文件中配置“'vue/no-mutating-props': 'off'”关闭这个检测

② 无关的非props属性传递给组件带来的警告

如果往组件传递了个未被定义属性(如下图的visible),且它又不是$attrs里带的(如title),
就会报这样的警告,大概率是代码有误

比如下图为<el-dialog>中写成“v-model:visible”所报的错,解决办法是写作“v-model”

③ 无关的非emits事件监听器传递给组件带来的警告

如果往组件传递了个未被定义属性(如下图的beforClose),且它又不是$attrs里带的(如input),
就会报这样的错,大概率是代码有误

比如下图,
“@befor-close”是错误写法,正确用法是“:befor-close”,
所以解决方法是删掉<el-dialog>上的“@befor-close”

④ 使用了废弃属性带来的警告

查看“vue3-treeselect”源码后发现,它使用了functional(vue3已移除),
完全切换成vue3版本后就不会有这个警告了,所以忽略这个警告

⑤ 解析不了标签带来的警告

不影响使用,忽略这个警告

⑥ attribute 强制行为改动带来的警告

如下图所示,可以看到“aria-current”这个属性(li标签上的无障碍属性),
再对比下vue3中对于attribute 强制行为的改动,

也就是说,对于“aria-current”,element-plus是希望,当它的值为false时,
输出为“aria-current=‘false’”,而不是移除

完全切换成vue3版本后就不会有这个警告了,所以忽略这个警告

⑦ 使用未定义的实例属性带来的警告(大概率是代码有误)

下图的例子是因为vue2中的“this.$createElement”也就是h函数,
在vue3中变成了通过import使用,“this.$createElement”也删除了

改法在第15点说过了,这里是插件的问题(用了但没完全用),建议用第二种改法

 

三、后记

别忘了学习Vue3的组合式API

官方推荐vue3的项目使用vite代替vuecli,
但是考虑到迁移成本、兼容性以及社区生态等因素,短期内还是推荐继续使用vuecli

Vuecli迁移vite教程open in new window,感兴趣的可以了解一下

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2023-08-10 17:12  泠风lj  阅读(12031)  评论(0编辑  收藏  举报