Vue - $attrs及$listeners属性实现多级嵌套组件通信

前言

组件是 vue.js 最强大的功能之一,而组件实例的作用域是相互独立的,这就意味着不同组件之间的数据无法相互引用。

 

如上图所示,A 和 B、B 和 C都是父子关系,A 和 C 是隔代关系(可能隔多代)。针对不同的使用场景,如何选择行之有效的通信方式?

A 组件与 B 组件之间的通信: (父子组件)

如上图所示,A、B、C三个组件依次嵌套,按照 Vue 的开发习惯,父子组件通信可以通过以下三种方式实现:

1、A To B 通过props的方式向子组件传递数据,B To A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现数据传递

2、通过设置全局Vuex共享状态,通过 computed 计算属性和 commit mutation的方式实现数据的获取和更新,以达到父子组件通信的目的。

3、Vue Event Bus,使用Vue的实例,实现事件的监听和发布,实现组件之间的传递。

往往数据在不需要全局的情况而仅仅是父子组件通信时,使用第一种方式即可满足。

A 组件与 C 组件之间的通信: (跨多级的组件嵌套关系)

如上图,A 组件与 C 组件之间属于跨多级的组件嵌套关系,以往两者之间如需实现通信,往往通过以下方式实现:

  1. 借助 B 组件的中转,从上到下props依次传递,从下至上,$emit事件的传递,达到跨级组件通信的效果
  2. 借助Vuex的全局状态共享
  3. Vue Event Bus,使用Vue的实例,实现事件的监听和发布,实现组件之间的传递。

多级组件嵌套需要传递数据时,通常使用的方法是通过 vuex。但如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,未免有点大材小用。为此 Vue2.4 版本提供了另一种方法$attrs/$listeners

$props/$attrs/$listeners介绍

$props:当前组件接收到的 props 对象,Vue 实例代理了对其 props 对象属性的访问。

$attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 interitAttrs 选项一起使用。 通俗的理解为:子辈可以通过$attrs将未在自己组件内注册的祖辈传递下来的参数接收来,并传递孙辈。

inheritAttrs:默认情况下父作用域的不被认作 props 的特性绑定 (attribute bindings) 将会“回退”且作为普通的 HTML 特性应用在子组件的根元素上。通过设置 inheritAttrs 为 false,这些默认行为将会被去掉。而通过实例属性 $attrs 可以让这些特性生效,且可以通过 v-bind 显性的绑定到非根元素上。

$listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

代码示例 

A组件(index.vue)

<template>
    <div>
        <h1>我是parent</h1>
        <hr>
        <child1 
            :foo="foo" 
            :boo="boo" 
            :coo="coo" 
            :doo="doo" 
            title="跨级组件通信" 
            @test1="test1" 
            @test2="test2"
        ></child1>
    </div>
</template>
<script>
    const child1 = () => import("./child1.vue");
    export default {
        components: { child1 },
        data() {
            return {
                foo: "foo",
                boo: "boo",
                coo: "coo",
                doo: "doo"
            };
        },
        methods: {
            test1() {
                alert('test1')
            },
            test2() {
                alert('test2')
            }
        },
    };
</script>

B组件(child1.vue)

<template>
    <div>
        <h1>我是child1</h1>
        <p>props: {{ $props }}</p>
        <p>attrs: {{ $attrs }}</p>
        <button @click="toParent1()">触发test1方法</button>
        <hr>
        <child2 v-bind="$attrs" v-on="$listeners"></child2>
    </div>
</template>
<script>
    const child2 = () => import("./child2.vue");
    export default {
        components: {
            child2
        },
        inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
        props: {
            foo: String // foo作为props属性绑定
        },
        created() {
            console.log(this.$attrs); // { "boo": "boo", "coo": "coo", "doo": "doo", "title": "跨级组件通信" }
        },
        methods: {
            toParent1() {
                this.$emit('test1') // 执行test1方法,alert弹框显示'test1'
            }
        },
    };
</script>

C组件(child2.vue)

<template>
    <div class="border">
        <h1>我是child2</h1>
        <p>props: {{ $props }}</p>
        <p>attrs: {{ $attrs }}</p>
        <button @click="toParent2">触发test2方法</button>
        <hr>
        <child3 v-bind="$attrs" v-on="$listeners"></child3>
    </div>
</template>
<script>
    const child3 = () => import("./child3.vue");
    export default {
        components: {
            child3
        },
        inheritAttrs: false,
        props: {
            boo: String
        },
        created() {
            console.log(this.$attrs); // { "coo": "coo", "doo": "doo", "title": "跨级组件通信" }
        },
        methods: {
            toParent2() {
                this.$emit('test2') // 执行test2方法,alert弹框显示'test2'
            }
        },
    };
</script>

D组件(child3.vue)

<template>
    <div class="border">
        <h1>我是child3</h1>
        <p>props: {{ $props }}</p>
        <p>attrs: {{ $attrs }}</p>
        <button @click="toParent1">触发test1方法</button>
        <button @click="toParent2">触发test2方法</button>
    </div>
</template>
<script>
    export default {
        props: {
            coo: String,
            title: String
        },
        methods: {
            toParent1() {
                this.$emit('test1') // 执行test1方法,alert弹框显示'test1'
            },
            toParent2() {
                this.$emit('test2') // 执行test2方法,alert弹框显示'test2'
            }
        },
    };
</script>

最终页面展示效果

总结:

A组件一共传了四个变量foo、boo、coo、doo,一个属性title,两个接收函数test1,test2

组件B中props中接收了一个foo。所以$attrs传的是剩下的 { "boo": "boo", "coo": "coo", "doo": "doo", "title": "跨级组件通信" }

组件C中props中接收了一个boo。所以$attrs传的是剩下的 { "coo": "coo", "doo": "doo", "title": "跨级组件通信" }

组件D中props中接收了coo和title。所以$attrs传的是剩下的 { "doo": "doo"}

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@Demi

您的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值