$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
,并在必要时使用它们来增强组件的灵活性和扩展性。