理解emits和attrs

2023/5/10 emits属性透传

# 结论

一句话来概括就是:

当在父组件声明了自定义事件,同时没有在子组件的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

# 分析

# 1️⃣ 第一个测试

App.vue中,第一个 A 组件中我们添加了自定义事件 —— f1,但是在A.vue中,没有声明defineEmits("f1"),所以我们不能通过emits("f1")来调用自定义事件 f1

此时f1会被作为$attrs透传给A组件,我们通过useAttrs()可以访问到透传过来的属性

透传过来attrs的内容如下:

图片2

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上,并不仅限于自定义的事件。

How to love
Lil Wayne