注:其中某些(...)和(???)是作者待补充(没咋看明白的)的
迁移指南
1. 要从单个绑定获取多个 ref,请将 ref 绑定到一个更灵活的函数上
<template>
<div v-for="item in list" :ref="setItemRef"></div>
</template>
import { onBeforeUpdate, onUpdated } from 'vue'
export default {
setup() {
let itemRefs = []
const setItemRef = el => {
if (el) {
itemRefs.push(el)
}
}
onBeforeUpdate(() => {
itemRefs = []
})
onUpdated(() => {
console.log(itemRefs)
})
return {
setItemRef
}
}
}
2. defineAsyncComponent
3. $attrs 现在包含了所有传递给组件的 attribute,包括 class 和 style
4. $children 实例 property 已从 Vue 3.0 中移除,不再支持
如果你需要访问子组件实例,我们建议使用 $refs。
5. 自定义指令
为自定义指令创建了一个更具凝聚力的 API !!
- ==created== - 新增!在元素的 attribute 或事件监听器被应用之前调用。
- bind → ==beforeMount==
- inserted → ==mounted==
- ==beforeUpdate==:新增!在元素本身被更新之前调用,与组件的生命周期钩子十分相似。
- update → 移除!该钩子与 updated 有太多相似之处,因此它是多余的。请改用 updated。
- componentUpdated → ==updated==
- ==beforeUnmount==:新增!与组件的生命周期钩子类似,它将在元素被卸载之前调用。
- unbind -> ==unmounted==
最终的 API 如下:
const MyDirective = {
created(el, binding, vnode, prevVnode) {}, // 新增
beforeMount() {},
mounted() {},
beforeUpdate() {}, // 新增
updated() {},
beforeUnmount() {}, // 新增
unmounted() {}
}
在 Vue 3 中,实例现在是 binding 参数的一部分:
mounted(el, binding, vnode) {
const vm = binding.instance
}
6. 与自定义元素的互操作性(???)
7. Data选项
data 选项已标准化为只接受返回 object 的 function
<script>
import { createApp } from 'vue'
createApp({
data() {
return {
apiKey: 'a1b2c3'
}
}
}).mount('#app')
</script>
Mixin 合并行为变更
const Mixin = {
data() {
return {
user: {
name: 'Jack',
id: 1
}
}
}
}
const CompA = {
mixins: [Mixin],
data() {
return {
user: {
id: 2
}
}
}
}
在 3.0 中,其结果将会是:
{
"user": {
"id": 2
}
}
8. emits选项
和 prop 类似,现在可以通过 emits 选项来定义组件可触发的事件:
<template>
<div>
<p>{{ text }}</p>
<button v-on:click="$emit('accepted')">OK</button>
</div>
</template>
<script>
export default {
props: ['text'],
emits: ['accepted']
}
</script>
9. 事件订阅用法
==我们从实例中完全移除了 off 和 emit 仍然包含于现有的 API 中,因为它用于触发由父组件声明式添加的事件处理函数。==
事件总线模式
事件总线模式可以被替换为使用外部的、实现了事件触发器接口的库,例如 mitt 或tiny-emitter。
// 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),
}
有多种事件总线的替代方案:
- Prop 和事件应该是父子组件之间沟通的首选。兄弟节点可以通过它们的父节点通信。
- Provide 和 inject 允许一个组件与它的插槽内容进行通信。这对于总是一起使用的紧密耦合的组件非常有用。
- provide/inject 也能够用于组件之间的远距离通信。它可以帮助避免“prop 逐级透传”,即 prop 需要通过许多层级的组件传递下去,但这些组件本身可能并不需要那些 prop。
- Prop 逐级透传也可以通过重构以使用插槽来避免。如果一个中间组件不需要某些 prop,那么表明它可能存在关注点分离的问题。在该类组件中使用 slot 可以允许父节点直接为它创建内容,因此 prop 可以被直接传递而不需要中间组件的参与。
- 全局状态管理,比如 Vuex。
10. 过滤器 filters 被移除
我们建议用方法调用或计算属性来替换它们。
全局过滤器
你可以通过全局属性(globalProperties)以让它能够被所有组件使用到:
// main.js
const app = createApp(App)
app.config.globalProperties.$filters = {
currencyUSD(value) {
return '$' + value
}
}
11. Vue 3 现在正式支持了多根节点的组件,也就是片段!(...)
12. 函数式组件
Vue 2.X 版本
export default {
functional: true,
props: ['level'],
render(h, { props, data, children }) {
return h(`h${props.level}`, data, children)
}
}
单文件组件(Vue 2.X 版本)
<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、slots 和 emit property。
此外,h 现在是全局导入的,而不是在 render 函数中隐式提供。
Vue 3.X 版本
import { h } from 'vue'
const DynamicHeading = (props, context) => {
return h(`h${props.level}`, context.attrs, context.slots)
}
DynamicHeading.props = ['level']
export default DynamicHeading
单文件组件(Vue 3.X 版本)
<template>
<component
v-bind:is="`h${$props.level}`"
v-bind="$attrs"
/>
</template>
<script>
export default {
props: ['level']
}
</script>
主要的区别在于:
- 从 template 中移除 functional attribute
- ==listeners 现在作为 $attrs 的一部分传递==,可以将其删除
13. 全局 API
一个新的全局 API —— createApp
import { createApp } from 'vue'
const app = createApp({})
- 挂载根实例
import { createApp } from 'vue'
import MyApp from './MyApp.vue'
const app = createApp(MyApp)
app.mount('#app')
| 2.x 全局 API | 3.x 实例 API (app) |
|---|---|
| Vue.config | app.config |
| Vue.config.productionTip | 移除 (见下方) |
| Vue.config.ignoredElements | app.config.compilerOptions.isCustomElement (见下方) |
| Vue.component | app.component |
| Vue.directive | app.directive |
| Vue.mixin | app.mixin |
| Vue.use app.use | (见下方) |
| Vue.prototype | app.config.globalProperties (见下方) |
| Vue.extend | 移除 (见下方) |
- ==Vue.prototype 替换为 config.globalProperties==
// 之前 - Vue 2
Vue.prototype.$http = () => {}
// 之后 - Vue 3
const app = createApp({})
app.config.globalProperties.$http = () => {}
- Vue.extend 移除
// 之前 - Vue 2
// 创建构造器
const Profile = Vue.extend({
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data() {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
})
// 创建一个 Profile 的实例,并将它挂载到一个元素上
new Profile().$mount('#mount-point')
// 之后 - Vue 3
const Profile = {
template: '<p>{{firstName}} {{lastName}} aka {{alias}}</p>',
data() {
return {
firstName: 'Walter',
lastName: 'White',
alias: 'Heisenberg'
}
}
}
Vue.createApp(Profile).mount('#mount-point')
- 组件继承
在 Vue 3 中,我们强烈建议==使用 组合式 API 来替代继承与 mixin==。如果因为某种原因仍然需要使用组件继承,你可以使用 extends 选项 来代替 Vue.extend。
- 插件的使用
由于 use 全局 API 在 Vue 3 中已无法使用,因此此方法将无法正常工作,并且调用 Vue.use() 现在将触发一个警告。取而代之的是,开发者必须在应用实例上显式指定使用此插件:
const app = createApp(MyApp)
app.use(VueRouter)
- Provide / Inject
Vue 3 应用实例也提供了可被应用内任意组件注入的依赖项:
// 在入口中
app.provide('guide', 'Vue 3 Guide')
// 在子组件中
export default {
inject: {
book: {
from: 'guide'
}
},
template: `<div>{{ book }}</div>`
}
==在编写插件时使用 provide 将尤其有用,可以替代 globalProperties。==
14. Tree Shaking (...)
如 webpack 这样的模块打包工具支持 tree-shaking,这是表达“消除死代码”的一个花哨术语。遗憾的是,由于之前的 Vue 版本中的代码编写方式,如 Vue.nextTick() 这样的全局 API 是不支持 tree-shake 的,不管它们实际上是否被使用了,都会被包含在最终的打包产物中。
在 Vue 3 中,全局和内部 API 都经过了重构,并考虑到了 tree-shaking 的支持。因此,对于 ES 模块构建版本来说,全局 API 现在通过具名导出进行访问。
15. 内联模板(???)
16. Key
==对于 v-if/v-else/v-else-if 的各分支项 key 将不再是必须的,因为现在 Vue 会自动生成唯一的 key。(本来我也没加过....)==
结合 template v-for
在 Vue 2.x 中,template 标签不能拥有 key。不过,你可以为其每个子节点分别设置 key。
<!-- Vue 2.x -->
<template v-for="item in list">
<div :key="'heading-' + item.id">...</div>
<span :key="'content-' + item.id">...</span>
</template>
在 Vue 3.x 中,key 则应该被设置在 template 标签上
<!-- Vue 3.x -->
<template v-for="item in list" :key="item.id">
<div>...</div>
<span>...</span>
</template>
17. 按键修饰符
从 KeyboardEvent.keyCode ==已被废弃==开始
<!-- 键码版本 -->
<input v-on:keyup.13="submit" />
<!-- 别名版本 -->
<input v-on:keyup.enter="submit" />
现在建议对任何要用作修饰符的键使用 ==kebab-cased (短横线)== 名称。
<!-- Vue 3 在 v-on 上使用按键修饰符 -->
<input v-on:keyup.page-down="nextPage">
<!-- 同时匹配 q 和 Q -->
<input v-on:keypress.q="quit">
<!-- 对于某些标点符号键 -->
<input v-on:keypress.,="commaPress">
语法的限制导致某些特定字符无法被匹配,比如 "、'、/、=、> 和 .。对于这些字符,你应该在监听器内使用 event.key 代替。
18. 挂载API变化 (...)
19. propsData 移除
在 2.x 中,我们可以在创建 Vue 实例的时候传入 prop:
const Comp = Vue.extend({
props: ['username'],
template: '<div>{{ username }}</div>'
})
new Comp({
propsData: {
username: 'Evan'
}
})
如果你需要在实例创建时向根组件传入 prop,你应该使用 createApp 的第二个参数:
const app = createApp(
{
props: ['username'],
template: '<div>{{ username }}</div>'
},
{ username: 'Evan' }
)
20. 在 prop 的默认函数中访问 this (???)
21. 渲染函数 API
- 渲染函数参数
<!-- h 函数现在是全局导入的,而不是作为参数自动传递。-->
// Vue 3 渲染函数示例
import { h } from 'vue'
export default {
render() {
return h('div')
}
}
- 渲染函数签名更改
由于 ==render 函数不再接收任何参数==,它将主要在 setup() 函数内部使用。这还有一个好处:可以访问在作用域中声明的响应式状态和函数,以及传递给 setup() 的参数。
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
)
}
}
- 注册组件
// 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')
}
}
在 3.x 中,由于 VNode 是上下文无关的,不能再用字符串 ID 隐式查找已注册组件。取而代之的是,需要使用一个导入的 resolveComponent 方法:
// 3.x
import { h, resolveComponent } from 'vue'
export default {
setup() {
const ButtonCounter = resolveComponent('button-counter')
return () => h(ButtonCounter)
}
}
22. 插槽统一 (...)
==在 3.x 中,将所有 this.slots。==
23. Suspense (实验性)
24. 过渡
- 过渡的 class 名更改
过渡类名 v-enter 修改为 v-enter-from、过渡类名 v-leave 修改为 v-leave-from
- Transition 作为根节点
当使用 作为根结点的组件从外部被切换时将不再触发过渡效果。
- Transition Group 根元素
不再默认渲染根元素,但仍然可以用 tag attribute 创建根元素。
25. 移除 v-on.native 修饰符
v-on 的 .native 修饰符已被移除。同时,==新增的 emits 选项允许子组件定义真正会被触发的事件==。
26. v-model
当在组件上使用v-model时
Vue 2.x
==在组件上使用 v-model 相当于绑定 value prop 并触发 input 事件==
<ChildComponent v-model="pageTitle" />
<!-- 是以下的简写: -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
如果想要更改 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-bind.sync
在某些情况下,我们可能需要对某一个 prop 进行“双向绑定”(除了前面用 v-model 绑定 prop 的情况)。
为此,我们建议使用 update:myPropName 抛出事件。例如,对于在上一个示例中带有 title prop 的 ChildComponent,我们可以通过下面的方式将分配新 value 的意图传达给父级:
this.$emit('update:title', newValue)
然后父组件可以在需要时监听该事件,并更新本地的 data property。例如:
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />
为了方便起见,我们可以使用 .sync 修饰符来缩写,如下所示:
<ChildComponent :title.sync="pageTitle" />
Vue 3.x
在 3.x 中,自定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件:
<ChildComponent v-model="pageTitle" />
<!-- 是以下的简写: -->
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>
若需要更改 model 的名称,现在我们可以为 v-model 传递一个参数,以作为组件内 model 选项的替代:
<ChildComponent v-model:title="pageTitle" />
<!-- 是以下的简写: -->
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

这也可以作为 .sync 修饰符的替代,而且允许我们在自定义组件上使用多个 v-model。
<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
<!-- 是以下的简写: -->
<ChildComponent
:title="pageTitle"
@update:title="pageTitle = $event"
:content="pageContent"
@update:content="pageContent = $event"
/>
27. v-if 与 v-for 的优先级对比
==3.x 版本中 v-if 总是优先于 v-for 生效。==
28. v-bind 合并行为
后面定义的覆盖前面的:
<!-- 模板 -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- 结果 -->
<div id="blue"></div>
<!-- 模板 -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- 结果 -->
<div id="red"></div>
29. VNode 生命周期事件(....)
30. 侦听数组
当使用 ==watch== 选项侦听数组时,只有在数组被替换时才会触发回调。换句话说,在数组被改变时侦听回调将不再被触发。要想在数组被改变时触发侦听回调,必须指定 deep 选项。watchEffect 另说 ( ꒪⌓꒪)
watch: {
bookList: {
handler(val, oldVal) {
console.log('book list changed')
},
deep: true
},
}