$attrs/$listeners实现爷孙组件通信
$attrs 和 $listeners 是两个特殊的对象,它们分别用来处理父组件传递给子组件的未声明为 props 的属性和事件。这两个特性可以用来实现较为复杂的组件通信,尤其是当涉及到多层级的组件时,比如爷孙组件之间的通信。
$attrs: 当一个组件接收到父组件传递的属性,但这些属性没有被声明为 props 时,它们会被收集到 $attrs 对象中。$attrs 可以用来将这些未声明的属性传递给更深层次的子组件。
$listeners: 类似于 $attrs,$listeners 包含了父组件传递给当前组件的所有事件监听器。它允许将这些监听器传递给子组件,将子组件 $emit 方法传递过来的事件传递给爷组件,调用从而实现事件的传递。
实现原理
属性传递:爷爷组件将一些属性传递给父组件,父组件通过 $attrs 将这些属性传递给孙子组件。
事件监听器传递:爷爷组件绑定了一些事件监听器到父组件,父组件通过 $listeners 将这些监听器传递给孙子组件。
优点
-
灵活性:
$attrs和$listeners提供了一种灵活的方式来传递未声明的属性和事件,使得组件可以更容易地适应不同的使用场景。 -
简化通信:通过
$attrs和$listeners,可以简化多层组件之间的通信,避免了显式地在每一层都定义props和事件转发。 -
扩展性:允许组件在不知道具体属性和事件的情况下接收和传递它们,增加了组件的扩展性和复用性。
缺点
-
类型安全问题:在 TypeScript 等强类型语言中使用
$attrs和$listeners可能会导致类型推断的问题,使得类型检查变得困难。 -
调试困难:由于
$attrs和$listeners包含的是未明确声明的内容,这可能使得调试变得更加困难。 -
性能影响:频繁地使用
$attrs和$listeners可能会影响性能,尤其是在大型应用中,因为每次父组件的属性或事件变化时,都会触发$attrs和$listeners的更新。 -
可维护性:过度依赖
$attrs和$listeners可能会使得组件之间的关系变得模糊,增加代码的复杂性和维护难度。
vue2.x 使用
在vue2中,$attrs 是组件的一个实例属性,包含了父作用域中 不作为 prop 被识别的属性。
grandpa.vue (爷组件)
// grandpa.vue<template><div class="grandpa"><h2>爷组件</h2><div>{{ msg }}</div><parent :message="message" :message2="message2" @sendGradpaMessage="getMessage"/></div>
</template><script>
import parent from './parent.vue';export default {name: 'grandpa',data() {return {message: 'This is some shared data1',message2: 'This is some shared data2',msg:""}},components: {parent},methods:{getMessage(msg){console.log('msg::: ', msg);this.msg = msg},}
}
</script>
parent.vue (父组件)
// parent.vue<template><div class="parent"><h2>父组件</h2><div>{{ message }}</div><son v-bind="$attrs" v-on="$listeners"/></div>
</template><script>
import son from './son.vue';export default {name: 'parent',data() {return {}},components: { son },computed: {message() {// 从 $attrs 中删除已定义的 propconsole.log('this.$attrs::: ', this.$attrs);const { message} = this.$attrs;return message;}},
}
</script>
son.vue (孙组件)
// son.vue<template><div class="son"><h2>孙组件</h2><div>{{ $attrs.message }}</div> <div>{{ message }}</div> <el-button type="primary" @click="sendMessage">发送消息</el-button></div>
</template><script>
export default {name: 'son',data() {return {}},computed: {message() {// 从 $attrs 中删除已定义的 propconsole.log('this.$attrs::: ', this.$attrs);const { message2} = this.$attrs;return message2;}},methods: {sendMessage(){const msg = 'Hello from son component!';this.$emit('sendGradpaMessage', msg)}}
}
</script>
上述示例中:
-
grandpa.vue 组件提供了
message,message2两个参数。 -
parent.vue 组件通过
$attrs接收了message参数,并在模板中使用message。 -
son.vue 组件通过
$attrs接收了message2参数,并在模板中使用message,同时通过点击按钮使用$emit方法将参数msg传给了 parent.vue 组件,parent.vue 通过$listeners将事件传递给 grandpa.vue。
vue3.x 使用
在 Vue 3 中,$attrs 和 $listeners 的使用有一些变化,特别是在 $listeners 的处理上。Vue 3 已经不再单独提供 $listeners,而是将事件监听器和其他属性一起放在 $attrs 中。这意味着在 Vue 3 中,你需要通过 $attrs 来传递所有的属性和事件监听器。
grandpa.vue (爷组件)
// grandpa.vue<template><div class="grandpa"><h2>爷组件</h2><div>{{ receive }}</div><parent :message="message" :message2="message2" @sendGrandpaMessage="getMessage" /></div>
</template><script setup lang="ts" name="grandpa">
import { ref } from 'vue';import parent from './parent.vue';// 定义一个可变的值
const message = ref('Hello from grandpa');
const message2 = ref('Hello from Parent');// 定义一个接收消息
const receive = ref('111');// 接收子组件传的参数
const getMessage = (msg: string) => {console.log('msg::', msg);receive.value = msg
};
</script>
parent.vue (父组件)
// parent.vue<template><div class="parent"><h2>父组件</h2><div>{{ message2 }}</div><div>{{ $attrs }}</div><son v-bind:="$attrs" /></div>
</template><script setup lang="ts" name="parent">
import son from './son.vue';defineProps(['message2']);
</script>
son.vue (孙组件)
// son.vue<template><div class="son"><h2>孙组件</h2><div>{{ message }}</div><el-button type="primary" @click="sendMessage()">发送消息</el-button></div>
</template><script setup lang="ts" name="son">
import { defineProps, defineEmits } from 'vue';defineProps(['message',]);const emits = defineEmits(['sendGrandpaMessage']);// 处理事件
function sendMessage() {const msg = 'Hello from child';emits('sendGrandpaMessage', msg);
}
</script>
上述示例中:
-
grandpa.vue 组件提供了
message,message2两个参数和sendGrandpaMessage方法。 -
parent.vue 组件通过
$attrs接收了message2参数,并在模板中使用message2。 -
son.vue 组件通过
$attrs接收了message参数,并在模板中使用message,同时通过点击按钮使用emit将参数msg传给了 parent.vue 组件,parent.vue 通过$attrs将事件传递给 grandpa.vue。
总结
$attrs 和 $listeners 是 Vue.js 提供的一种机制,用于处理未声明的属性和事件的传递。它们在特定场景下非常有用,特别是在需要灵活传递属性和事件的情况下。然而,它们也有一定的局限性,如类型安全问题、调试困难等。因此,在实际开发中,应该根据具体情况权衡是否使用 $attrs 和 $listeners,并在必要时使用它们来增强组件的灵活性和扩展性。
