Vue祖孙组件如何实现传值
作者:小天才程序员
先看基础
祖孙组件,也就是 3
层嵌套的组件。关于 vue
中父子组件之间的数据传递是通过 props
和 $emit
实现,参考 Vue 父子组件传值。
那祖孙组件之间传值怎么实现,先了解下面的几个 vue
属性。
$props
当前组件接收到的 props
对象。Vue 实例代理了对其 props
对象 property
的访问。
$attrs
$attrs
是一个 Object
,它包含了父作用域中不作为 prop
被识别 (且获取) 的 attribute
绑定 (class
和 style
除外)。
如果组件没有声明任何 prop
时,这里会包含所有父作用域的绑定 (class
和 style
除外),并且可以通过 v-bind="$attrs"
传入内部组件。
怎么理解呢?
就是父组件绑定到子组件上的属性,在子组件中没有声明 props
进行接收的那些属性会被包含在 attrs
中,举个栗子
在父组件中对子组件绑定了两个属性 data1
data2
。
parent.vue
<Child :data1="data1" :data2="data2" /> <script> import Child from './child' ... data(){ return{ data1: 'String', data2: ['String','Array'] } } ... </script>
但在子组件的 props
只对 data1
做了接收声明,那 data2
就会被包含在 $attrs
中。
在子组件中也是可以取到 $attrs
的值的,既然是对象,那就还可以按照属性名来取值的。
child.vue
<template> <div> <div>$attrs: {{ $attrs }}</div> <div>data2: {{ $attrs['data2'] }}</div> </div> </template> <script> ... props: ['data1'], /* <--看这里,只对data1做接收声明 */ data(){ return{...} } ... </script>
其实到这里还没有结束,接着聊聊 inheritAttrs
吧。不过这和传值之间关系不大。
inheritAttrs
是 vue 2.4.0
的新增选项,官方的介绍是酱紫的。
1️⃣ 默认情况下父作用域的不被认作 props
的 attribute
绑定 (attribute bindings
) 将会“回退”且作为普通的 HTML attribute
应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrs
到 false
,这些默认行为将会被去掉。
2️⃣ 而通过 (同样是 2.4 新增的) 实例 property $attrs
可以让这些 attribute
生效,且可以通过 v-bind
显性的绑定到非根元素上。
🚫 注意:这个选项不影响 class
和 style
绑定。
第1️⃣简单理解就是前面说的,在子组件中的 props
没有声明接收的属性(也就是 $attrs
所包含的属性)会被绑定到这个子组件的 HTML
根节点上,我们检查代码也是可以看到的。
就像下面的例子,来自父组件的消息没有被接收时会作为属性被渲染到子组件的根节点上。
然后是使用 inheritAttrs: false
可以避免被渲染。
第2️⃣说的就是可以通过 v-bind="$attrs"
把这些属性绑定到其他的节点上(包括子节点,这是祖孙组件传值的技术基础)
$listeners
vue 2.4.0
新增
$listeners
是个 Object
。包含了父作用域中的 (不含 .native
修饰器的) v-on
事件监听器。它可以通过 v-on="$listeners"
传入内部组件。
祖传孙
在 vue
中,祖孙组件之间是不能直接通信的,需要通过父组件作为 中间组件。
实际上祖父的关系就是两个 父与子 的关系。
1. $props 链
【不推荐使用】
既然是两个父与子之间的关系,那就可以 祖传父 再由 父传子。而 props 可以用来接收来自父组件的值,那就可以通过 props 实现链式传递了。不举栗子🌰了,举个香蕉吧🍌。
传递次序:GrandFather → Father → GrandSon
① 在 GrandFather
中给 Father
传递了两条消息。
GrandFather.vue
<template> <div class="parent"> 🎈爷爷 <Father :msg1="msg1" :msg2="msg2" /> </div> </template> <script> import Father from './Father' export default { components: {Father}, data () { return { msg1: '1️⃣我是GrandFather,把第二条传给GrandSon', msg2: '2️⃣GrandSon你好,我是GrandFather' } } } </script>
② Father
中使用 props
接收了来自 GrandFather
的所有消息。是的,他把所有的消息都收下了而且还可以随便看😱。
当然,使用 props
链传递就必须要 Father
接收之后才能继续传递。
看完消息之后,Father
按照 GrandFather
的意思,把 msg2
传递给了 GrandSon
Father.vue
<template> <div class="parent"> 🎈父亲 <p>GrandFather说:{{msg1}}。{{msg2}}</p> <GrandSon :msg2="msg2" /> </div> </template> <script> import GrandSon from './GrandSon' export default { props: ['msg1', 'msg2'], components: {GrandSon}, } </script>
③ 终于到 GrandSon
了,它通过 props
从 Father
那里接收到了来自 GrandFather
的消息。
GrandSon.vue
<template> <div class="child"> 🎈孙子 <p>GrandFather说:{{msg2}}</p> </div> </template> <script> export default { props: ['msg2'] } </script>
小结
这种方式虽然是比较容易理解,但也是比较繁琐的。中间组件需要接收所有的 props
等。
2. $attrs
上面的 $props
传值方式必须要经过 Father
接收之后继续传递,也是个缺点,毕竟 Father
还是很忙的,要负责自己的功能,不能总为爷孙俩接传消息😂。
vue
在 2.4.0
版本中新增了 $attrs
属性。根据前面的理解 $attrs
就是没有在 props
中声明要接收的一些属性。此外,还可以通过 v-bind="$attrs"
把来自父组件的一些属性直接传递到子组件中。
这样一来,Father
组件就没必要在 props
中声明接收那些不必要属性了。看看实例吧!
① GrandFather
组件不用做修改
② 这次在 Father
中只在 props
接收了 msg1
,与自己无关的直接使用 v-bind="attrs"
绑定到子组件上。
当然,在 Father
中还是可以访问 $attrs
的。在代码中访问要使用 this.$attrs
Father.vue
<template> <div class="parent"> 🎈父亲 <p>$attrs:{{$attrs}}</p> <GrandSon v-bind="$attrs" /> </div> </template> <script> import GrandSon from './GrandSon' export default { props: ['msg1'], //只接收了msg1 components: {GrandSon}, } </script>
③ 子组件也不需要做修改
在 Father
中接收了 msg1
,所以在 Father
中继续传递到 GrandSon
的就只有 msg2
了。
孙传祖
$listeners
给 Father
组件绑定 自定义事件 getReply
,便于后面在 GrandSon
中触发
GrandFather.vue
<template> <div class="parent"> 🎈爷爷 <div>GrandSon的回复:{{reply}}</div> <Father :msg1="msg1" :msg2="msg2" @getReply="getReply"/> </div> </template> <script> import Father from './Father' export default { components: {Father}, data () { return { msg1: '1️⃣我是GrandFather,把第二条传给GrandSon', msg2: '2️⃣GrandSon你好,我是GrandFather', reply: '' //接收来自GrandSon的消息 } }, methods: { /* 将获得的数据绑定到data中,便于视图层渲染 */ getReply (param) { this.reply = param } } } </script>
在 Father
中使用 v-on="$listeners"
把 GrandFather
的事件绑定到 GrandSon
Father.vue
<template> <div class="parent"> 🎈父亲 <p>$attrs:{{$attrs}}</p> <GrandSon v-bind="$attrs" v-on="$listeners" /> </div> </template> <script> import GrandSon from './GrandSon' export default { props: ['msg1'], components: { GrandSon }, } </script>
在 GrandSon
中触发来自 GrandFather
的自定义事件就 🆗 了,有两种方式。
① this.$listeners.eventName(param)
② this.$emit(eventName, param)
GrandSon.vue
<template> <div class="child"> 🎈孙子 <p>GrandFather说:{{msg2}}</p> <button @click="reply">回复GrandFather</button> </div> </template> <script> export default { props: ['msg2'], data () { return { replyWord: 'GrandFather你好,我是GrandSon,收到消息了' } }, methods: { reply () { this.$emit('getReply', this.replyWord) // this.$listeners.getReply(this.replyWord) } } } </script>
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。