一、问题背景
首先用一个简单的例子来铺垫下背景:
有一个组件Parent
,它有个子组件叫RevertButton
,子组件支持传入isDeleted
属性来控制子组件是展示正常态还是被删除态。伪代码如下所示:
// Parent.vue
<template>
<RevertButton
v-model:isDel="isDeleted"
@deleted="handleButtonDeleted"
@revert="handleButtonRevert"
>这是个可删除按钮</RevertButton>
</template>
<script lang="ts" setup>
const isDeleted = ref(false)
const handleButtonDeleted = () => {
// 在按钮被删除时,执行一些操作
console.log("RevertButton被删除了")
}
const handleButtonRevert = () => {
// 在按钮从被删除态恢复时触发
console.log("RevertButton被恢复了")
}
</script>
在子组件中,会包含一个删除按钮,用户点击删除按钮后按钮展示被删除态,在此状态可以点击恢复按钮再次变为正常态:
- 正常态
- 被删除态
二、错误的子组件状态变更emit写法
关于子组件内部的实现逻辑,其实非常简单,就是在删除按钮的点击事件时将组件状态修改被删除,然后emits("deleted")
,在按钮从被删除态恢复时,修改组件状态,然后emits("revert")
。依照这个思路,很容易写出如下代码:
<template>
...
<button @click="handleDelete">删除</button>
<button @click="handleRevert">恢复</button>
</template>
<script lang="ts" setup>
// useVModel是vueuse提供的快速实现双向绑定的工具hook
const isDelModel = useVModel("props", "isDel")
const handleDelete = () => {
isDelModel.value = true
emits("deleted")
}
const handleRevert = () => {
isDelModel.value = false
emits("revert")
}
</script>
仅限于我们的功能而言,似乎已经可以了,但是其实是有问题的,大家可以先思考下问题在哪里?
三、emits依赖事件还是依赖状态?
结合标题,在结合第二部分中的例子,很明显,在前面的例子中,emits
是依赖事件的。
什么是依赖事件呢?就是emits的执行时机是在事件中。也就是第二部分例子中的handleDelete
和handleRevert
。
那么在本文的例子中,依赖事件会有什么问题呢?
简单的说,就是父组件在监听子组件状态变化时,只能监听到事件引起的状态变化,而监听不到不依赖事件的状态变化。
- 事件引起的状态变化
即在handleDelete
和handleRevert
事件中修改状态,同时触发emits
。这时候父组件是可以监听到的。
- 不依赖事件的状态变化
试想,如果我们在子组件中直接修改isDelModel.value
的值,会发生什么?没错,是无法触发父组件的监听事件的。有的同学可能会说,我可以修改后手动调用emits
,当然可以,但是这样会导致写很多冗余代码,在每一次修改isDelModel.value
后,都手动调用emits
。
四、组件状态的emits应该和状态绑定
所以,到这里,我们可以总结一个小经验:就是组件状态变化相关的emits,应该和组件状态唯一绑定,不应该依赖任何修改状态的事件。
还是拿第二部分的代码为例,我们只需要如下修改即可实现emits和状态绑定:
const handleDelete = () => {
isDelModel.value = true
}
const handleRevert = () => {
isDelModel.value = false
}
watch(isDelModel, (val, prev) => {
if (val && !prev) {
emits("deleted")
return
}
if (!val && prev) {
emits("revert")
}
})
这样,不管我们是在子组件内部修改isDelModel.value
,还是在父组件修改了isDeleted.value
,都可以正确触发父组件对状态的监听。
五、总结
这里我把状态变化归为两类:
- 依赖事件的状态变化
- 状态值被修改引起的变化
同时,emits的执行时机也可以分为两类:
- 事件引起的emits
- 状态变更引起的emits,这种情况包括事件
在实际开发中,分清楚是哪一种情况可以帮助我们确定应该在哪里执行emits
。
本文内容皆来自于日常开发的简单思考,肯定有不完善或是不合理的地方,如有遗漏或者谬误,欢迎指正~