通常情况下,父子组件之间的数据是通过 props 由父向子传递的,当子组件想要修改数据时,则需要通过 $emit 以事件形式交由父组件完成,而这种交互方式只存在于父子组件之间,多层嵌套的时候,处于内层的组件想要获取外层的数据时,需要外层组件一层一层地将数据向下传递;同理,当内层组件想要修改数据时,也需要将事件一层一层向上传递。
当外层组件向最终接收组件传递数据时,中间经过的每个组件都需要定义 props 去接收并向下传递,这种做法肯定是不太合理的,不仅代码冗余了,而且对于中间不需要数据的组件来说,定义自身不需要的 props 也是一种污染;同理,将事件一层一层向上传递也是不太合理的。
我们都知道,任何单页应用中的组件间都不可能只有简单的父子关系,如果有,说明这个应用并不需要做成单页应用。
那么如何才能减少(或避免)这种情况的发生呢?Vue 中提供了 $attrs 和 $listeners。
$attrs
包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。
简单的话翻译:接收所有给组件传的值(class,style 和 props 声名的属性除外)
$listeners
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。
简单的话翻译:接收除了带有 .native 事件修饰符的所有事件监听器,即原生的事件是不包含在内的
$attrs 与 $listeners 怎么用
父组件 parent.vue
<template>
<son :sentence="sentence" @customClick="handleClick"></son>
</template>
<script>
import son from "./son";
export default {
components: {
son
},
data() {
return {
sentence: "this is a sentence"
};
},
methods: {
handleClick(e) {
console.log(e);
}
}
};
</script>
在 `data` 中定义一个变量 `sentence`,值为 `this is a sentence`。
引入子组件 `son`,将 `sentence` 传入,同时绑定自定义事件 `customClick`,在回调函数中将自定义事件的参数打印出来。
子组件 son.vue
<template>
<div>
I am son
<p>$attrs in son {{ $attrs }}</p>
<grandson v-bind="$attrs" v-on="$listeners"></grandson>
</div>
</template>
<script>
import grandson from "./grandson";
export default {
name: "son",
components: {
grandson
},
data() {
return {};
},
methods: {}
};
</script>
引入孙组件 grandson,使用 v-bind="$attrs" 和 v-on="$listeners" 向下传递数据和事件监听器。
在页面上绑定 $attrs,用于显示效果。
孙组件 grandson.vue
<template>
<div>
I am grandson
<p @click="handleClick">$attrs in grandson {{ $attrs }}</p>
<greatGrandson v-bind="$attrs" v-on="$listeners"></greatGrandson>
</div>
</template>
<script>
import greatGrandson from "./great-grandson";
export default {
name: "grandson",
components: {
greatGrandson
},
methods: {
handleClick() {
this.$emit("customClick", "clicked grandson");
}
}
};
</script>
首先还是为组件添加自定义点击事件,取名 `customClick`,参数为一句话 `clicked grandson`,用以区分,并在点击时触发这个事件。
接着引入曾孙组件 `great-grandson`,使用 `v-bind="$attrs"` 和 `v-on="$listeners"` 向下传递数据和事件监听器。
在页面上绑定 $attrs,用于显示效果。
曾孙组件 great-grandson.vue
<template>
<div @click="handleClick">$attrs in great grandson {{ $attrs }}</div>
</template>
<script>
export default {
name: "great-grandson",
methods: {
handleClick() {
this.$emit("customClick", "clicked great grandson");
}
}
};
</script>
添加自定义点击事件并在点击时触发这个事件,取名 customClick,参数为一句话 clicked great grandson。
在页面上绑定 $attrs,用于显示效果。
运行示例,打开浏览器,页面上的内容将会是下面的样子:
在组件 son、grandson 和 great-grandson 中都显示了 this is a sentence,而子组件 son 和孙组件 grandson 中都没有定义 props,说明 parent 组件中的数据正确的传递到了内部组件中。
打开控制台,分别点击组件 grandson 和 great-grandson 中 $attrs 所在的句子,可以看到事件也是生效的:
image.png
这就是 $attrs
和 $listeners
的功能,去掉数据和事件在多层嵌套组件中传递时的定义部分。注意,仅仅是定义部分,绑定的步骤还是少不了的,即经过的每一层组件都需要使用 v-bind="$attrs"
和 v-on="$listeners"
。
另外,这两个属性都是 只读 的,不要试图通过 $attrs
去直接修改原数据。
第二个案例
// 父组件,向子组件传递数据和定义自定义事件
<template>
<div>
<hintButton icon="el-icon-plus" size="mini" type="success" title="翻滚的露西" @click="handler"></hintButton>
</div>
</template>
<script>
import hintButton from '@/components/hintButton.vue'
export default {
name: 'app',
components: {
hintButton
},
methods: {
handler() {
alert('露西666')
}
}
}
</script>
// 子组件
// 通过 v-bind='$attrs' 接收父组件传递的数据
// 通过 v-on='$listeners' 可以获取到父组件给子组件传递自定义事件
<template>
<div>
<a :title="title">
<el-button v-bind="$attrs" v-on="$listeners"></el-button>
</a>
</div>
</template>
<script>
export default {
name: 'hintButton',
props: ['title'],
mounted() {
console.log('this.$attrs', this.$attrs)
console.log('this.$listeners', this.$listeners)
}
}
</script>
this.$attrs 和 this.$listeners 的打印结果
网友评论