# 结论
一句话来概括就是:
当在父组件声明了自定义事件,同时没有在子组件的emits
中声明时,将自动绑定在父组件的$attrs
上;而当在子组件声明时,则不会在父组件的$attrs
上出现
## :desktop_computer:测试
App.vue
—— 父组件
<template>
<div>
<p>加上自定义事件f1,但未在A中声明</p>
<A @f1="test"></A>
<br />
<hr />
<br />
<p>加上自定义事件f2,同时在A中声明</p>
<A @f2="test"></A>
<br />
<hr />
<p>加上自定义事件f1和f2,但在A中声明只声明了f2</p>
<A @f2="test" @f1="test"></A>
</div>
</template>
<script setup>
import A from "./components/A.vue";
const test = () => {
console.log("测试成功");
};
</script>
<style scoped></style>
A.vue
—— 子组件
<template>
<div>
<button @click="test()">测试</button>
</div>
</template>
<script setup>
import { useAttrs } from "vue";
const emits = defineEmits(["f2"]);
const attrs = useAttrs(); // 获取透传过来的属性
console.log(attrs);
const test = () => {
if(attrs.onF1){
attrs.onF1();
}
emits("f2");
};
</script>
<style scoped></style>
分别点击三个测试按钮, 控制台得到的结果如下:
# 分析
# 1️⃣ 第一个测试
在App.vue
中,第一个 A 组件中我们添加了自定义事件 —— f1
,但是在A.vue
中,没有声明defineEmits("f1")
,所以我们不能通过emits("f1")
来调用自定义事件 f1
此时f1
会被作为$attrs
透传给A
组件,我们通过useAttrs()
可以访问到透传过来的属性
透传过来attrs
的内容如下:
attrs
是一个Proxy
对象,App.vue
添加的自定义事件 f1 会作为其中的一个属性,类别与 f1 一致,就是函数,要注意属性名会被修改为onF1
,如果想要调用自定义事件f1
,就执行onF1()
即可
### :two:第二个测试
第二个A
组件中添加了自定义事件 —— f2
,在A.vue
中,定义了对应的方法defineEmits("f2")
,所以我们可以直接在A.vue
中使用emits("f2")
来调用父组件的自定义事件f2
—— 打印台输出“f2 测试成功”
### :three:第三个测试
第三个A
组件中同时添加了自定义事件 —— f1
, f2
,但在A.vue
中只声明了defineEmits("f2")
,所以我们可以通过emits("f2")
来调用自定义事件f2
,不能通过emits("f1")
来调用f1
但是此时f1
仍然会通过$attrs
透传过来,然后通过attrs.onF1()
调用
# ✍️总结
在大多数情况下,两者的功能其实是没有差异的,我们要做的是分析两者各自适用的使用场景
emits
是首先在子组件声明,父组件引用,而attrs
则是先由父组件在子组件上自定义事件,子组件通过查看父组件的attrs
来使用。
- 当一个组件经常需要通过自定义事件和父组件通信时,可以使用
emits
- 当通信次数只有一两次或者很少时,可以选择使用
attrs
—— 比如class
,style
这些
:sunny:官方对这二者的说法是
强烈建议使用 emits
记录每个组件所触发的所有事件。任何未在 emits
中声明的事件监听器都会被算入组件的 $attrs
,无论是不是自定义事件,并将默认绑定到组件的根节点上
所以,如果需要区分自己的自定义事件和原生事件,最好还是使用emits
来定义每一个组件触发的事件。其实对于父组件的所有事件,子组件只要不在emits
中声明,那这些事件都会默认绑定在父组件的attrs
上,并不仅限于自定义的事件。