Vue3 初探(四)模板指令

继续看一下Vue3的迁移文档

v-model

2.x 语法

在 2.x 中,在组件上使用 v-model 相当于绑定 value prop 和 input 事件:

1
2
3
4
5
<ChildComponent v-model="pageTitle" />

<!-- 简写: -->

<ChildComponent :value="pageTitle" @input="pageTitle = $event" />

如果要将属性或事件名称更改为其他名称,则需要在 ChildComponent 组件中添加 model 选项:

  • 在子组件中通过model.prop配置,修改v-model所绑定的prop属性。比如下面的例子中v-model属性绑定的prop就从value变为title。
  • 在子组件中通过model.event配置,可以修改v-model所绑定的值在子组件中发生变化时需要传递到父组件时触发的事件。比如下面的例子中,触发的事件由默认input变为change。
  • 由于通过model.prop配置改变了v-model所绑定的prop为title,所以value可以当做一个普通的prop传入。
1
2
3
<!-- ParentComponent.vue -->

<ChildComponent v-model="pageTitle" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 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 的简写(这里的简写指的是,通过以下写法,可以省略上面在子组件中的特殊配置)如下:

1
<ChildComponent :title="pageTitle" @change="pageTitle = $event" />

使用 v-bind.sync

在某些情况下,我们可能需要对某一个 prop 进行“双向绑定”(除了前面用 v-model 绑定 prop 的情况)。为此,我们建议使用 update:myPropName 抛出事件。例如,对于在上一个示例中带有 title prop 的 ChildComponent,我们可以通过下面的方式将分配新 value 的意图传达给父级:

1
this.$emit('update:title', newValue)

如果需要的话,父级可以监听该事件并更新本地 data property。例如:

1
<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

为了方便起见,我们可以使用 .sync 修饰符来缩写,如下所示:

1
<ChildComponent :title.sync="pageTitle" />

3.x 语法

在 3.x 中,自定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件:

1
2
3
4
5
6
7
8
<ChildComponent v-model="pageTitle" />

<!-- 简写: -->

<ChildComponent
:modelValue="pageTitle"
@update:modelValue="pageTitle = $event"
/>

v-model 参数

若需要更改 model 名称,而不是更改组件内的 model 选项,那么现在我们可以将一个 argument 传递给 model

1
2
3
4
5
<ChildComponent v-model:title="pageTitle" />

<!-- 简写: -->

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

这也可以作为 .sync 修饰符的替代,而且允许我们在自定义组件上使用多个 v-model

1
2
3
4
5
6
7
8
9
10
<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 还支持自定义修饰符:

1
<ChildComponent v-model.capitalize="pageTitle" />

详情可以看另一篇博客:https://sunra.top/posts/4258a220/ 中关于自定义事件的内容

Key attribute

  • 新增v-if, v-else, v-else-if的各分支项,key将不再是必须的,因为现在 Vue 会自动生成唯一的key
    • 非兼容:如果你手动提供 key,那么每个分支必须使用唯一的 key。你不能通过故意使用相同的 key 来强制重用分支。
  • 非兼容<template v-for>key 应该设置在 <template> 标签上 (而不是设置在它的子节点上)。

在条件分支中

Vue 2.x 建议在 v-if/v-else/v-else-if 的分支中使用 key

1
2
3
<!-- 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

1
2
3
<!-- Vue 3.x -->
<div v-if="condition">Yes</div>
<div v-else>No</div>

非兼容变更体现在如果你手动提供了 key,那么每个分支都必须使用一个唯一的 key。因此大多数情况下都不需要设置这些 key

1
2
3
4
5
6
7
8
9
10
11
<!-- 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

1
2
3
4
5
<!-- 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> 标签上。

1
2
3
4
5
<!-- Vue 3.x -->
<template v-for="item in list" :key="item.id">
<div>...</div>
<span>...</span>
</template>

类似地,当使用 <template v-for> 时存在使用 v-if 的子节点,key 应改为设置在 <template> 标签上。

1
2
3
4
5
6
7
8
9
10
11
<!-- 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>

v-if 与 v-for 的优先级对比

  • 非兼容:两者作用于同一个元素上时,v-if 会拥有比 v-for 更高的优先级。

v-bind 合并行为

2.x 语法

在 2.x,如果一个元素同时定义了 v-bind="object" 和一个相同的单独的 property,那么这个单独的 property 总是会覆盖 object 中的绑定。

1
2
3
4
<!-- template -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- result -->
<div id="red"></div>

3.x 语法

在 3.x,如果一个元素同时定义了 v-bind="object" 和一个相同的单独的 property,那么声明绑定的顺序决定了它们如何合并。换句话说,相对于假设开发者总是希望单独的 property 覆盖 object 中定义的内容,现在开发者对自己所希望的合并行为有了更好的控制。

1
2
3
4
5
6
7
8
9
<!-- 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>

v-for 中的 Ref 数组非兼容

在 Vue 2 中,在 v-for 里使用的 ref attribute 会用 ref 数组填充相应的 $refs property。当存在嵌套的 v-for 时,这种行为会变得不明确且效率低下。

在 Vue 3 中,这样的用法将不再在 $ref 中自动创建数组。要从单个绑定获取多个 ref,请将 ref 绑定到一个更灵活的函数上 (这是一个新特性):

1
<div v-for="item in list" :ref="setItemRef"></div>

结合选项式 API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
export default {
data() {
return {
itemRefs: []
}
},
methods: {
setItemRef(el) {
this.itemRefs.push(el)
}
},
beforeUpdate() {
this.itemRefs = []
},
updated() {
console.log(this.itemRefs)
}
}

结合组合式 API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
}
}
}

注意:

  • itemRefs 不必是数组:它也可以是一个对象,其 ref 会通过迭代的 key 被设置。
  • 如果需要,itemRef 也可以是响应式的且可以被监听。