一、 新增特性 New
Async components异步组件
import { defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'
// Async component without options
const asyncPage = defineAsyncComponent(() => import('./NextPage.vue'))
// Async component with options
const asyncPageWithOptions = defineAsyncComponent({
loader: () => import('./NextPage.vue'),
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent
})
新的defineAsyncComponent helper方法显式地定义了异步组件 component选项重命名为loader 加载器函数本身并不接受resolve和reject参数,并且必须返回一个Promise实例
emits Option
Vue 3 提供了一个 emits option,类似于 props option. 这个 option 可用于定义组件可以发送给其父组件的事件。
<template>
<div>
<p>{{ text }}</p>
<button v-on:click="$emit('accepted')">OK</button>
</div>
</template>
<script>
export default {
props: ['text'],
emits: ['accepted']
}
</script>
emits option还接受一个对象,它允许开发人员为随发出的事件传递的参数定义验证器,类似于props定义中的验证器
const app = Vue.createApp({})
// Array syntax
app.component('todo-item', {
emits: ['check'],
created() {
this.$emit('check')
}
})
// Object syntax
app.component('reply-form', {
emits: {
// no validation
click: null,
// with validation
submit: payload => {
if (payload.email && payload.password) {
return true
} else {
console.warn(`Invalid submit event payload!`)
return false
}
}
}
})
Fragments
在Vue 3中,组件现在正式支持多根节点组件,也就是fragments
<template>
<header>...</header>
<main v-bind="$attrs">...</main>
<footer>...</footer>
</template>
二、删除的特性 remove
$children
$children instance属性已从Vue 3.0中移除,不再受支持。如果您需要访问子组件实例,我们建议使用$refs。
Filters 过滤器
fliters过滤器已从Vue 3.0中移除,不再支持。相反,建议用methods或computed计算属性替换它们。
$listeners
在Vue 3中,$listeners对象已经被删除。事件监听器现在是$attrs的一部分
events API
vue3中完全删除了off和$once方法
v-on.native
v-on的.native修饰符已经被删除,vue3中,新的emits option 允许child定义它确实发出哪些事件。
因此,Vue3将把所有没有定义为child中组件触发事件的事件监听器添加到 child的根元素中(除非 child的选项中设置了inheritAttrs: false)。
<my-component
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
/>
<!-- MyComponent.vue -->
<script>
export default {
emits: ['close']
}
</script>
三、修改的特性 breaking
v-for Array Refs
<div v-for="item in list" :ref="setItemRef"></div>
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
}
}
}
在Vue 3中,这样的使用不再会自动在$refs中创建数组。要从单个绑定中检索多个引用,请将ref绑定到提供更多灵活性的函数(这是一个新特性)
itemRefs不一定是一个数组,也可以是一个对象,其中引用由它们的迭代键设置。
如果需要的话,这也允许对项目引用进行reactive和watched。
$attrs 包含class和style
class和style在Vue 2的虚拟DOM实现中得到了一些特殊的处理。由于这个原因,它们没有包含在attrs中。
当使用inheritAttrs: false时,会出现这样的副作用:$attrs中的属性不再自动添加到根元素,而是由开发人员决定添加到哪里。但是class和style不是$attrs的一部分,仍然会被应用到组件的根元素上。
在Vue 3中,$attrs现在包含传递给组件的所有属性,包括class和style。
<template>
<label>
<input type="text" v-bind="$attrs" />
</label>
</template>
<script>
export default {
inheritAttrs: false
}
</script>
<!--这样使用时-->
<my-component id="my-id" class="my-class"></my-component>
<!--vue2中渲染的Html如下-->
<label class="my-class">
<input type="text" id="my-id" />
</label>
<!--vue3中渲染的Html如下-->
<label>
<input type="text" id="my-id" class="my-class" />
</label>
注意:在使用inheritAttrs: false的组件中,确保样式仍然按照预期工作。如果以前依赖于类和样式的特殊行为,那么一些视觉效果可能会被破坏,因为这些属性现在可能被应用到另一个元素。
Custom Directives自定义指令
vue3中,指令的钩子函数已经被重命名,以更好地与组件的生命周期保持一致。
created:在应用元素的属性或事件监听器之前调用此函数。(新增)
bind → beforeMount
inserted → mounted
beforeUpdate:这是在更新元素之前调用的,很像组件生命周期钩子。(新增)
update → 有太多的相似之处更新,所以这是多余的,已删除。请使用updated代替。
componentUpdated → updated
beforeUnmount:类似于组件生命周期钩子,它将在卸载元素之前被调用。(新增)
unbind → unmounted
const MyDirective = {
beforeMount(el, binding, vnode, prevVnode) {},
mounted() {},
beforeUpdate() {}, // new
updated() {},
beforeUnmount() {}, // new
unmounted() {}
}
vue3中的指令可以这样使用
<p v-highlight="'yellow'">Highlight this text bright yellow</p>
const app = Vue.createApp({})
app.directive('highlight', {
beforeMount(el, binding, vnode) {
el.style.background = binding.value
}
})
Custom Elements Interop自定义元素交互操作
如果我们想添加一个在Vue外部定义的定制元素(例如使用Web Components API),我们需要“指示”Vue将其视为定制元素。让我们以下面的模板为例。
<plastic-button></plastic-button>
Vue 2中,通过Vue.config.ignoredElements将标签白名单为自定义元素
Vue.config.ignoredElements = ['plastic-button']
在Vue 3中,这个检查是在模板编译期间执行的。指示编译器将视为自定义元素:
如果使用构建步骤:将isCustomElement选项传递给Vue模板编译器。如果使用vue-loader,这应该通过vue-loader的compilerOptions选项传递
// in webpack config
rules: [
{
test: /\.vue$/,
use: 'vue-loader',
options: {
compilerOptions: {
isCustomElement: tag => tag === 'plastic-button'
}
}
}
// ...
]
如果使用动态模板编译,请通过app.config.isCustomElement传递:
const app = Vue.createApp({})
app.config.isCustomElement = tag => tag === 'plastic-button'
需要注意的是,运行时配置只影响运行时模板编译——它不会影响预编译模板。
Customized Built-in Elements 自定义的内置的元素
Custom Elements规范提供了一种将自定义元素作为自定义内置元素(打开新窗口)使用的方法,方法是将is属性添加到内置元素
在Vue3中,我们将Vue对is道具的特殊处理仅限于标签。
-
当在保留的标签上使用时,它的行为和2.x中的完全一样;
-
当在普通组件上使用时,它的行为将像一个普通属性:
<button is="plastic-button">Click Me!</button>
- 2.x行为:呈现bar组件。
- 3.x行为:渲染foo组件并传递is属性。
<foo is="bar" />
- 2.x行为:渲染plastic-button组件。
<button is="plastic-button">Click Me!</button>
- 3.x行为:通过调用来呈现本地按钮
document.createElement('button', { is: 'plastic-button' })
在Vue 2中,我们建议通过在本地标签上使用is道具来解决这些限制:
<table>
<tr is="blog-post-row"></tr>
</table>
Vue3中,随着is的行为改变,我们引入了一个新的指令v-is来处理这些情况:
<table>
<tr v-is="'blog-post-row'"></tr>
</table>
v-is函数就像动态的vue2中的 :is binding,所以要通过注册名来渲染组件,它的值应该是一个JavaScript字符串。
data option
vue2中,data option只能被定义成object或function
vue3中,data option 已经标准化,只接受返回对象的函数
<script>
import { createApp } from 'vue'
createApp({
data() {
return {
apiKey: 'a1b2c3'
}
}
}).mount('#app')
</script>
Mixin合并行为改变
当一个组件的data()及其mixins或extends基类被合并时,vue3中的合并是浅层执行的
const Mixin = {
data() {
return {
user: {
name: 'Jack',
id: 1
}
}
}
}
const CompA = {
mixins: [Mixin],
data() {
return {
user: {
id: 2
}
}
}
}
// 在vue2中合并的结果是:
{
user: {
id: 2,
name: 'Jack'
}
}
// 在vue3中合并的结果是:
{
user: {
id: 2
}
}
Functional Components 函数组件
在Vue 2中,功能组件有两个主要用例: 使用组件,它负责渲染出适当的标题(例如,h1, h2, h3等),这可以在2中写成一个单文件组件
// Vue 2 Functional Component Example
export default {
functional: true,
props: ['level'],
render(h, { props, data, children }) {
return h(`h${props.level}`, data, children)
}
}
或者
<!--Vue 2 Functional Component Example with -->
<template functional>
<component
:is="`h${props.level}`"
v-bind="attrs"
v-on="listeners"
/>
</template>
<script>
export default {
props: ['level']
}
</script>
现在在Vue 3中,所有功能组件都是用普通函数创建的。换句话说,不需要定义{functional: true}组件选项。
它们将收到两个参数:props和context。context参数是一个对象,它包含组件的attrs、slot和emit属性。
此外,h现在是全局导入的,而不是在渲染函数中隐式地提供h。
import { h } from 'vue'
const DynamicHeading = (props, context) => {
return h(`h${props.level}`, context.attrs, context.slots)
}
DynamicHeading.props = ['level']
export default DynamicHeading
Single File Components (SFCs)
functional属性在<template>中被移除
监听器现在作为$attrs的一部分传递,可以删除
<template>
<component
v-bind:is="`h${$props.level}`"
v-bind="$attrs"
/>
</template>
<script>
export default {
props: ['level']
}
</script>
Global API 全局API
-
createApp 调用createApp会返回一个app实例,这是Vue 3中的一个新概念
import { createApp } from 'vue' const app = createApp({})所有全局改变Vue行为的api现在都移动到应用实例中。
2.x Global API 3.x Instance 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 Vue.prototype app.config.globalProperties -
Provide / Inject
Vue 3应用实例还可以提供依赖项,这些依赖项可以被应用内部的任何组件注入
// in the entry app.provide('guide', 'Vue 3 Guide') // in a child component export default { inject: { book: { from: 'guide' } }, template: `<div>{{ book }}</div>` }在编写插件时,使用provide尤其有用,它可以替代全局属性
Global API Treeshaking
在Vue 3中,全局api和内部api都进行了重组,因此,全局api现在只能作为ES模块构建的命名导出访问。例如:
import { nextTick } from 'vue'
nextTick(() => {
// something DOM-related
})
直接调用Vue.nextTick()现在会导致的undefined is not a function错误。
有了这个更改,如果模块绑定器tree-shaking,Vue应用程序中不使用的全局api将从最终绑定包中删除,从而得到最佳的文件大小。
以下vue2中的指令都收到了tree-shaking的影响:
Vue.nextTickVue.observable(被Vue.reactive替换)Vue.versionVue.compile(仅在完整版本中)Vue.set(仅在compat构建中)Vue.delete(仅在compat构建中)
内部变化
除了公共api之外,许多内部组件/帮助程序现在也被导出为命名导出。这允许编译器输出仅在使用特性时才导入的代码。例如:
<transition>
<div v-show="ok">hello</div>
</transition>
被编译成类似于下面的内容:
import { h, Transition, withDirectives, vShow } from 'vue'
export function render() {
return h(Transition, [withDirectives(h('div', 'hello'), [[vShow, this.ok]])])
}
这实际上意味着只有当应用程序真正使用转换组件时,才会导入它。换句话说,如果应用程序没有任何组件,那么支持该特性的代码将不会出现在最终的bundle中。
通过全局tree-shaking,用户只为他们实际使用的功能“付费”。更好的是,如果知道可选特性不会增加不使用它们的应用程序的包大小,那么框架大小就不再是未来额外核心特性的考虑因素了。
如果你使用像webpack这样的模块包,这可能会导致Vue的源代码被绑定到插件中,这通常不是你所期望的。防止这种情况发生的一个常见做法是配置模块绑定程序,将Vue从最终的绑定包中排除。在webpack的情况下,你可以使用externals(打开新窗口)配置选项:
// webpack.config.js
module.exports = {
/*...*/
externals: {
vue: 'Vue'
}
}
这将告诉webpack把Vue模块作为一个外部库,而不是bundle它。
keyattribute
在vue3中,不再推荐在v-if/v-else/v-else-if分支上使用key属性,因为如果你不提供唯一的键,现在在条件分支上会自动生成它们。
<template v-for>
Vue 2中<template>标签不能有键值。相反,您可以将键放在它的每个子节点上:
<!-- Vue 2.x -->
<template v-for="item in list">
<div :key="item.id">...</div>
<span :key="item.id">...</span>
</template>
Vue 3中,key应该放在<template>标签上。
<!-- Vue 3.x -->
<template v-for="item in list" :key="item.id">
<div>...</div>
<span>...</span>
</template>
当使用<template v-for>和一个子元素使用v-if时,键应该移动到<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>
KeyCode Modifiers 键码修饰符
因为KeyboardEvent。keyCode已被弃用(打开新窗口),Vue 3继续支持它也不再有意义。因此,现在建议对任何希望用作修饰符的键使用kebab-case名称。
<!-- Vue 3 Key Modifier on v-on -->
<input v-on:keyup.delete="confirmDelete" />
Render Function API
在Vue2中,render函数将自动接收h函数(这是createElement的常规别名)作为参数:
// Vue 2 Render Function Example
export default {
render(h) {
return h('div')
}
}
在Vue3中, h现在是全局导入的,而不是自动作为参数传递。
// Vue 3 Render Function Example
import { h } from 'vue'
export default {
render() {
return h('div')
}
}
注册组件
在Vue2中,如果一个组件已经注册,render函数将组件的名称以字符串形式传递给第一个参数:
// 2.x
Vue.component('button-counter', {
data() {
return {
count: 0
}
}
template: `
<button @click="count++">
Clicked {{ count }} times.
</button>
`
})
export default {
render(h) {
return h('button-counter')
}
}
在Vue3中,由于vnode与上下文无关,不能再使用字符串ID隐式地查找已注册的组件,这里我们需要使用一个导入的resolveComponent方法:
// 3.x
import { h, resolveComponent } from 'vue'
export default {
setup() {
const ButtonCounter = resolveComponent('button-counter')
return () => h(ButtonCounter)
}
}
slots统一
当使用渲染函数时,例如 h,在Vue2中用于定义内容节点上的slot数据属性。
// 2.x Syntax
h(LayoutComponent, [
h('div', { slot: 'header' }, this.header),
h('div', { slot: 'content' }, this.content)
])
此外,当引用作用域槽时,可以使用以下语法引用它们
// 2.x Syntax
this.$scopedSlots.header
在Vue3中, slot被定义为当前节点作为对象的子节点
// 3.x Syntax
h(LayoutComponent, {}, {
header: () => h('div', this.header),
content: () => h('div', this.content)
})
当需要以编程方式引用限定范围的插槽时,它们现在统一到$slots选项中。
// 2.x Syntax
this.$scopedSlots.header
// 3.x Syntax
this.$slots.header()
Transition Class 变化
v-enter转换类被重命名为v-enter-from, v-leave转换类被重命名为v-leave-from。
/* vue2中*/
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
/* vue3中*/
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
Transition Group Root Element
默认情况下不再渲染根元素,但仍然可以用tag 创建。
在Vue 2中,,像其他定制组件一样,需要一个根元素,默认情况下是,但可以通过tag定制。
<transition-group tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</transition-group>
在Vue 3中,我们提供了fragment支持,组件不再需要根节点。因此,在默认情况下不再渲染。
如果您已经在Vue 2代码中定义了标记道具,就像上面的例子一样,一切都将像以前一样工作; 如果你没有定义,而你的样式或其他行为依赖于根元素的存在来正常工作,只需添加tag="span"到
<transition-group tag="span">
<!-- -->
</transition-group>
v-model
- 当在自定义组件上使用时,v-model的prop和event的默认名称会改变
- props: value -> modelValue;
- event: input->update: modelValue;
- v-bind的.sync修饰符和组件model option被移除,并被v-model的一个参数替换;
- 新功能:现在可以在同一个组件上创建多个v-model绑定;
- 新功能:增加了创建自定义v-model的能力。
在Vue2中,在组件上使用v-model等价于传递一个value并发出一个input事件:
<ChildComponent v-model="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
如果我们想改变prop或event的名称,我们需要在ChildComponent组件中添加一个model option:
<!-- ParentComponent.vue -->
<ChildComponent v-model="pageTitle" />
// ChildComponent.vue
export default {
model: {
prop: 'title',
event: 'change'
},
props: {
// this allows using the `value` prop for a different purpose
value: String,
// use `title` as the prop which take the place of `value`
title: {
type: String,
default: 'Default title'
}
}
}
所以,v-model在这里可以简写成:
<ChildComponent :title="pageTitle" @change="pageTitle = $event" />
在某些情况下,我们可能需要对一个prop进行“双向绑定”(有时需要为不同的prop添加现有的v-model)。为此,我们建议以update:myPropName模式发出事件。例如,在上一个带有title prop的例子中,我们可以通过以下方式来传达给ChildComponent赋值的意图:
this.$emit('update:title', newValue)
然后parent可以监听该事件并更新本地数据属性。例如:
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
可以简写成 .sync的修改方式:
<ChildComponent :title.sync="pageTitle" />
在vue3中,在自定义组件上的 v-model等价于传递modelValue prop并发出一个update:modelValue事件:
<ChildComponent v-model="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>
v-model arguments
要更改一个 model的名称, 而不是model component选项 , 现在我们传递一个参数给v-model:
<ChildComponent v-model:title="pageTitle" />
<!-- would be shorthand for: -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
这也可以作为.sync修饰符的替代,并允许我们在自定义组件上拥有多个v-model。
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
<!-- would be shorthand for: -->
<ChildComponent
:title="pageTitle"
@update:title="pageTitle = $event"
:content="pageContent"
@update:content="pageContent = $event"
/>
v-model修改器
除了vue2硬编码的v-model修饰符,如 .trim,现在是vue3支持自定义修饰符:
<ChildComponent v-model.capitalize="pageTitle" />
v-if 和 v-for的优先级
在vue3中,v-if总是比v-for优先级高
v-bind合并行为
在Vue2中,如果一个元素同时定义了v-bind="object"和一个相同的单个属性,那么单个属性将总是覆盖对象中的绑定。
<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="red"></div>
在Vue3中,如果一个元素同时定义了v-bind="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>
watch监听数组
在Vue3中,当使用watch选项来监视一个数组时,回调只会在数组被替换时触发。换句话说,watch回调将不再当数组突变时被触发。若要在突变时触发,必须指定deep选项。
watch: {
bookList: {
handler(val, oldVal) {
console.log('book list changed')
},
deep: true
},
}