vue组件的数据通信方式很多,本篇着重讲$attrs/$listeners,神助是v-bind="$attrs"/v-on="$listeners。
$attrs/$listeners的常用场景:封装第三方组件或者表单组件,从而减少组件处理成本。
$attrs
使用组件的时候,vue内部会将组件上面的属性,自动会合并到组件内部根元素上面。
<template lang="pug">
list-item(a="a" b="b" c="c" style="color:red")
</template>
<!-- ListItem组件 -->
<template lang="pug">
section(title="hello") 我是组件内部
</template>
渲染的时候会这样
<section title="hi" a="a" b="b" c="c">组件内部</section>
但这里限制在组件根元素上,若,组件内部其他元素也需要使用属性呢,此时就用到$attrs。
<template lang="pug">
section(title="hi") 组件内部
//- { "a": "a", "b": "b", "c": "c" }
div {{$attrs}}
</template>
这里如果不希望根元素继承那些属性,可以在组件内部配置inheritAttrs: false
$listeners
同理,对于事件,如果组件上面的事件是native模式,组件内部的最外层元素也会自动绑定该事件。
<template lang="pug">
list-item(@click.native="()=>{ title = 1}")
</template>
<!-- ListItem组件 -->
<template lang="pug">
section 我是组件内部
</template>
渲染的时候,事件就会自动绑定到最外层元素上面
<section @click="()=>{ title = 1}">组件内部</section>
但这里任然限制在组件根元素上,若,组件内部其他元素也需要绑定事件呢,此时就用到$listeners,其是非native事件的合集。
<template lang="pug">
//- 父组件
div
span {{title}}
list-item(
@click="()=>{ title = '点了 内部绑定事件的元素'}"
@mousedown="()=>{ title = 'mousedown'}"
@click.native="()=>{ title='点了 内部根元素'}")
</template>
<script>
import ListItem from "@/components/ListItem";
export default {
name: "List",
components: { ListItem },
data() {
return {
title: "点击组件的不同元素修改标题"
};
}
};
</script>
<template lang="pug">
section(title="hi") 组件根元素
div(@click.stop="$listeners.click") 组件绑定事件的元素
</template>
<script>
export default {
name: "ListItem",
mounted() {
// {click:fn,mousedown:fn}
console.log("$listeners", this.$listeners);
}
};
</script>
使用场景:封装第三方组件、表单组件
封装第三方组件、或者表单组件的的时候使用,往往结合v-bind="$attrs"/v-on="$listeners",这样就不用考虑用户给组件传入什么属性或者事件了。
比如希望封装baidu-map组件,而baidu-map上面的属性和事件直接用他们自己库的。
<template lang="pug">
enforced-map(:title="title" @resizeMap="...")
</template>
<!-- EnforcedMap.vue -->
<template lang="pug">
div
baidu-map(v-bind="$attrs" v-on="$listeners")
</template>
封装表单组件的时候,inputListeners之类的计算属性通常非常有用。
始终牢记,子组件想要改变父组件的数据,需要$emit哈。
<template lang="pug">
//- 父组件
div
enhanced-input(v-model="title" label='标题')
div {{title}}
</template>
<script>
import EnhancedInput from "@/components/ListItem";
export default {
name: "List",
components: { EnhancedInput },
data() {
return {
title: "初始值"
};
}
};
</script>
组件内部如下:
<template lang="pug">
label {{label}}
//- 这里记得绑定value的值,这样才能显示初始值
input(v-bind="$attrs",:value="value",v-on="inputListeners")
</template>
<script>
export default {
name: "enhanced-input",
inheritAttrs: false,
props: ["label", "value"],
mounted() {},
computed: {
inputListeners() {
const newListeners = {
// 我们从父级添加所有的监听器
...this.$listeners,
// 然后我们添加自定义监听器,或覆写一些监听器的行为
// 这里确保组件配合 `v-model` 的工作
input: event => this.$emit("input", event.target.value)
};
return newListeners;
}
}
};
</script>