一、问题背景
首先用一个简单的例子来铺垫下背景:
有一个组件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 。
本文内容皆来自于日常开发的简单思考,肯定有不完善或是不合理的地方,如有遗漏或者谬误,欢迎指正~



评论(0)