$attrs
与 $listeners
多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此Vue2.4 版本提供了另一种方法---- $attrs
/ $listeners
-
$attrs
:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。 -
$listeners
:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件;它是一个对象,里面包含了作用在这个组件上的所有监听器,你就可以配合v-on="$listeners"
将所有的事件监听器指向这个组件的某个特定的子元素。 -
inheritAttrs
:默认值true,继承所有的父组件属性(除props的特定绑定)作为普通的HTML特性应用在子组件的根元素上,如果你不希望组件的根元素继承特性设置inheritAttrs: false,但是class属性会继承
主要用途
用在父组件传递数据给子组件或者孙组件
父组件
<template>
<div class="hello"\>
<h2>父组件浪里行舟</h2>
<child
:foo = "foo"
:boo = "boo"
:coo = "coo"
:doo = "doo"
title = "前端公虾米"
@func1='Func1Click'
@func2='Func2Click'
\></child>
</div></template>
<script>
import child from './child'
export default {
name: 'HelloWorld',
data () {
return {
foo: "Javascript",
boo: "Html",
coo: "CSS",
doo: "Vue"
}
},
methods:{
Func1Click(val) {
this.second = val
},
Func2Click(val) {
this.third = val
}
},
components:{
child
}
}
</script>
子组件
<template>
<div> <h2>子组件</h2>
<p>foo:foo作为props属性绑定 {{ foo }}</p>
<p>child的$attrs: {{ $attrs }}</p>
<son v-bind="$attrs" v-on="$listeners"\></son>
</div></template>
<script>
import Son from './son'
export default {
components: {Son},
// inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
props: {
foo: String // foo作为props属性绑定
},
created() {
console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
console.log(this.$listeners);
}
}
</script>
孙组件:
<template>
<div class="border"\>
<h2>孙组件</h2>
<p>boo: {{ boo }}</p>
<p>childCom2: {{ $attrs }}</p>
<button @click='handleFunc1'>fuc1 button</button>
<button @click='handleFunc2'>fuc2 button</button>
<child-com3 v-bind="$attrs"\></child-com3>
</div></template>
<script>
import childCom3 from './child3'
export default {
components: {
childCom3
},
inheritAttrs: false,
props: {
boo: String
},
created() {
console.log(this.$attrs); // { "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
} ,
methods: {
handleFunc1() {
this.$listeners.func1('props changed second')
},
handleFunc2() {
this.$listeners.func2('props changed third')
}
}
}
</script>
重孙组件:
<template>
<div class="border"\>
<h2>重孙组件</h2>
<p>childCom3: {{ $attrs }}</p>
</div></template>
<script>
export default {
name: "child3",
props: {
coo: String,
title: String
},
created() {
console.log(this.$attrs); // { "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
}
}
</script>
inheritAttrs
默认情况下父作用域的不被认作 props 的特性绑定将会 ‘回退’ 且作为普通的 HTML 特性应用在子组件的跟元素上。 当撰写包裹一个目标元素或者另一个组件的组件时,这可能不会总是符合预期行为。 通过设置 inheritAttrs 为 false, 这些默认行为将会被去掉。 而通过实例属性 $attrs 可以让这些特性生效, 而且可以通过 v-bind 显性的绑定到非跟元素上。 (注意:这个选项不影响 class 和 style 绑定)
在子组件中我们没有做【inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性】,现在看子组件在浏览器上的渲染
:
当我们添加上这个属性:
小结:
父组件向子组件传递了boo
`coofoo
dootitle`两个参数, 但是在 子组件内部`props`只接收了一个参数`foo`, 并没有接收`boo
coodoo
title, 没有接收的多余的属性将会作为子组件根元素的属性节点。
html`形式就会显示为:
<div data-v-469af010="" boo="Html" coo="CSS" doo="Vue" title="前端公虾米"></div>
发生上面的情况就是因为 inheritAttrs 选项默认是 true , 但是这和 $attrs 又有什么关系呢?
再来解读一下官网上面的最后几句:
通过设置 inheritAttrs 为 false, 这些默认行为将会被去掉。 而通过实例属性 $attrs 可以让这些特性生效, 而且可以通过 v-bind 显性的绑定到非跟元素上。
也就是说,如果在子组件内将 inheritAttras 选项设置为 false ,我们能够通过 $attrs 这个属性拿到组件 props 中没有定义但是父组件中有传递的属性值:
子组件:
created() {
console.log(this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端工匠" }
}
$attrs
上面只是讲了 结合inheritAttrs
获取$attrs
属性, 这两者的结合最厉害的地方在于实现深层次组件之间的传值。
<div>
<h2>子组件1</h2>
<p>foo:foo作为props属性绑定 {{ foo }}</p>
<p>child的$attrs: {{ $attrs }}</p>
<son v-bind="$attrs" v-on="$listeners"\></son>
</div>
<div class="border"\>
<h2>孙组件</h2>
<p>boo: {{ boo }}</p>
<p>childCom2: {{ $attrs }}</p>
<button @click='handleFunc1'>fuc1 button</button> <button @click='handleFunc2'>fuc2 button</button>
<child-com3 v-bind="$attrs"\></child-com3>
</div>
需要注意的两点:
- 作为中间传递数据的组件,必须要把 inheritAttrs 这个选项设置为 false 才能正确获取 $attrs 数据。
- v-bind 在平常使用时都是使用 v-bind:src=xx 或者 :src=xx 这种形式, 这里使用 v-bind=xx 是指绑定一个包含键值对的对象到组件。
listeners
上面讲了使用 $attrs 实现数据的向下传递, 但是又**怎么实现下层数据或事件的向上交互呢?
** 这里我们要使用到 $listeners
$listeners 和 $attrs 两者表面上都是一个意思,attrs是向下传递数据,listeners 是向下传递方法
,通过手动去调用 $listeners 对象里的方法,来触发从父级接受来的函数。
对比父组件,子组件和孙组件:
同 $attrs 的使用相似, 中间组件对于父级传递的事件并没有使用,也是作为一个过度,使用 v-on='this.$listeners' 将从父级接受来的事件再次向下传递,直到传递给最后一级组件, 最后一级的组件就能够使用 this.$listeners 来调用父级的事件,从而改变父级绑定在子组建上面的属性值。
provide/inject
简单来说:就是父组件通过provide来提供数据;子组件通过inject来读取数据
注意: 这里不论子组件嵌套有多深, 只要调用了
inject 那么就可以注入
provide中的数据,而不局限于只能从当前父组件的props属性中回去数据
// A.vue
<template>
<div>
<comB></comB>
</div>
</template>
<script>
import comB from '../components/test/comB.vue'
export default {
name: "A",
provide: {
for: "demo"
},
components:{
comB
}
}
</script>
// B.vue
<template>
<div>
{{demo}}
<comC></comC>
</div>
</template>
<script>
import comC from '../components/test/comC.vue'
export default {
name: "B",
inject: ['for'],
data() {
return {
demo: this.for
}
},
components: {
comC
}
}
</script>
// C.vue
<template>
<div>
{{demo}}
</div>
</template>
<script>
export default {
name: "C",
inject: ['for'],
data() {
return {
demo: this.for
}
}
}
</script>
一言而蔽之:祖先组件中通过provider来提供变量,然后在子孙组件中通过inject来注入变量。
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
注意: 这里不论子组件嵌套有多深, 只要调用了
inject 那么就可以注入
provide中的数据,而不局限于只能从当前父组件的props属性中回去数据.
ref
/ refs
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果**用在子组件上,引用就指向组件实例**
,可以通过实例直接调用组件的方法或访问数据, 我们看一个ref 来访问组件的例子:
// 子组件 A.vue
export default {
data () {
return {
name: 'Vue.js'
}
},
methods: {
sayHello () {
console.log('hello')
}
}
}
// 父组件 app.vue
<template>
<component-a ref="comA"></component-a>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.name); // Vue.js
comA.sayHello(); // hello
}
}
</script>
$children
/ $parent
// 父组件中
<template>
<div class="hello_world">
<div>{{msg}}</div>
<com-a></com-a>
<button @click="changeA">点击改变子组件值</button>
</div>
</template>
<script>
import ComA from './test/comA.vue'
export default {
name: 'HelloWorld',
components: { ComA },
data() {
return {
msg: 'Welcome'
}
},
methods: {
changeA() {
// 获取到子组件A
this.$children[0].messageA = 'this is new value'
}
}
}
</script>
// 子组件中
<template>
<div class="com_a">
<span>{{messageA}}</span>
<p>获取父组件的值为: {{parentVal}}</p>
</div>
</template>
<script>
export default {
data() {
return {
messageA: 'this is old'
}
},
computed:{
parentVal(){
return this.$parent.msg;
}
}
}
</script>
要注意边界情况
,如在#app上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent得到的是undefined,而在最底层的子组件拿$children是个空数组。也要注意得到$parent和$children的值不一样,$children 的值是数组,而$parent是个对象
总结:
上面两种方式用于父子组件之间的通信, 而使用props进行父子组件通信更加普遍; 二者皆不能用于非父子组件之间的通信。
总结
常见使用场景可以分为三类:
父子组件通信: props; $parent / $children; provide / inject ; ref ; $attrs / $listeners
兄弟组件通信: eventBus ; vuex
跨级通信: eventBus;Vuex;provide / inject 、$attrs / $listeners
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。