Vue组件间通信的方式总结


Vue组件之间通讯的常见方法总结

1. props/$emit

最常用的一种方法。

  • 父组件传值给子组件

子组件中通过声明props传递itemList,父组件引入子组件以后,传入itemListfruitList

  • 子组件传值给父组件

子组件中传递值通过$emit的第二个参数。父组件接受值通过声明方法addItem,并获取其中的参数。

// 父组件
<template>
  <div class="test">
    <h3>this is parents page</h3>
    <cChildA v-on:changeItemList='addItem' v-bind:itemList="fruitList" />
  </div>
</template>

<script>
import cChildA from "./a";
export default {
  data() {
    return {
      fruitList: ["peach", "banana", "lemon"]
    };
  },
  methods:{
    addItem(item){
      console.log(item);
    }
  },
  components: {
    cChildA
  }
};

//子组件
<template>
  <div>
    <h3>{{childTitle}}</h3>
    <ul>
      <li v-on:click="changeItemList" v-for="(item,index) in itemList" v-bind:key="index">{{item}}</li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    itemList: {
      type: Array
    }
  },
  data() {
    return {
      childTitle: `this is child page`
    };
  },
  methods: {
    changeItemList() {
      this.$emit("changeItemList", this.childTitle);
    }
  }
};
</script>

2. Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

在网上看到有人把这个也归类到Vue组件间通信的方法。这么说肯定没错的哈。但是看一下Vuex的定义,它是Vue中用来管理所有组件的状态的。它都可以用于管理所有组件的状态了,父子组件之间肯定可以使用它进行通讯。杀鸡焉用牛刀,所以该方法并不适用父子组件通讯这个场景。

具体使用方法就不详细说了,具体可以参考看一下Vuex文档

平时使用Vuex的时候,需要注意的一个问题,是Vuex的数据不是持久化的,也就是说,页面刷新,或者跳转到第三方页面的时候,Vuex中的数据会清空,所以一般都会配套使用vuex-persistedstate

3. $emit/$on

该方法的思路是通过一个空的Vue实例作为事件中心,用来触发事件和监听事件。

假设index页面引入了a,b,c三个组件。a和b组件需要传值给c组件。bus.js就是new了一个空的Vue并暴露出来。

这个方法之前没有接触过,所以就详细的写出来具体的用法。总结一下:

  • 使用一个空的Vue实例作为中央事件总线(事件中心)
  • 把事件暴露出去使用 Bus.$emit("event-name", this.data);,就可以把this.data暴露出去
  • 接收方接受数据使用Bus.$on("event-name", data => {this.data = data;});
  • 暴露和接受方法使用的event-name相同即可
//index.vue
<template>
  <div class="test">
    <h3>this is parents page</h3>
    <c-child-a></c-child-a>
    <c-child-b></c-child-b>
    <c-child-c></c-child-c>
  </div>
</template>

<script>
import cChildA from "./a";
import cChildB from "./b";
import cChildC from "./c";

export default {
  data() {
    return {
    };
  },
  methods:{
    
  },
  components: {
    cChildA,
    cChildB,
    cChildC
  }
};
</script>

// a.vue
<template>
  <div>
    <h3>A组件{{name}}</h3>
    <button v-on:click="send">传值给C</button>
  </div>
</template>

<script>
import Bus from './bus';

export default {
  data() {
    return {
      name: 'Chance'
    };
  },
  methods: {
    send() {
      Bus.$emit("com-a", this.name);
    }
  }
};
</script>

<style scoped>
button{
  width: 100px;
  height: 20px;
  border-radius: 10px;
  padding: 0;
}
</style>

// b.vue
<template>
  <div>
    <h3>B组件{{age}}</h3>
    <button v-on:click="send">传值给C</button>
  </div>
</template>

<script>
import Bus from './bus';

export default {
  data() {
    return {
      age: 27
    };
  },
  methods: {
    send() {
      Bus.$emit("com-b", this.age);
    }
  }
};
</script>

<style scoped>
button{
  width: 100px;
  height: 20px;
  border-radius: 10px;
  padding: 0;
}
</style>

// c.vue
<template>
  <div class="c">
    <h3>C组件</h3>
    <div>接受来自A/B组件的值{{name}}{{age}}</div>
  </div>
</template>

<script>
import Bus from './bus';

export default {
  data() {
    return {
      name: "",
      age: ""
    };
  },
  mounted() {
    Bus.$on("com-a", name => {
      this.name = name;
    });
    Bus.$on("com-b", age => {
      this.age = age;
    });
  }
};
</script>

//bus.js
import Vue from 'vue'
const Bus = new Vue()
export default Bus

4. $parent/$children/ref

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

$parent 父实例,如果当前实例有的话。

$children 当前实例的直接子组件。

5. provide/inject

2.2.0新增。 这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

provide/inject使用起来很方便,它的适用场景就是爷父子孙这种的组件关系,只能从祖先组件向子孙后台往下流。

通过在父组件povide钩子中声明变量暴露出数据;在子组件inject钩子中接受数据即可。

// index.vue
<template>
  <div class="test">
    <h3>this is parents page</h3>
    <c-child-c></c-child-c>
    <c-child-b></c-child-b>
  </div>
</template>

<script>
import cChildC from "./c";
import cChildB from "./b";

export default {
  provide: {
    name: 'chance',
    age: '27'
  },
  components: {
    cChildC,
    cChildB
  }
};
</script>

// b.vue
<template>
  <div class="b">
    <h3>B组件</h3>
    <div>接受来自父组件组件的值{{name}}{{age}}</div>
  </div>
</template>

<script>
export default {
  inject: ['name','age']
};
</script>

6. $attrs/$listeners

来自官方的说明:

$attrs

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

$listeners

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

  • $attrs

看着是有些晦涩的,用自己的话,来解释一下就是。在index组件中,data中声明了foo、boo、coo、doo、name、age共6个变量,其中前4个变量和title,index组件都传给了子组件childCom1;在childCom1组件中用props接受了foo变量,剩余的4个变量都是通过$attrs来接受的。往下的传递同理。

该属性需配合inheritAttrs来决定是否可以获取父组件中定义的数据。(PS:试了一下不太好使,不知道是不是没有用对)

  • $listeners

这一块还没有完全弄明白,暂时的理解就是子组件可以调用父组件中的方法。父组件中定义了两个方法,eventClick1和eventClick2。在childCom1组件的mounted钩子的时候,就触发了event1;在childCom2组件中button标签中声明handleClick去触发了event2。

下面的例子中:index.vue为根组件,childCom1是index的子组件,childCom2是childCom1的子组件,childCom3是childCom2的子组件。

// index.vue
<template>
  <div>
    <h2>index page</h2>
    <child-com1
      :foo="foo"
      :boo="boo"
      :coo="coo"
      :doo="doo"
      title="前端"
      @event1='eventClick1'
      @event2='eventClick2'
    ></child-com1>
  </div>
</template>
<script>
const childCom1 = () => import("./childCom1.vue");
export default {
  components: { childCom1 },
  data() {
    return {
      foo: "Javascript",
      boo: "Html",
      coo: "CSS",
      doo: "Vue",
      name: "chance",
      age: 17
    };
  },
  methods:{
    eventClick1(){
      console.log('this is event click1')
    },
    eventClick2(){
      console.log('this is event click2')
    }
  }
};
</script>

// childCom1.vue
<template class="border">
  <div>
    <p>foo: {{ foo }}</p>
    <p>childCom1的$attrs: {{ $attrs }}</p>
    <child-com2 v-bind="$attrs" v-on="$listeners"></child-com2>
  </div>
</template>
<script>
const childCom2 = () => import("./childCom2.vue");
export default {
  components: {
    childCom2
  },
  inheritAttrs: false, // 可以关闭自动挂载到组件根元素上的没有在props声明的属性
  props: {
    foo: String // foo作为props属性绑定
  },
  mounted(){
    this.$emit('event1')
  },
  created() {
    console.log('c1',this.$attrs); // { "boo": "Html", "coo": "CSS", "doo": "Vue", "title": "前端" }
  }
};
</script>

// childCom2.vue
<template>
  <div class="border">
    <p>boo: {{ boo }}</p>
    <p>childCom2: {{ $attrs }}</p>
    <button @click="handleClick">fire</button>
    <child-com3 v-bind="$attrs" v-on="$listeners"></child-com3>
  </div>
</template>
<script>
const childCom3 = () => import("./childCom3.vue");
export default {
  components: {
    childCom3
  },
  inheritAttrs: false,
  props: {
    boo: String
  },
  mounted() {},
  methods: {
    handleClick() {
      this.$emit("event2");
      console.log('fried');
    }
  },
  created() {
    console.log("c2", this.$attrs); // { "coo": "CSS", "doo": "Vue", "title": "前端" }
  }
};
</script>

// childCom3.vue
<template>
  <div class="border">
    <p>childCom3: {{ $attrs }}</p>
  </div>
</template>
<script>
export default {
  props: {
    coo: String,
    title: String
  },
  created(){
    console.log('c3',this.$attrs) // { "doo": "Vue"}
  }
};
</script>

总结

方法 使用场景
props/$emit 正常的父子组件传输
Vuex 多组件之间的状态共享
$emit/$on 兄弟组件之间传输,不通过父组件统一下发数据来解决
$parent/$children/ref 兄弟组件不适用
provide/inject 爷=>父=>子=>孙 数据流向