vue3.0 迁移指南
- 全局 api 更改为 应用程序实例.
vue2.0 有很多全局的 api 和配置,比如 Vue.component 创建全局组件,Vue.directive 创建全局指令,Vue.mixins 和 Vue.use 等等..
因为Vue2.0 通过 new Vue(…)来创建根 Vue 实例,从同一个 Vue 构造函数创建的根实例共享相同的全局配置.1
2
3
4
5
6
7// 这会影响两个根实例
Vue.mixin({
/* ... */
})
const app1 = new Vue({ el: '#app-1' })
const app2 = new Vue({ el: '#app-2' })
vue3.0 提供了 一个全新的全局 API - creatApp,调用它返回一个应用实例.应用实例拥有当前全局 API 的子集.1
2
3
4import { createApp } from 'vue'
const app = createApp({})
// app.component,app.directive,app.mixin,app.use...
app.mount('#app');
- 全局和内部 api 以及重构为可 tree-shaking (删除无用代码,不打包到 bundle)
vue2.0 时再用 Vue.nextTick() 或它的简单包装形式$nextTick()时,webpack 的 tree-shaking 不可摇动.
Vue3.0 对全局和内部 api 进行了重构,考虑到 tree-shaking 的支持,全局 api 现在只能作为 es 模块侯建的命名导出进行访问.例如:1
2import {nextTick} from 'vue'
nextTick(...);
受影响的 api 有:
- Vue.nextTick
- VUe.observable(用 Vue.reactive 替代)
- Vue.version
- Vue.compile
- Vue.set
- Vue.delete
- v-model 用法更改
在 vue2.0 中,v-model 用来双向绑定数据,但一个组件只能用于一个 v-model,如果需要多个双向绑定只能用.sync.1
2
3
4
5
6// 2.x
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
// 简写
<ChildComponent :title.sync="pageTitle" />
// 子组件内部触发
this.$emit('update:title', newValue)
在 vue3.0 中,v-model 通过后面要绑定的属性名来实现绑定多个值.
原理: vue2.0 的 v-model 通过绑定一个 value 属性和 input 事件,将输入e.target.value映射到 绑定的变量 值上.
而 vue3.0 相当于传递了modelValue 的 prop 并接收了抛出的 update 事件.1
2
3
4
5
6
7
8
9
10
11
12// 用在组件上
<custom-input v-model="searchText"></custom-input>
// 组件内部的 input 必须将属性绑定在 modelValue 上且 事件触发通过 update:modelValue 抛出.
app.component('custom-input', {
props: ['modelValue'],
template: `
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
>
`
})
key 属性用法更改
对于 v-if,v-else,v-else-if 的 key 不再必须,Vue3.0 会自动生成 唯一的key,不建议手动赋予 key 值.
vue2.0 中 template 标签不能有 key 值,通常在它的子节点设置 key.在 Vue3.0 中,key 值应该设置在 template 标签中.1
2
3
4
5
6
7
8
9
10
11<!-- Vue 2.x -->
<template v-for="item in list">
<div v-if="item.isVisible" :key="item.id">...</div>
<span v-else :key="item.id">...</span>
</template>
<!-- Vue 3.x -->
<template v-for="item in list" :key="item.id">
<div v-if="item.isVisible">...</div>
<span v-else>...</span>
</template>v-if 和 v-for 优先级调整.
vue2.0 中,v-for 的优先级最高.而在 Vue3.0 中,v-if 的优先级最高. 但都建议避免他们在同一元素上使用.v-for 中的 ref 数组.
vue2.0 中 v-for 使用 ref 会用 ref 数组填充相应的 $refs.当 v-for 存在嵌套 v-for 时,这是不明确和效率低下的.
Vue3.0 中.这样的用法将不再自动创建数组,需要将 ref 绑定到一个灵活地函数上.只能使用普通函数创建功能组件(函数式组件).
vue2.0 函数式组件示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// Vue 2 函数式组件示例
export default {
functional: true,
props: ['level'],
render(h, { props, data, children }) {
return h(`h${props.level}`, data, children)
}
}
// 或者
<template functional>
<component
:is="`h${props.level}`"
v-bind="attrs"
v-on="listeners"
/>
</template>
<script>
export default {
props: ['level']
}
</script>
Vue3.0 不需要定义 functional,接收两个参数,props 和 context.(同 setup)1
2
3
4
5
6import { h } from 'vue'
const DynamicHeading = (props, context) => {
return h(`h${props.level}`, context.attrs, context.slots)
}
DynamicHeading.props = ['level']
export default DynamicHeading
- 异步组件
Vue2.0 通过将组件定义为返回 promise 的函数来创建的.1
const asyncPage = () => import('./NextPage.vue')
Vue3.0 由于函数式组件被定义为纯函数,因此异步组件需要包装在新的 defineAsyncComponent 方法显示定义.1
2import { defineAsyncComponent } from 'vue'
const asyncPage = defineAsyncComponent(() => import('./NextPage.vue'))
h 渲染函数更改.
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 2.x 语法 render 函数接收 h 之类的参数
export default {
render(h) {
return h('div')
}
}
// 3.x 语法 h 函数全局导入,不作为参数传递,可以用作 setup 的返回值函数
import { h } from 'vue'
export default {
render() {
return h('div')
}
}slot 统一
在 3.x 中,将所有 this.$scopedSlots 替换为 this.$slots自定义指令
自定义指令的钩子函数更改为与组件声明周期统一的事件钩子.
bind => beforeMount, inserted => mounted, 新增 beforeUpdate, update 与 componentUpdated => updated, 新增 beforeUnmounte, unbind => unmounted.watch 和 $watch 不再支持.分隔符字符串路径,请改为计算函数作为参数.
destroyed 重命名为 unmounted, beforeDestroy 重命名为 beforeUnmount
prop 默认值函数中不再能访问 this,可以把组件接收到的原始 prop 作为参数传递给默认函数.或使用 inject.
1
2
3
4
5
6
7
8
9
10
11
12
13
14import { inject } from 'vue'
export default {
props: {
theme: {
default (props) {
// `props` 是传递给组件的原始值。
// 在任何类型/默认强制转换之前
// 也可以使用 `inject` 来访问注入的 property
return inject('theme', 'default-theme')
}
}
}
}data 组件选项不再接受纯 js Object,而必须是 function.而组件和 mixins 或 extends 基类合并是,现在将浅层次合并.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33const Mixin = {
data() {
return {
user: {
name: 'Jack',
id: 1
}
}
}
}
const CompA = {
mixins: [Mixin],
data() {
return {
user: {
id: 2
}
}
}
}
// vue 2.0 合并后是
{
user: {
id: 2,
name: 'Jack'
}
}
// Vue 3.0 合并后是
{
user: {
id: 2
}
}
迁移建议: 对于依赖 mixin 的深度合并行为的用户,我们建议重构代码以完全避免这种依赖,因为 mixin 的深度合并非常隐式,这让代码逻辑更难理解和调试。
过渡 class 名更改
过渡类名 v-enter 修改为 v-enter-from、过渡类名 v-leave 修改为 v-leave-from。.移除功能
- 不再支持使用数字键吗作为 v-on 的修饰符
不再支持 config.keyCodes
1
2
3
4
5
6
7
8Vue.config.keyCodes = {
f1: 112
}
<!-- 键码版本 -->
<input v-on:keyup.112="showHelpText" />
<!-- Vue 3 在 v-on 上使用 按键修饰符, 建议对任何要用作修饰符的键使用 kebab-cased (短横线) 大小写名称 -->
<input v-on:keyup.delete="confirmDelete" />$on,$off 和 $once 实例方法(全局事件侦听器)已被移除,应用实例不再实现事件触发接口。
$emit 仍然是现有 API 的一部分,因为它用于触发由父组件以声明方式附加的事件处理程序Fileter 已删除,不再受支持,建议使用计算属性替代
1
2
3
4
5
6
7
8
9<p>{{ accountBalance | currencyUSD }}</p>
export default{
filters: {
currencyUSD(value) {
return '$' + value
}
}
}删除 inline-template 内联属性
- 删除$destroy 实例方法,用户不应再手动管理 Vue 组件的生命周期
vue3新特性
setup
setup是Vue3.0提供的一个新的属性,可以在setup中使用Composition API.setup函数有两个参数,分别是props和context。props 是组件外部传入进来的属性,contextcontext是一个对象,里面包含了三个属性attrs,slots,emit.
attrs 与 vue2.0 的 this.$attrs 一样,是外部传入未在 props 中定义的属性.
slots 对应 vue2.0 的 this.$slots 代表组件插槽.
emit 对应 vue2.0 的 this.$emit,对外暴露的事件.
setup 返回一个对象,对象中包含了组件使用到的 data 与一些函数或事件,也可以返回一个函数,对应 vue2.0 的 render 函数在里面可以使用 jsx.
不要在 setup 中使用 this,通过 props 和 content 基本可以满足开发需求.1
2
3
4
5
6
7
8
9
10
11export default {
props: {
value: {
type: String,
default: ""
}
},
setup(props) {
console.log(props.value)
}
}composition API
在 vue2.0 中,我们在 data()函数中定义数据,在 methods,computed,watch 等等地方使用数据,书写逻辑.但随着功能增加,代码越来越难阅读和理解,因为现有的 api 迫使我们通过选项写代码,但有时候通过逻辑写代码更有意义.但 vue2.0 缺少一种简介的机制来提取和重用多个组件间的逻辑.
了解 composition api 前,想了解下 reactive 和 ref.
reactive
在 vue2.6 中有一个 新的 api, Vue.observer,通过这个 api 可以创建一个响应式对象.而 reactive 和 observer 功能基本一样.
1 | <template> |
vue2.x 时,经常出现更改数据后页面没有刷新,需要使用 Vue.set()来解决.vue3.0 抛弃了 2.0 使用的 Object.defineProperty .使用 proxy 来监听.我们可以直接在reactive声明的对象上添加新的属性.
reactive 返回的不是原对象,而是 proxy 实例的一个全新对象.
ref
假如现在我们需要在一个函数里面声明用户的信息,那么我们可能会有两种不一样的写法1
2
3
4
5
6
7
8// 写法1
let name = 'vue'
let version = '3.0'
// 写法2
let info = {
name: 'vue',
version: '3.0'
}
对于写法1我们直接使用变量就可以了,而对于写法2,我们需要写成info.name的方式。我们可以发现info的写法与reactive是比较相似的,而Vue3.0也提供了另一种写法,就像写法1一样,即ref。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21<template>
<div>
<div>名称:{{ name }}</div>
</div>
</template>
<script>
import { ref } from "vue";
export default {
setup() {
const name = ref("vue");
console.log('名称',name.value)
// 5秒后修改name为 react
setTimeout(() => {
name.value = "react";
}, 1000 * 5);
return {
name
};
}
};
</script>
reactive传入的是一个对象,返回的是一个响应式对象,而ref传入的是一个基本数据类型(其实引用类型也可以),返回的是传入值的响应式值.
reactive获取或修改属性可以直接通过state.xxx来操作,而ref返回值需要通过xxx.value的方式来修改或者读取数据。但是需要注意的是,在template中并不需要通过.value来获取值,这是因为template中已经做了解套。
- toRefs会把一个响应式对象的每个属性都转换为一个ref,在 setup 返回值中…roRefs(data),在模板中引用不需要再加上 data 前缀,可以直接使用变量.
1
2
3
4
5
6
7
8
9
10
11import { toRefs, reactive } from 'vue'
export default {
setup() {
let data = reactive({
count: 0
})
return {
...toRefs(data)
}
}
}
- watch
vue2.0 中使用 watch 来监听结果的变化
1 | export default{ |
vue3.0 兼容 2.0 的写法,也提供了新的 api.分别是 watch 和 watchEffect.
watch 与2.0 $watch 用法一样.但可以监听单个值和函数的返回值,还可以监听多个数据源(放在一个数组中).
watchEffect会传入一个函数,然后立即执行这个函数,对函数中的响应式依赖进行监听,当依赖变化时,重新调用传入的函数.1
2
3
4
5
6
7
8
9
10
11
12
13
14import { ref, watchEffect } from 'vue'
export default {
setup() {
const id = ref('0')
watchEffect(() => {
// 先输出 0 然后两秒后输出 1
console.log(id.value)
})
setTimeout(() => {
id.value = '1'
}, 2000)
}
}
vue2.0 中$watch 会返回一个函数用于停止监听.vue3.0 中 watch 和 watchEffect 也会返回一个用于 unwatch 的函数.
computed
vue 3.0 中 computed 与 vue2.0 一样.1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22// 2.0
computed:{
getName(){
return this.firstName + this.lastName;
}
}
// vue3.0
const info = reactive({
firstName: 'xx',
lastName: 'xxx'
})
// getter
const getName = computed(() => info.firstName + info.lastName)
// 或 getter + setter
const getName = computed({
get: () => info.firstName + info.lastName,
set(val){
const name = val.split('-')
info.firstName = name[0]
info.lastName = name[1]
}
})readonly
获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。只读代理是深层的:访问的任何嵌套 property 也是只读的。provide 和 inject
provide 和 inject 启用依赖注入。只有在使用当前活动实例的 setup() 期间才能调用这两者。