Vue.js 3.0 在北京时间 2020 年 9 月 19 日凌晨,终于发布了 3.0 版本,代号:One Piece。
查看 vue 3.0 文档 后,总结记录一下从 vue 2 迁移到 vue 3 所需的一些更改,方便日后对比学习新版本。
与 vue 2.x 比较,将 vue 3.0 中的改动分类为 3 种:
下面主要记录 vue 3.0 中的写法。
新增了 defineAsyncComponent 方法,用于显式地定义异步组件。
import {
defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'
// 不带选项的异步组件
const asyncPage = defineAsyncComponent(() => import('./NextPage.vue'))
// 带选项的异步组件
const asyncPageWithOptions = defineAsyncComponent({
loader: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent
})
针对 DOM 模板,使用 v-is 来动态渲染组件:
<tr v-is="'blog-post-row'">tr>
在 vue 3 中,组件支持多根节点组件,即片段。
<template>
<header>...header>
<main v-bind="$attrs">...main>
<footer>...footer>
template>
v-for 中的 ref 绑定为一个函数,而不是直接的一个数组。
<div v-for="item in list" :ref="setItemRef">div>
export default {
data() {
return {
itemRefs: []
}
},
methods: {
setItemRef(el) {
this.itemRefs.push(el)
}
},
beforeUpdate() {
this.itemRefs = []
},
updated() {
console.log(this.itemRefs)
}
}
// 或者
import {
ref, onBeforeUpdate, onUpdated } from 'vue'
export default {
setup() {
let itemRefs = []
const setItemRef = el => {
itemRefs.push(el)
}
onBeforeUpdate(() => {
itemRefs = []
})
onUpdated(() => {
console.log(itemRefs)
})
return {
itemRefs,
setItemRef
}
}
}
重命名自定义指令 API,以便更好地与组件生命周期保持一致。
const MyDirective = {
beforeMount(el, binding, vnode, prevVnode) {
},
mounted() {
},
beforeUpdate() {
},
updated() {
},
beforeUnmount() {
}, // 新
unmounted() {
}
}
is 特性用法仅限于保留的 标记,在其它标记上将失效。
对于 SFC(单文件组件),is prop 的使用格式为:
// 在 vue 3 中渲染的将会是普通的 button
<button is="plastic-button">点击我!button>
data 组件选项声明不再接收纯 JavaScript object,而需要 function 声明。
<script>
import {
createApp } from 'vue'
createApp({
data() {
return {
apiKey: 'a1b2c3'
}
}
}).mount('#app')
script>
当来自组件的 data() 及其 mixin 或 extends 基类被合并时,现在将浅层次执行合并。
$on,$off 和 $once 实例方法已被移除,$emit 尚保留为一个 API。
现可以通过外部库 mitt 来替换现有的事件中心。
或者,在兼容性构建中,也可以支持这些方法。
在 Vue 3 中,所有的函数式组件都是用普通函数创建的,换句话说,不需要定义 { functional: true } 组件选项。
import {
h } from 'vue'
const DynamicHeading = (props, context) => {
return h(`h${
props.level}`, context.attrs, context.slots)
}
DynamicHeading.props = ['level']
export default DynamicHeading
调用 createApp 返回一个应用实例,这是 Vue 3 中的新概念:
import {
createApp } from 'vue'
const app = createApp({
})
应用程序实例暴露当前全局 API 的子集,经验法则是,任何全局改变 Vue 行为的 API 现在都会移动到应用实例上,以下是当前全局 API 及其相应实例 API 的表:
2.x 全局 API | 3.x 实例 API(app) |
---|---|
Vue.config | app.config |
Vue.config.productionTip | removed |
Vue.config.ignoredElements | app.config.isCustomElement |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
所有其他不全局改变行为的全局 API 现在被命名为 exports
在应用程序之间共享配置 (如组件或指令) 的一种方法是创建工厂功能,如下所示:
import {
createApp } from 'vue'
import Foo from './Foo.vue'
import Bar from './Bar.vue'
const createMyApp = options => {
const app = createApp(options)
app.directive('focus' /* ... */)
return app
}
createMyApp(Foo).mount('#foo')
createMyApp(Bar).mount('#bar')
在 vue 2.x 中,tree-shaking 针对全局 API Vue.nextTick() 无计可施。
在 Vue 3 中,全局和内部 API 都经过了重构,并考虑到了 tree-shaking 的支持。因此,全局 API 现在只能作为 ES 模块构建的命名导出进行访问。
import {
nextTick } from 'vue'
nextTick(() => {
// 一些和DOM有关的东西
})
import {
shallowMount } from '@vue/test-utils'
import {
MyComponent } from './MyComponent.vue'
import {
nextTick } from 'vue'
test('an async feature', async () => {
const wrapper = shallowMount(MyComponent)
// 执行一些DOM相关的任务
await nextTick()
// 运行你的断言
})
在 vue 3.0 中,对内联特性(inline-template attribute)的支持已经废除。
现在,有 2 种迁移策略:
<script type="text/html" id="my-comp-template">
<div>{
{
hello }}</div>
</script>
// 在组件中,使用选择器将模板作为目标:
const MyComp = {
template: '#my-comp-template'
// ...
}
<!-- 2.x 语法 -->
<my-comp inline-template :msg="parentMsg">
{
{
msg }} {
{
childState }}
</my-comp>
<!-- 默认 Slot 版本 -->
<my-comp v-slot="{ childState }">
{
{
parentMsg }} {
{
childState }}
</my-comp>
key attribute 被用于提示 Vue 的虚拟 DOM 算法对节点身份保持持续的追踪,以便 vue 知道在何时能够重用节点、何时能够修改节点、何时能够对节点重新排序或者重新创建。
<!-- Vue 2.x -->
<div v-if="condition" key="a">Yes</div>
<div v-else key="a">No</div>
<!-- Vue 3.x (recommended solution: remove keys) -->
<div v-if="condition">Yes</div>
<div v-else>No</div>
<!-- Vue 3.x (alternate solution: make sure the keys are always unique) -->
<div v-if="condition" key="a">Yes</div>
<div v-else key="b">No</div>
<!-- Vue 3.x -->
<template v-for="item in list" :key="item.id">
<div>...</div>
<span>...</span>
</template>
<!-- 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>
在 vue 3 中,config.keyCodes 现在也已弃用,不再受支持;也不再支持使用数字 (即键码) 作为 v-on 修饰符。建议对任何要用作修饰符的键使用 kebab-cased (短横线) 大小写名称。
<!-- Vue 3 在 v-on 上使用 按键修饰符 -->
<input v-on:keyup.delete="confirmDelete" />
生成 prop 默认值的工厂函数不再能访问 this。
import {
inject } from 'vue'
export default {
props: {
theme: {
default (props) {
// `props` 是传递给组件的原始值。
// 在任何类型/默认强制转换之前
// 也可以使用 `inject` 来访问注入的 property
return inject('theme', 'default-theme')
}
}
}
}
在 3.x 中,h 现在是全局导入的,而不是作为 render 函数的参数自动传递。
// Vue 3 渲染函数示例
import {
h } from 'vue'
export default {
render() {
return h('div')
}
}
在 3.x 中,插槽被定义为当前节点的子对象:
// 3.x Syntax
h(LayoutComponent, {
}, {
header: () => h('div', this.header),
content: () => h('div', this.content)
})
当你需要以编程方式引用作用域 slot 时,它们现在被统一到 s l o t s 选 项 中 。 在 3. x 中 , 将 所 有 t h i s . slots 选项中。在 3.x 中,将所有 this. slots选项中。在3.x中,将所有this.scopedSlots 替换为 this.$slots。
// 2.x 语法
this.$scopedSlots.header
// 3.x 语法
this.$slots.header
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
在 3.x 中,自定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件:
<ChildComponent v-model="pageTitle" />
<!-- 简写: -->
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>
若需要更改 model 名称,而不是更改组件内的 model 选项,可以将一个 argument 传递给 model:
<ChildComponent v-model:title="pageTitle" />
<!-- 简写: -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
在自定义组件上使用多个 v-model:
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
<!-- 简写: -->
<ChildComponent
:title="pageTitle"
@update:title="pageTitle = $event"
:content="pageContent"
@update:content="pageContent = $event"
/>
3.x 还支持自定义修饰符。
调整方法:
<ChildComponent :title.sync="pageTitle" />
<!-- 替换为 -->
<ChildComponent v-model:title="pageTitle" />
<ChildComponent v-model="pageTitle" />
// ChildComponent.vue
export default {
props: {
modelValue: String // 以前是`value:String`
},
methods: {
changePageTitle(title) {
this.$emit('update:modelValue', title) // 以前是 `this.$emit('input', title)`
}
}
}
2.x 版本中在一个元素上同时使用 v-if 和 v-for 时,v-for 会优先作用;
3.x 版本中 v-if 总是优先于 v-for 生效。
比起在模板层面管理相关逻辑,更好的办法是通过创建计算属性筛选出列表,并以此创建可见元素。
在 3.x,如果一个元素同时定义了 v-bind=“object” 和一个相同的单独的 property,那么声明绑定的顺序决定了它们如何合并。换句话说,相对于假设开发者总是希望单独的 property 覆盖 object 中定义的内容,现在开发者对自己所希望的合并行为有了更好的控制。
<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="blue"></div>
<!-- template -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- result -->
<div id="red"></div>
Filters 已从 Vue 3.0 中删除,不再受支持。
可以使用计算属性或方法来代替过滤器。
<template>
<h1>Bank Account Balance</h1>
<p>{
{
accountInUSD }}</p>
</template>
<script>
export default {
props: {
accountBalance: {
type: Number,
required: true
}
},
computed: {
accountInUSD() {
return '$' + this.accountBalance
}
}
}
</script>