Vue组件通信整理

总结

常见使用场景可以分为三类:

父子组件通信: props$parent / $childrenprovide / injectref$attrs / $listeners$emit / on

兄弟组件通信: eventBusvuex

跨级通信: eventBusvuexprovide / inject$attrs / $listeners

Props

注意:HTML 中的 attribute 名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。这意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名。

<!-- 父组件 -->
<children v-bind:props-id="id" ></children>

<!-- 父组件数据data -->
id:3


<!-- 子组件 -->

<!-- 方式一 -->
props:['propsId']

<!-- 方式二 -->
props:{
  propsId:{
    type:number,
    requeired:true
  }
}

$emit / on

详细介绍 https://www.cnblogs.com/Scooby/p/14775547.html

$children / $parent

注意 $children 并不保证顺序,也不是响应式的。在 3.x 中,$children property 已被移除,且不再支持。

<!-- 子组件 -->
children-one   data {title:'Children 1'}
children-two   data {title:'Children 2'}
children-three data {title:'Children 3'}

<!-- 父组件 -->    
<children-one></children-one>
<children-two></children-two>
<children-three></children-three>

<!-- 父组件数据 -->  
<!-- 特别注意:如果你需要访问子组件实例,我们建议使用 $refs。 -->
<!-- 特别注意:返回类型为Array<Vue instance> 。-->

this.$children    //[object Object],[object Object],[object Object]

<!-- 子组件数据 可以获取到父组件的数据和方法 -->   
this.$parent      //[object Object]

节制地使用 $parent$children - 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信。

特别注意边界情况。如在#app上拿$parent得到的是new Vue()的实例,在这实例上再拿$parent得到的是undefined,而在最底层的子组件拿$children是个空数组。也要注意得到$parent$children的值不一样,$children 的值是数组,而$parent是个对象。

provide / inject

provide / inject是非响应式。

通常,当我们需要从父组件向子组件传递数据时,我们使用 props。想象一下这样的结构:有一些深度嵌套的组件,而深层的子组件只需要父组件的部分内容。在这种情况下,如果仍然将 prop 沿着组件链逐级传递下去,可能会很麻烦。

对于这种情况,我们可以使用一对 provideinject。无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。这个特性有两个部分:父组件有一个 provide 选项来提供数据,子组件有一个 inject 选项来开始使用这些数据。

provide链。当子孙组件上层父组件重复定义provide提供的值。它的行为跟原型链是一致的——沿着链向上寻找,只要找到就停止。

注意:provideinject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。既如果在祖辈组件里更改provide的值,在孙子组件里是拿不到更新后的值。

/**
* 数据格式:
* provide:Object | () => Object
* inject:Array<string> | { [key: string]: string | Symbol | Object }
*
**/

//祖先组件
//该写法会报错,当需要传递是data中的数据时。
//Uncaught TypeError: Cannot read property 'color' of undefined 。因为访问不到Vue实例 。   
provide: {
  color: this.color, 
}
  
//正确写法,但是此种写法依然不是可响应式的。 
provide() {
  return {
    color: this.color,
  };
}
    
    
/**
* 子孙组件

*inject用来指定一个数组或者一个对象,数组的话就放provide里字段的名称,而对象的话可以指定
*  当前实例中的字段名
*  对应provide里的字段名
*  默认值或者返回默认值的函数
*
**/

// 写法一
const Child = {
  inject:['color']
}  

// 写法二
const Child = {
  inject: {
    foo: {
      from: 'color',       //如果它需要从一个不同名字的 property 注入,则使用 from 来表示其源 property。
      default: () => "默认值" //在 vue 2.5.0+ 的注入可以通过设置默认值使其变成可选项 
    }
  }
}     
    
  
    

provide / inject 可响应式

第一种方法:把值转为函数,记得要用箭头函数,不然不能正确获取this。
//祖先组件
data{ demoData:"red" } 

provide() {
  return {
    test: () => {
      return this.demoData;
    },
  };
}

//子孙组件
<template>
  <div :style="{'color':test()}">测试数据{{test()}}</div>
</template>

第二种方法:把provide所在的Vue实例给传递下去。
/**
* 推荐此种主流写法。
* 可以看到很多UI组件库就是通过这个方式来传递属性的,因为有可能在不确定层级的子组件里要获得祖组件里的值。
*
**/

//祖先组件
data{ demoData:"red" } 

provide() {
  return {
    test: this,
  };
}

//子孙组件
<template>
  <div :style="{'color':test.demoData}">测试数据{{test.demoData}}</div>
</template>

函数式组件使用inject

由于函数式组件的一切都是通过context来传递的,不能像普通组件一样来写inject配置,所以对于函数式组件来说,要拿到provide提供的值就要从context.injections字段里去取。

ref / $refs

ref 被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs 对象上。如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例:

<!-- 如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素 -->
<p ref="p">hello</p>



<!-- 如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据 -->

<!-- child-component组件 -->
data{ test: "demoData" } 

<!-- 父组件组件 -->
<child-component ref="child"></child-component>
this.$refs.child.test    // demoData

v-for 用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实例的数组。

关于 ref 注册时间的重要说明:因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 - 它们还不存在!$refs 也不是响应式的,因此你不应该试图用它在模板中做数据绑定。

eventBus

在vue项目中,父子组件间的通讯很方便。但兄弟组件或多层嵌套组件间的通讯,就会比较麻烦。这时,使用eventBus通讯,就可以很便捷的解决这个问题。

eventBus可以在全局定义,实现全项目通讯,使用方法也很简单。

全局定义,可以将eventBus绑定到vue实例的原型上,也可以直接绑定到window对象上。

eventBus绑定到vue实例的原型

// eventName为自定义事件名称
// 初始化-全局定义。mian.js
Vue.prototype.$EventBus = new Vue();

// 触发事件
this.$EventBus.$emit('eventName', param1,param2,...)

// 监听事件
this.$EventBus.$on('eventName', (param1,param2,...)=>{
    //需要执行的代码
})

// 移除事件
this.$EventBus.$off('eventName');

eventBus绑定到window对象

// eventName为自定义事件名称
// 初始化-全局定义。mian.js
window.eventBus = new Vue();

// 触发事件
EventBus.$emit('eventName', param1,param2,...)

// 监听事件
EventBus.$on('eventName', (param1,param2,...)=>{
    //需要执行的代码
})

// 移除事件
EventBus.$off('eventName');

VUEX

详情文章链接 https://www.cnblogs.com/Scooby/p/15214335.html

$attrs / $listeners

多级组件嵌套需要传递数据时,通常使用的方法是通过vuex。如果仅仅是传递数据,而不做中间处理,使用 vuex 处理,这就有点大材小用了。所以就有了 $attrs / $listeners ,通常配合 inheritAttrs 一起使用。

inheritAttrs:默认值为 true。

默认情况下父作用域的不被认作 props 的 attribute 绑定 (attribute bindings) 将会“回退”且作为普通的 HTML attribute 应用在子组件的根元素上。当撰写包裹一个目标元素或另一个组件的组件时,这可能不会总是符合预期行为。通过设置 inheritAttrsfalse,这些默认行为将会被去掉。而通过 (同样是 2.4 新增的) 实例 property $attrs 可以让这些 attribute 生效,且可以通过 v-bind 显性的绑定到非根元素上。查 看 官 网

注意:这个选项不影响 classstyle 绑定。

感觉还是挺晦涩难懂的,简单的说就是 inheritAttrs:true 继承除props之外的所有属性;inheritAttrs:false 只继承class属性。

$attrs:包含了父作用域中不被认为 (且不预期为) props 的特性绑定 (class 和 style 除外),并且可以通过 v-bind=”$attrs” 传入内部组件。当一个组件没有声明任何 props 时,它包含所有父作用域的绑定 (class 和 style 除外)。

$listeners:包含了父作用域中的 (不含 .native 修饰符) v-on 事件监听器。它可以通过 v-on=”$listeners” 传入内部组件。它是一个对象,里面包含了作用在这个组件上的所有事件监听器,相当于子组件继承了父组件的事件。

祖先组件

<template>
   <child :name="name" :age="age" :infoObj="infoObj" @updateInfo="updateInfo" @delInfo="delInfo" />
</template>
<script>
    import Child from '../components/child.vue'

    export default {
        name: 'father',
        components: { Child },
        data () {
            return {
                name: 'Lily',
                age: 22,
                infoObj: {
                    from: '上海',
                    job: 'policeman',
                    hobby: ['reading', 'writing', 'skating']
                }
            }
        },
        methods: {
            updateInfo() {
                console.log('update info');
            },
            delInfo() {
                console.log('delete info');
            }
        }
    }
</script>

父组件:

<template>
    <grand-son :height="height" :weight="weight" @addInfo="addInfo" v-bind="$attrs" v-on="$listeners"  />
    // 通过 $listeners 将父作用域中的事件,传入 grandSon 组件,使其可以获取到 father 中的事件
</template>
<script>
    import GrandSon from '../components/grandSon.vue'
    export default {
        name: 'child',
        components: { GrandSon },
        props: ['name'],
        data() {
          return {
              height: '180cm',
              weight: '70kg'
          };
        },
        created() {
            // 结果:age, infoObj, 因为父组件共传来name, age, infoObj三个值,由于name被 props接收了,所以只有age, infoObj属性
            console.log(this.$attrs);        
            console.log(this.$listeners); // updateInfo: f, delInfo: f
        },
        methods: {
            addInfo () {
                console.log('add info')
            }
        }
    }
</script>

子组件:

<template>
    <div>
        {{ $attrs }} --- {{ $listeners }}
    <div>
</template>
<script>
    export default {
        ... ... 
        props: ['weight'],
        created() {
            console.log(this.$attrs); // age, infoObj, height 
            console.log(this.$listeners) // updateInfo: f, delInfo: f, addInfo: f
            this.$emit('updateInfo') // 可以触发 father 组件中的updateInfo函数
        }
    }
</script>

参考链接

vue中8种组件通信方式: https://juejin.cn/post/6844903887162310669

eventBus详细原文链接: https://blog.csdn.net/qq_26834399/article/details/106387585

vue中的$attrs和$listeners: https://www.cnblogs.com/dhui/p/12931953.html

provide/inject详情文章链接: https://zhuanlan.zhihu.com/p/184967263

posted @ 2022-05-08 02:24  Scok  阅读(79)  评论(0编辑  收藏  举报