彻底理解Vue组件间7种方式通信

一、父子组件通信:

1.1、传递静态或动态的 Prop

单向数据流

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外变更父级组件的状态,从而导致你的应用的数据流向难以理解。

这样,你已经知道了可以像这样给 prop 传入一个静态的值:

<blog-post title="My journey with Vue"></blog-post>

你也知道 prop 可以通过 v-bind 或简写 : 动态赋值,例如:

<!-- 动态赋予一个变量的值 -->
<blog-post :title="post.title"></blog-post>

<!-- 动态赋予一个复杂表达式的值 -->
<blog-post :title="post.title + ' by ' + post.author.name"></blog-post>

举例子:

父页面:

 子页面:

展示:

1.2、子父组件emit通信

子页面

 <button v-on:click="giveAdvice">Click me for advice </button>
 data() {
    return {
       possibleAdvice: ['Yes', 'No', 'Maybe']
    };
  },
methods:{
     giveAdvice: function () {
      console.log('子页面', this.possibleAdvice )
      var randomAdviceIndex = Math.floor(Math.random() * this.possibleAdvice.length)
      console.log('子页面randomAdviceIndex---', randomAdviceIndex)
// 名字give-advice和父元素保持一致
this.$emit('give-advice', this.possibleAdvice[randomAdviceIndex]) } },

父页面

// 名字give-advice和子页面$emit名字保持一致
<a-blog v-on:give-advice="showAdvice" :title="post.title + ' by ' + post.author.name" :post="post"/>
methods: {
    showAdvice: function (advice) {
      console.log('-父页面-advice---', advice)
    }
   
  }

结果:

1.3、provide / inject

详细:

这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在其上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。

provide 选项应该是一个对象或返回一个对象的函数。该对象包含可注入其子孙的 property。在该对象中你可以使用 ES2015 Symbols 作为 key,但是只在原生支持 Symbol 和 Reflect.ownKeys 的环境下可工作。

inject 选项应该是:

一个字符串数组,或
一个对象,对象的 key 是本地的绑定名,value 是:
在可用的注入内容中搜索用的 key (字符串或 Symbol),或
一个对象,该对象的:
from property 是在可用的注入内容中搜索用的 key (字符串或 Symbol)
default property 是降级情况下使用的 value

父页面APP.vue

import ABlog from './components/demo/a-blog.vue'
name: 'App',
provide: {
    foo: 'I from parents bar'
 },

 components: {
   ABlog
},

子页面a-blog.vue

<b-blog  />
import BBlog from './b-blog.vue'

.....
name: 'App',

components: {
    BBlog 
 },

孙子页面:b-blog.vue

<div class='b-blog'>
      <p>孙子页面:{{foo}}</p>
  </div>
.....
name: 'b-blog',
inject: ['foo'],
created() {
        console.log('孙子页面inject---', this.foo) // => "bar"
},


Vue.observable( object )

让一个对象可响应。Vue 内部会用它来处理 data 函数返回的对象。

返回的对象可以直接用于渲染函数和计算属性内,并且会在发生变更时触发相应的更新。也可以作为最小化的跨组件状态存储器,用于简单的场景:

父页面:

<button @click="() => changeColor()">改变color</button>
import ABlog from './components/demo/a-blog.vue'
name: 'App',
 provide() { // 方法二:使用2.6最新API Vue.observable 优化响应式 provide
    this.theme = Vue.observable({
      color: "blue"
    });
    return {
      theme: this.theme
    };
  },
.....
methods: {
  
    changeColor(color) {
      if (color) {
        this.theme.color = color;
      } else {
        this.theme.color = this.theme.color === "blue" ? "red" : "blue";
      }
    }

   
  }

儿子页面

<b-blog  />
...
import BBlog from './b-blog.vue'
...
components: {
    BBlog 
 },

孙子页面

<p :style="{ color: theme.color }">孙子页面b-blog 组件</p>
...
inject: {
        theme: {
            //函数式组件取值不一样
            default: () => ({})
        }
},

1.4、$parent / $children与 ref

        $parent / $children

父页面:

<button @click="changeVal">点击改变子组件的值</button>
....
data() {
    return {
        msg: 'hello, I from parents page!'
    };
  },
....
methods: {
    changeVal(){
      console.log('父页面----', this.$children[0])
      this.$children[0].message = "hello, "
    }
}

子页面:

<span>改变自组建的的值::{{message}}</span>
<p>子页面this.$parent.msg获取父组件的值:{{parentVal}}</p>
....
data() {
    return {
      possibleAdvice: ['Yes', 'No', 'Maybe'],
      message:'songxiaotao**'
    };
 },
....
components: {
    BBlog 
  },
  computed: {
    parentVal(){
      return this.$parent.msg;
    }
  },
...

点击按钮前

点击按钮后

      ref的用法

父页面:

 <a-blog ref="refDom"  v-on:give-advice="showAdvice" :title="post.title + ' by ' + post.author.name"  :post="post"/>
    
mounted () {
    const refDom = this.$refs.refDom // 打印子页面的refDom的数据和方法
    console.log('mounted---refDom ---', refDom, refDom.message, refDom.possibleAdvice)
    refDom.refFn()// 
    refDom.giveAdvice()// 
  },

子页面

1.5、$emit/$on

假如一个页面AB兄弟组件通信, 可以用此方法 

var EVENT=new Vue();
vue.prototype = EVENT
this.EVENT.$emit(事件名,数据);
this.EVENT.$on(事件名,data => {});
this.EVENT.$on('test', function (msg) {
  console.log(msg)
})
this.EVENT.$emit('test', 'hi')
// => "hi"

1.6、attrs/$listeners

2.4.0 新增

  • 类型:{ [key: string]: string }

  • 只读

  • 详细:

    包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用。

  • vm.$listeners

    2.4.0 新增

    • 类型:{ [key: string]: Function | Array<Function> }

    • 只读

    • 详细:

      包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。

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

inheritAttrs:默认值为 true。

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

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

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

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

来看看例子加深一下印象

父组件:

<template>
  <div id="app">
   <a-blog :width="width" :height="height" :book="book" :fruit="fruit"  @updateFn="handleUpdate" @deleteFn="handleDelete"/>
    
  </div>
</template>
data() {
    return {
       300,
      height: 200,
      book: 'java',
      fruit: 'banana',
      weight: '70kg'

    };
  },
...
components: {
  ABlog
},
...
methods: {
    handleUpdate(val){
      console.log('父组件已经接收到--update info', val);
    },
    handleDelete(){
      console.log('delete info');
    }
}
...

儿子页面:a-blog.vue

<p> 儿子页面: attrs--      {{ $attrs }} --- {{ $listeners }}</p>
    <b-blog @addInfo="addInfo" v-bind="$attrs"  v-on="$listeners"/>
...
import BBlog from './b-blog.vue'
props: {
    height:[String, Number],
},
components: {
    BBlog 
},
...
created () {
    console.log('儿子页面-this.$attrs', this.$attrs); 
    console.log('儿子页面-this.$listeners', this.$listeners); // updateInfo: f, delInfo: f
  },
...
methods:{
   addInfo(){
      console.log('---addInfo----')
    }
  },
  
}

孙子页面:b-blog

<template>
   <div class='b-blog'>
      <!-- <p>孙子页面:{{foo}}</p> -->
      <!-- <p :style="{ color: theme.color }">孙子页面b-blog 组件</p> -->
      <p> 孙子页面--- attrs--      {{ $attrs }} --- {{ $listeners }}</p>
   </div>
   
</template>
....
 props: {
       book:[String, Number]
    },
...
 created() {
        // console.log('孙子页面inject---', this.theme) // => "bar"
        console.log('孙子页面-this.$attrs', this.$attrs); 
        console.log('孙子页面-this.$listeners', this.$listeners);
        this.$emit('updateFn', {a:'孙子传过来的的值'})
  },

1.7、vuex 

-----------------陆续更新中-----

原文地址:https://www.cnblogs.com/pikachuworld/p/15252070.html