上一篇文章:是时候从vue2 迁移到 vue3了 (系列一)
16.内联模板 Attribute 非兼容
变化概览:
- 对内联特性的支持已被移除。
2.x 语法
在 2.x 中,Vue 为子组件提供了 inline-template attribute,以便将其内部内容用作模板,而不是将其作为分发内容。
<my-component inline-template>
<div>
<p>它们被编译为组件自己的模板</p>
<p>不是父级所包含的内容。</p>
</div>
</my-component>
复制代码
3.x 语法
将不再支持此功能。
迁移策略
inline-template 的大多数用例都假设没有构建工具设置,所有模板都直接写在 HTML 页面中。
选项1:使用 <script> 标签
在这种情况下,最简单的解决方法是将
<script type="text/html" id="my-comp-template">
<div>{{ hello }}</div>
</script>
复制代码
在组件中,使用选择器指向目标模板:
const MyComp = {
template: '#my-comp-template'
// ...
}
复制代码
这不需要任何构建设置,可以在所有浏览器中工作,不受任何内部 DOM HTML 解析警告的约束 (例如,你可以使用 camelCase prop 名称),并且在大多数 ide 中提供了正确的语法高亮显示。在传统的服务器端框架中,可以将这些模板拆分为服务器模板部分 (包括在主 HTML 模板中),以获得更好的可维护性。
选项2:默认插槽
以前使用 inline-template 的组件也可以使用默认插槽——进行重构,这使得数据范围更加明确,同时保留了内联编写子内容的便利性:
<!-- 2.x 语法 -->
<my-comp inline-template :msg="parentMsg">
{{ msg }} {{ childState }}
</my-comp>
<!-- 默认 Slot 版本 -->
<my-comp v-slot="{ childState }">
{{ parentMsg }} {{ childState }}
</my-comp>
复制代码
子级现在应该渲染默认 slot*,而不是提供的空模板:
<!--
在子模板中,在传递时渲染默认插槽
在必要的 private 状态下。
-->
<template>
<slot :childState="childState" />
</template>
复制代码
17.key attribute 非兼容
变化概览:
- 新增:对于 v-if/v-else/v-else-if 的各分支项 key 将不再是必须的,因为现在 Vue 会自动生成唯一的 key。
- 非兼容:如果你手动提供 key,那么每个分支必须使用唯一的 key。你不能通过故意使用相同的 key 来强制重用分支。
- 非兼容:<template v-for> 的 key 应该设置在 <template> 标签上 (而不是设置在它的子节点上)。
背景
特殊的 key attribute 被用于提示 Vue 的虚拟 DOM 算法来保持对节点身份的持续跟踪。这样 Vue 可以知道何时能够重用和修补现有节点,以及何时需要对它们重新排序或重新创建。
在条件分支中
Vue 2.x 建议在 v-if/v-else/v-else-if 的分支中使用 key。
<!-- Vue 2.x -->
<div v-if="condition" key="yes">Yes</div>
<div v-else key="no">No</div>
复制代码
这个示例在 Vue 3.x 中仍能正常工作。但是我们不再建议在 v-if/v-else/v-else-if 的分支中继续使用 key attribute,因为没有为条件分支提供 key 时,也会自动生成唯一的 key。
<!-- Vue 3.x -->
<div v-if="condition">Yes</div>
<div v-else>No</div>
复制代码
非兼容变更体现在如果你手动提供了 key,那么每个分支都必须使用一个唯一的 key。因此大多数情况下都不需要设置这些 key。
<!-- 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>
复制代码
结合 <template v-for>
在 Vue 2.x 中 <template> 标签不能拥有 key。不过你可以为其每个子节点分别设置 key。
<!-- Vue 2.x -->
<template v-for="item in list">
<div :key="item.id">...</div>
<span :key="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>
复制代码
类似地,当使用 <template v-for> 时存在使用 v-if 的子节点,key 应改为设置在 <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>
复制代码
18.按键修饰符 非兼容
变化概览:
- 非兼容:不再支持使用数字 (即键码) 作为 v-on 修饰符
- 非兼容:不再支持 config.keyCodes
2.x 语法
在 Vue 2 中,支持 keyCodes 作为修改 v-on 方法的方法。
<!-- 键码版本 -->
<input v-on:keyup.13="submit" />
<!-- 别名版本 -->
<input v-on:keyup.enter="submit" />
复制代码
此外,你也可以通过全局 config.keyCodes 选项。
Vue.config.keyCodes = {
f1: 112
}
复制代码
<!-- 键码版本 -->
<input v-on:keyup.112="showHelpText" />
<!-- 自定别名版本 -->
<input v-on:keyup.f1="showHelpText" />
复制代码
3.x 语法
从KeyboardEvent.keyCode has been deprecated 开始,Vue 3 继续支持这一点就不再有意义了。因此,现在建议对任何要用作修饰符的键使用 kebab-cased (短横线) 大小写名称。
<!-- Vue 3 在 v-on 上使用 按键修饰符 -->
<input v-on:keyup.delete="confirmDelete" />
复制代码
因此,这意味着 config.keyCodes 现在也已弃用,不再受支持。
19.移除 $listeners 非兼容
变化概览:
- $listeners 对象在 Vue 3 中已被移除。现在事件监听器是 $attrs 的一部分:
{
text: 'this is an attribute',
onClose: () => console.log('close Event triggered')
}
复制代码
2.x 语法
在 Vue 2 中,你可以使用 this.$attrs 和 this.$listeners 分别访问传递给组件的 attribute 和事件监听器。结合 inheritAttrs: false,开发者可以将这些 attribute 和监听器应用到其它元素,而不是根元素:
<template>
<label>
<input type="text" v-bind="$attrs" v-on="$listeners" />
</label>
</template>
<script>
export default {
inheritAttrs: false
}
</script>
复制代码
3.x 语法
在 Vue 3 的虚拟 DOM 中,事件监听器现在只是以 on 为前缀的 attribute,这样就成了 $attrs 对象的一部分,因此 $listeners 被移除了。
<template>
<label>
<input type="text" v-bind="$attrs" />
</label>
</template>
<script>
export default {
inheritAttrs: false
}
</script>
复制代码
如果这个组件接收一个 id attribute 和一个 v-on:close 监听器,那么 $attrs 对象现在将如下所示:
{
id: 'my-input',
onClose: () => console.log('close Event triggered')
}
复制代码
20.在 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')
}
}
}
}
复制代码
21. 渲染函数 API 非兼容
变化概览:
-
此更改不会影响 <template> 用户。
-
以下是更改的简要总结:
- h 现在全局导入,而不是作为参数传递给渲染函数
- 渲染函数参数更改以在有状态组件和函数组件之间更加一致
- VNode 现在有一个扁平的 prop 结构
渲染函数参数
2.x 语法
在 2.x 中,render 函数会自动接收 h 函数 (它是 createElement 的传统别名) 作为参数:
// Vue 2 渲染函数示例
export default {
render(h) {
return h('div')
}
}
复制代码
3.x 语法
在 3.x 中,h 现在是全局导入的,而不是作为参数自动传递。
// Vue 3 渲染函数示例
import { h } from 'vue'
export default {
render() {
return h('div')
}
}
复制代码
渲染函数签名更改
2.x 语法
在 2.x 中,render 函数自动接收诸如 h 之类的参数。
// Vue 2 渲染函数示例
export default {
render(h) {
return h('div')
}
}
复制代码
3.x 语法
在 3.x 中,由于 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
)
}
}
复制代码
VNode Prop 格式化
2.x 语法
在 2.x 中,domProps 包含 VNode prop 中的嵌套列表:
// 2.x
{
staticClass: 'button',
class: { 'is-outlined': isOutlined },
staticStyle: { color: '#34495E' },
style: { backgroundColor: buttonColor },
attrs: { id: 'submit' },
domProps: { innerHTML: '' },
on: { click: submitForm },
key: 'submit-button'
}
复制代码
3.x 语法
在 3.x 中,整个 VNode prop 的结构都是扁平的。使用上面的例子,来看看它现在的样子。
// 3.x 语法
{
class: ['button', { 'is-outlined': isOutlined }],
style: [{ color: '#34495E' }, { backgroundColor: buttonColor }],
id: 'submit',
innerHTML: '',
onClick: submitForm,
key: 'submit-button'
}
复制代码
注册组件
2.x 语法
在 2.x 中,注册一个组件后,把组件名作为字符串传给渲染函数的第一个参数,渲染函数会很好地工作:
// 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 语法
在 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 现在将插槽作为函数公开
- 非兼容:移除 this.$scopedSlots
2.x 语法
当使用渲染函数时,即 h,2.x 用于在内容节点上定义 slot 数据 property。
// 2.x 语法
h(LayoutComponent, [
h('div', { slot: 'header' }, this.header),
h('div', { slot: 'content' }, this.content)
])
复制代码
此外,在引用作用域插槽时,可以使用以下方法引用它们:
// 2.x 语法
this.$scopedSlots.header
复制代码
3.x 语法
在 3.x 中,将插槽定义为当前节点的子对象:
// 3.x Syntax
h(LayoutComponent, {}, {
header: () => h('div', this.header),
content: () => h('div', this.content)
})
复制代码
当你需要以编程方式引用作用域插槽时,它们现在被统一到 $slots 选项中。
// 2.x 语法
this.$scopedSlots.header
// 3.x 语法
this.$slots.header()
复制代码
迁移策略
大部分更改已经在 2.6 中发布。因此,迁移可以一步到位:
- 在 3.x 中,将所有 this.$scopedSlots 替换为 this.$slots。
- 将所有 this.$slots.mySlot 替换为 this.$slots.mySlot()。
23. 过渡的 class 名更改 非兼容
变化概览
- 过渡类名 v-enter 修改为 v-enter-from、过渡类名 v-leave 修改为 v-leave-from。
2.x 语法
在 v2.1.8 版本之前,为过渡指令提供了两个过渡类名对应初始和激活状态。
在 v2.1.8 版本中,引入 v-enter-to 来定义 enter 或 leave 变换之间的过渡动画插帧,为了向下兼容,并没有变动 v-enter 类名:
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
复制代码
3.x 语法
为了更加明确易读,我们现在将这些初始状态重命名为:
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
复制代码
现在,这些状态之间的区别就清晰多了。
<transition> 组件相关属性名也发生了变化:
- leave-class 已经被重命名为 leave-from-class (在渲染函数或 JSX 中可以写为:leaveFromClass)
- enter-class 已经被重命名为 enter-from-class (在渲染函数或 JSX 中可以写为:enterFromClass)
24.Transition Group 根元素 非兼容
变化概览:
- <transition-group> 不再默认渲染根元素,但仍然可以用 tag prop 创建根元素。
2.x 语法
在 Vue 2 中,<transition-group> 像其它自定义组件一样,需要一个根元素。默认的根元素是一个 <span>,但可以通过 tag prop 定制。
<transition-group tag="ul">
<li v-for="item in items" :key="item">
{{ item }}
</li>
</transition-group>
复制代码
3.x 语法
在 Vue 3 中,我们有了片段的支持,因此组件不再需要根节点。所以,<transition-group> 默认不再渲染根节点。
- 如果像上面的示例一样,已经在 Vue 2 代码中定义了 tag prop,那么一切都会和之前一样
- 如果没有定义 tag prop,而且样式或其它行为依赖于 <span> 根元素的存在才能正常工作,那么只需将 tag="span" 添加到 <transition-group>:
<transition-group tag="span">
<!-- -->
</transition-group>
复制代码
25.移除 v-on.native 修饰符 非兼容
变化概览:
- v-on 的 .native 修饰符已被移除。
2.x 语法
默认情况下,传递给带有 v-on 的组件的事件监听器只有通过 this.$emit 才能触发。要将原生 DOM 监听器添加到子组件的根元素中,可以使用 .native 修饰符:
<my-component
v-on:close="handleComponentEvent"
v-on:click.native="handleNativeClickEvent"
/>
复制代码
3.x 语法
v-on 的 .native 修饰符已被移除。同时,新增的 emits 选项允许子组件定义真正会被触发的事件。
因此,对于子组件中未被定义为组件触发的所有事件监听器,Vue 现在将把它们作为原生事件监听器添加到子组件的根元素中 (除非在子组件的选项中设置了 inheritAttrs: false)。
<my-component
v-on:close="handleComponentEvent"
v-on:click="handleNativeClickEvent"
/>
复制代码
MyComponent.vue
<script>
export default {
emits: ['close']
}
</script>
复制代码
26.v-model 非兼容
变化概览:
- 非兼容:用于自定义组件时,v-model prop 和事件默认名称已更改:
- prop:value -> modelValue;
- event: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 和事件。但是,这仍然只允许在组件上使用一个 model。
在 Vue 3 中,双向数据绑定的 API 已经标准化,减少了开发者在使用 v-model 指令时的混淆并且在使用 v-model 指令时可以更加灵活。
2.x 语法
在 2.x 中,在组件上使用 v-model 相当于绑定 value prop 和 input 事件:
<ChildComponent v-model="pageTitle" />
<!-- 是以下的简写: -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
复制代码
如果要将属性或事件名称更改为其他名称,则需要在 ChildComponent 组件中添加 model 选项:
<!-- ParentComponent.vue -->
<ChildComponent v-model="pageTitle" />
复制代码
// 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 是以下的简写:
<ChildComponent :title="pageTitle" @change="pageTitle = $event" />
复制代码
使用 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" />
复制代码
3.x 语法
在 3.x 中,自定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件:
<ChildComponent v-model="pageTitle" />
<!-- 是以下的简写: -->
<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>
复制代码
v-model 参数
若需要更改 model 名称,而不是更改组件内的 model 选项,那么现在我们可以将一个 argument 传递给 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"
/>
复制代码
v-model 修饰符
除了像 .trim 这样的 2.x 硬编码的 v-model 修饰符外,现在 3.x 还支持自定义修饰符:
<ChildComponent v-model.capitalize="pageTitle" />
复制代码
27.v-if 与 v-for 的优先级对比 非兼容
变化概览
- 非兼容:两者作用于同一个元素上时,v-if 会拥有比 v-for 更高的优先级。
介绍
Vue.js 中使用最多的两个指令就是 v-if 和 v-for,因此开发者们可能会想要同时使用它们。虽然不建议这样做,但有时确实是必须的,于是我们想提供有关其工作方式的指南。
2.x 语法
2.x 版本中在一个元素上同时使用 v-if 和 v-for 时,v-for 会优先作用。
3.x 语法
3.x 版本中 v-if 总是优先于 v-for 生效。
28.v-bind 合并行为 非兼容
变化概览:
- 不兼容:v-bind 的绑定顺序会影响渲染结果
介绍
在元素上动态绑定 attribute 时,常见的场景是在一个元素中同时使用 v-bind="object" 语法和单独的 property。然而,这就引出了关于合并的优先级的问题。
2.x 语法
在 2.x,如果一个元素同时定义了 v-bind="object" 和一个相同的单独的 property,那么这个单独的 property 总是会覆盖 object 中的绑定。
<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="red"></div>
复制代码
3.x 语法
在 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>
复制代码
29.Watch on Arrays 非兼容
变化概览:
- 非兼容: 当侦听一个数组时,只有当数组被替换时才会触发回调。如果你需要在数组改变时触发回调,必须指定 deep 选项。
3.x 语法
当使用 watch 选项侦听数组时,只有在数组被替换时才会触发回调。换句话说,在数组改变时 watch 回调将不再被触发。要想在数组改变时触发 watch 回调,必须指定 deep 选项。
watch: {
bookList: {
handler(val, oldVal) {
console.log('book list changed')
},
deep: true
},
}
复制代码
最后:
本笔记主要基于官方文档 迁移策略 汇总而来。如有理解出入,请以官方文档为主。建议您以官方文档为主,本文为辅。这样您可以“以自己为主”审视的阅读,从而不被我的观点带偏