# 结论
一句话来概括就是:
当在父组件声明了自定义事件,同时没有在子组件的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上,并不仅限于自定义的事件。
