1. 异步组件(新增
)
- 通过defineAsyncComponent方法显式地定义异步组件
- component 选项被重命名为 loader
- Loader 函数本身不再接收 resolve 和 reject 参数,且必须返回一个 Promise
- 注意:VueRouter支持一个类似的机制来异步加载路由组件,也就是俗称的懒加载。尽管类似,但是这个功能和Vue支持的异步组件是不同的
- 当用 Vue Router 配置路由组件时,你不应该使用 defineAsyncComponent
-
Vue2
以前,异步组件是通过将组件定义为返回 Promise 的函数来创建的,例如:
const asyncModal = () => import('./Modal.vue')
或者,对于带有选项的更高阶的组件语法
const asyncModal = {
component: () => import('./Modal.vue'),
delay: 200,
timeout: 3000,
error: ErrorComponent,
loading: LoadingComponent
}
-
Vue3
现在,在 Vue 3 中,由于函数式组件被定义为纯函数,因此异步组件需要通过将其包裹在新的 defineAsyncComponent 助手方法中来显式地定义
import { defineAsyncComponent } from 'vue'
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'
// 不带选项的异步组件
const asyncModal = defineAsyncComponent(() => import('./Modal.vue'))
// 带选项的异步组件
const asyncModalWithOptions = defineAsyncComponent({
loader: () => import('./Modal.vue'),
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent
})
此外,与 2.x 不同,loader 函数不再接收 resolve 和 reject 参数,且必须始终返回 Promise
// 2.x 版本
const oldAsyncComponent = (resolve, reject) => {
/* ... */
}
// 3.x 版本
const asyncComponent = defineAsyncComponent(
() =>
new Promise((resolve, reject) => {
/* ... */
})
)
2. this.$children(移除
)
-
Vue2
在 2.x 中,开发者可以使用 this.$children 访问当前实例的直接子组件:
Change logo
-
Vue3
在 3.x 中,refs
3. 使用 vue: 前缀来解决 DOM 内模板解析问题(非兼容
)
提示:本节仅影响直接在页面的 HTML 中写入 Vue 模板的情况。 在 DOM 模板中使用时,模板受原生 HTML 解析规则的约束。一些 HTML 元素,例如 ul、ol、table 和 select 对它们内部可以出现的元素有限制,以及一些像 li、tr、和 option 只能出现在特定的其他元素中
-
Vue2
-
Vue3
4. Data 选项(非兼容
)
非兼容:组件选项 data 的声明不再接收纯 JavaScript object,而是接收一个 function。
非兼容:当合并来自 mixin 或 extend 的多个 data 返回值时,合并操作现在是浅层次的而非深层次的 (只合并根级属性)。
-
Vue2
在 2.x 中,开发者可以通过 object 或者是 function 定义 data 选项。
-
Vue3
在 3.x 中,data 选项已标准化为只接受返回 object 的 function。
使用上面的示例,代码只可能有一种实现:
5. emits选项(新增
)
Vue 3 现在提供一个 emits 选项,和现有的 props 选项类似。这个选项可以用来定义一个组件可以向其父组件触发的事件
强烈建议使用 emits 记录每个组件所触发的所有事件。这尤为重要,因为我们移除了 .native 修饰符。任何未在 emits 中声明的事件监听器都会被算入组件的 $attrs,并将默认绑定到组件的根节点上
-
Vue2
在 Vue 2 中,你可以定义一个组件可接收的 prop,但是你无法声明它可以触发哪些事件:
{{ text }}
-
Vue3
和 prop 类似,现在可以通过 emits 选项来定义组件可触发的事件:
{{ text }}
6. 事件API(非兼容
)
off 和 $once 实例方法已被移除,组件实例不再实现事件触发接口
-
Vue2
在 2.x 中,Vue 实例可用于触发由事件触发器 API 通过指令式方式添加的处理函数 (off 和 $once)。这可以用于创建一个事件总线,以创建在整个应用中可用的全局事件监听器:
// eventBus.js
const eventBus = new Vue()
export default eventBus
// ChildComponent.vue
import eventBus from './eventBus'
export default {
mounted() {
// 添加 eventBus 监听器
eventBus.$on('custom-event', () => {
console.log('Custom event triggered!')
})
},
beforeDestroy() {
// 移除 eventBus 监听器
eventBus.$off('custom-event')
}
}
// ParentComponent.vue
import eventBus from './eventBus'
export default {
methods: {
callGlobalCustomEvent() {
eventBus.$emit('custom-event') // 当 ChildComponent 已被挂载时,控制台中将显示一条消息
}
}
}
-
Vue3
根组件事件
createApp(App, {
// 监听 'expand' 事件
onExpand() {
console.log('expand')
}
})
事件总线
// eventBus.js
import emitter from 'tiny-emitter/instance'
export default {
$on: (...args) => emitter.on(...args),
$once: (...args) => emitter.once(...args),
$off: (...args) => emitter.off(...args),
$emit: (...args) => emitter.emit(...args),
}
7. 过滤器(移除
)
-
Vue2
在 2.x 中,开发者可以使用过滤器来处理通用文本格式,虽然这看起来很方便,但它需要一个自定义语法,打破了大括号内的表达式“只是 JavaScript”的假设,这不仅有学习成本,而且有实现成本
Bank Account Balance
{{ accountBalance | currencyUSD }}
-
Vue3
在 3.x 中,过滤器已移除,且不再支持。取而代之的是,我们建议用方法调用或计算属性来替换它们。以上面的案例为例,以下是一种实现方式。
Bank Account Balance
{{ accountInUSD }}
8. 片段(新增
)
Vue 3 现在正式支持了多根节点的组件,也就是片段!
-
Vue2
在 2.x 中,由于不支持多根节点组件,当其被开发者意外地创建时会发出警告。结果是,为了修复这个问题,许多组件被包裹在了一个 div 中。
...
...
-
Vue3
在 3.x 中,组件可以包含多个根节点!但是,这要求开发者显式定义 attribute 应该分布在哪里。
...
...
9. propsData(移除
)
propsData 选项之前用于在创建 Vue 实例的过程中传入 prop,现在它被移除了。如果想为 Vue 3 应用的根组件传入 prop,请使用 createApp 的第二个参数
-
Vue2
在 2.x 中,我们可以在创建 Vue 实例的时候传入 prop:
const Comp = Vue.extend({
props: ['username'],
template: '{{ username }}'
})
new Comp({
propsData: {
username: 'Evan'
}
})
-
Vue3
propsData 选项已经被移除。如果你需要在实例创建时向根组件传入 prop,你应该使用 createApp 的第二个参数:
const app = createApp(
{
props: ['username'],
template: '{{ username }}'
},
{ username: 'Evan' }
)
10. 在 prop 的默认函数中访问this(非兼容
)
生成 prop 默认值的工厂函数不再能访问 this。
取而代之的是:
- 组件接收到的原始 prop 将作为参数传递给默认函数;
- inject API 可以在默认函数中使用。
import { inject } from 'vue'
export default {
props: {
theme: {
default (props) {
// `props` 是传递给组件的、
// 在任何类型/默认强制转换之前的原始值,
// 也可以使用 `inject` 来访问注入的 property
return inject('theme', 'default-theme')
}
}
}
}
11. 渲染函数API(非兼容
)
此更改不会影响 template 用户。
以下是更改的简要总结:
- h 现在是全局导入,而不是作为参数传递给渲染函数
- 更改渲染函数参数,使其在有状态组件和函数组件的表现更加一致
- VNode 现在有一个扁平的 prop 结构
-
Vue2
export default {
render(h) {
return h('div')
}
}
export default {
render(h) {
return h('div')
}
}
{
staticClass: 'button',
class: { 'is-outlined': isOutlined },
staticStyle: { color: '#34495E' },
style: { backgroundColor: buttonColor },
attrs: { id: 'submit' },
domProps: { innerHTML: '' },
on: { click: submitForm },
key: 'submit-button'
}
Vue.component('button-counter', {
data() {
return {
count: 0
}
},
template: `
`
})
export default {
render(h) {
return h('button-counter')
}
}
-
Vue3
import { h } from 'vue'
export default {
render() {
return h('div')
}
}
import { h, reactive } from 'vue'
export default {
setup(props, { slots, attrs, emit }) {
const state = reactive({
count: 0
})
function increment() {
state.count++
}
// 返回渲染函数
return () =>
h(
'div',
{
onClick: increment
},
state.count
)
}
}
{
class: ['button', { 'is-outlined': isOutlined }],
style: [{ color: '#34495E' }, { backgroundColor: buttonColor }],
id: 'submit',
innerHTML: '',
onClick: submitForm,
key: 'submit-button'
}
import { h, resolveComponent } from 'vue'
export default {
setup() {
const ButtonCounter = resolveComponent('button-counter')
return () => h(ButtonCounter)
}
}
12. 插槽统一(非兼容
)
此更改统一了 3.x 中的普通插槽和作用域插槽。
以下是变化的变更总结:
- this.$slots 现在将插槽作为函数公开
- 非兼容:移除 this.$scopedSlots
-
Vue2
// 2.x 语法
h(LayoutComponent, [
h('div', { slot: 'header' }, this.header),
h('div', { slot: 'content' }, this.content)
])
// 2.x 语法
this.$scopedSlots.header
-
Vue3
// 3.x Syntax
h(LayoutComponent, {}, {
header: () => h('div', this.header),
content: () => h('div', this.content)
})
// 2.x 语法
this.$scopedSlots.header
// 3.x 语法
this.$slots.header()
13. 过渡的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;
}
14. Transition 作为根节点(非兼容
)
当使用
作为根结点的组件从外部被切换时将不再触发过渡效果
-
Vue2
hello
-
Vue3
hello
15. Transition Group 根元素(非兼容
)
不再默认渲染根元素,但仍然可以用 tag attribute 创建根元素
-
Vue2
{{ item }}
-
Vue3
16. 移除v-on.native修饰符(非兼容
)
v-on 的 .native 修饰符已被移除
-
Vue2
-
Vue3
// MyComponent.vue
17. v-model(非兼容
)
以下是对变化的总体概述:
- 非兼容:用于自定义组件时,v-model prop 和事件默认名称已更改:
-
- prop:value -> modelValue;
-
- 事件:input -> update:modelValue;
- 非兼容:v-bind 的 .sync 修饰符和组件的 model 选项已移除,可在 v-model 上加一个参数代替;
- 新增:现在可以在同一个组件上使用多个 v-model 绑定;
- 新增:现在可以自定义 v-model 修饰符。
在 Vue 2.0 发布后,开发者使用 v-model 指令时必须使用名为 value 的 prop。如果开发者出于不同的目的需要使用其他的 prop,他们就不得不使用 v-bind.sync。此外,由于v-model 和 value 之间的这种硬编码关系的原因,产生了如何处理原生元素和自定义元素的问题。
在 Vue 2.2 中,我们引入了 model 组件选项,允许组件自定义用于 v-model 的 prop 和事件。但是,这仍然只允许在组件上使用一个 v-model。
在 Vue 3 中,双向数据绑定的 API 已经标准化,以减少开发者在使用 v-model 指令时的混淆,并且更加灵活。
-
Vue2
在 2.x 中,在组件上使用 v-model 相当于绑定 value prop 并触发 input 事件:
如果想要更改 prop 或事件名称,则需要在 ChildComponent 组件中添加 model 选项:
// ChildComponent.vue
export default {
model: {
prop: 'title',
event: 'change'
},
props: {
// 这将允许 `value` 属性用于其他用途
value: String,
// 使用 `title` 代替 `value` 作为 model 的 prop
title: {
type: String,
default: 'Default title'
}
}
}
所以,在这个例子中 v-model 是以下的简写:
使用 v-bind.sync
在某些情况下,我们可能需要对某一个 prop 进行“双向绑定”(除了前面用 v-model 绑定 prop 的情况)。为此,我们建议使用 update:myPropName 抛出事件。例如,对于在上一个示例中带有 title prop 的 ChildComponent,我们可以通过下面的方式将分配新 value 的意图传达给父级:
this.$emit('update:title', newValue)
然后父组件可以在需要时监听该事件,并更新本地的 data property。例如:
为了方便起见,我们可以使用 .sync 修饰符来缩写,如下所示:
-
Vue3
在 3.x 中,自定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件:
v-model 参数
若需要更改 model 的名称,现在我们可以为 v-model 传递一个参数,以作为组件内 model 选项的替代:
这也可以作为 .sync 修饰符的替代,而且允许我们在自定义组件上使用多个 v-model。
v-model 修饰符
除了像 .trim 这样的 2.x 硬编码的 v-model 修饰符外,现在 3.x 还支持自定义修饰符:
18. v-if 与 v-for 的优先级对比(非兼容
)
非兼容:两者作用于同一个元素上时,v-if 会拥有比 v-for 更高的优先级。
Vue.js 中使用最多的两个指令就是 v-if 和 v-for,因此开发者们可能会想要同时使用它们。虽然不建议这样做,但有时确实是必须的,于是我们想提供有关其工作方式的指南。
-
Vue2
2.x 版本中在一个元素上同时使用 v-if 和 v-for 时,v-for 会优先作用
-
Vue3
3.x 版本中 v-if 总是优先于 v-for 生效
19. v-bind 合并行为(非兼容
)
不兼容:v-bind 的绑定顺序会影响渲染结果
在一个元素上动态绑定 attribute 时,同时使用 v-bind="object" 语法和独立 attribute 是常见的场景。然而,这就引出了关于合并的优先级的问题
-
Vue2
在 2.x 中,如果一个元素同时定义了 v-bind="object" 和一个相同的独立 attribute,那么这个独立 attribute 总是会覆盖 object 中的绑定。
-
Vue3
在 3.x 中,如果一个元素同时定义了 v-bind="object" 和一个相同的独立 attribute,那么绑定的声明顺序将决定它们如何被合并。换句话说,相对于假设开发者总是希望独立 attribute 覆盖 object 中定义的内容,现在开发者能够对自己所希望的合并行为做更好的控制。
20. VNode 生命周期事件(非兼容
)
在 Vue 2 中,我们可以通过事件来监听组件生命周期中的关键阶段。这些事件名都是以 hook: 前缀开头,并跟随相应的生命周期钩子的名字。
在 Vue 3 中,这个前缀已被更改为 vnode-。额外地,这些事件现在也可用于 HTML 元素,和在组件上的用法一样
-
Vue2
在 Vue 2 中,这些事件名和相应的生命周期钩子一致,并带有 hook: 前缀:
-
Vue3
在 Vue 3 中,事件名附带的是 vnode- 前缀:
21. 侦听数组(非兼容
)
非兼容: 当侦听一个数组时,只有当数组被替换时才会触发回调。如果你需要在数组被改变时触发回调,必须指定 deep 选项
-
Vue2
-
Vue3
当使用 watch 选项侦听数组时,只有在数组被替换时才会触发回调。换句话说,在数组被改变时侦听回调将不再被触发。要想在数组被改变时触发侦听回调,必须指定 deep 选项。
watch: {
bookList: {
handler(val, oldVal) {
console.log('book list changed')
},
deep: true
},
}