inheritAttrs
在谈及深层次组件通信之前先说一下 inheritAttrs
, 这个属性在官网中是这样解释的:
默认情况下父作用域的不被认作
props
的特性绑定将会 ‘回退’ 且作为普通的 HTML 特性应用在子组件的跟元素上。 当撰写包裹一个目标元素或者另一个组件的组件时,这可能不会总是符合预期行为。 通过设置inheritAttrs
为false
, 这些默认行为将会被去掉。 而通过实例属性$attrs
可以让这些特性生效, 而且可以通过v-bind
显性的绑定到非跟元素上。 (注意:这个选项不影响class
和style
绑定)
不愧是官方的,看了几遍真的很难理解,还是通过 demo 来看一下吧:
子组件
<template>
<div>{{val1}} </div>
</template>
<script>
export default {
name: "child",
props: ["val1"]
};
</script>
父组件
<template>
<div class="hello">
<child :val1="val1" :val2="val2"></child>
</div>
</template>
<script>
import Child from "./child";
export default {
components: {
Child
},
data() {
return {
val1: "props val1",
val2: "props val2"
};
}
};
</script>
父组件向子组件传递了 val1
val2
两个参数, 但是在 子组件内部 props
只接收了一个参数 val1
, 并没有接收 val2
, 没有接收的多余的属性将会作为子组件根元素的属性节点。 html
形式就会显示为:
<div val2='val2'>{{props val2}} </div>
发生上面的情况就是因为 inheritAttrs
选项默认是 true
, 但是这和 $attrs
又有什么关系呢?
再来解读一下官网上面的最后几句:
通过设置
inheritAttrs
为false
, 这些默认行为将会被去掉。 而通过实例属性$attrs
可以让这些特性生效, 而且可以通过v-bind
显性的绑定到非跟元素上。
也就是说,如果在子组件内将 inheritAttras
选项设置为 false
,我们能够通过 $attrs
这个属性拿到组件 props
中没有定义但是父组件中有传递的属性值:
子组件
<template>
<div>{{val1}} </div>
</template>
<script>
export default {
name: "child",
props: ["val1"],
inheritAttrs: false,
created() {
console.log(this.$attrs);
// {val2: 'props val2'}
}
};
</script>
$attrs
上面只是讲了 结合 inheritAttrs
获取 $attrs
属性, 这两者的结合最厉害的地方在于实现深层次组件之间的传值。
孙组件
<template>
<div>
{{second}}{{third}}
</div>
</template>
<script>
export default {
name: "next",
props: ["second", "third"]
};
</script>
子组件
<template>
<next v-bind="$attrs"></next>
</template>
<script>
import Next from './next'
export default {
name: "child",
components: {Next},
inheritAttrs: false
};
</script>
父组件
<template>
<child :second='second' :third='third'></child>
</template>
<script>
import Child from './child'
export default {
components: {Child},
data(){
return {
second: 'props second',
third: 'props third'
}
}
};
</script>
可以看到, 第二层的组件只是作为一个过度组件,他对于父组件传来的两个属性并没有使用,并且组件内部也并没有定义任何 props
要接受的属性, 直接通过 v-bind='$attrs'
这种方式把从父级接收来的属性以对象的形式向下传递给了孙组件的 props
。
需要注意的两点:
- 作为中间传递数据的组件,必须要把
inheritAttrs
这个选项设置为false
才能正确获取$attrs
数据。- v-bind 在平常使用时都是使用
v-bind:src=xx
或者:src=xx
这种形式, 这里使用v-bind=xx
是指绑定一个包含键值对的对象到组件。
listeners
上面讲了使用 $attrs
实现数据的向下传递, 但是又怎么实现下层数据或事件的向上交互呢? 这里我们要使用到 $listeners
$listeners
和 $attrs
两者表面上都是一个意思,
a
t
t
r
s
是
向
下
传
递
数
据
,
attrs 是向下传递数据,
attrs是向下传递数据,listeners 是向下传递方法,通过手动去调用 $listeners 对象里的方法,来触发从父级接受来的函数。
孙组件
<template>
<div>
{{second}}{{third}}
<button @click='handleFunc1'>fuc1 button</button>
<button @click='handleFunc2'>fuc2 button</button>
</div>
</template>
<script>
export default {
name: "next",
props: ["second", "third"],
methods: {
handleFunc1() {
this.$listeners.func1('props changed second')
},
handleFunc2() {
this.$listeners.func2('props changed third')
}
}
};
</script>
子组件
<template>
<next v-bind="$attrs" v-on='$listeners'></next>
</template>
<script>
import Next from './next'
export default {
name: "child",
components: {Next},
inheritAttrs: false
};
</script>
父组件
<template>
<child
:second='second'
:third='third'
@func1='Func1Click'
@func2='Func2Click'
/>
</template>
<script>
import Child from './child'
export default {
components: {Child},
data(){
return {
second: 'props second',
third: 'props third'
}
},
methods:{
Func1Click(val) {
this.second = val
},
Func2Click(val) {
this.third = val
}
}
};
</script>
同 $attrs
的使用相似, 中间组件对于父级传递的事件并没有使用,也是作为一个过度,使用 v-on='this.$listeners'
将从父级接受来的事件再次向下传递,直到传递给最后一级组件, 最后一级的组件就能够使用 this.$listeners
来调用父级的事件,从而改变父级绑定在子组建上面的属性值。