Vue组件间通信的几种方式

用vue可以是要组件复用的,而组件实例的作用域是相互独立,这意味着不同组件之间的数据无法互相引用
一般来说,组件之间可以有几种关系:

Vue组件间通信的几种方式_第1张图片
如上图所示,A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)

方法一:props/ $emit

1、父组件通过 props 的方式向子组件传递

//App.vue父组件
<template>
  <div id="app">
    <users v-bind:users="users">users> //前者自定义名称便于子组件调用,后者要传递数据名
  div>
template>
<script>
import Users from "./components/Users"
export default {
  name: 'App',
  data(){
    return{
      users:["Henry","Bucky","Emily"]
    }
  },
  components:{
    "users":Users
  }
}
script>
//users子组件
<template>
  <div class="hello">
    <ul>
      <li v-for="user in users">{{user}}li>//遍历传递过来的值,然后呈现到页面
    ul>
  div>
template>
<script>
export default {
  name: 'HelloWorld',
  props:{
    users:{           //这个就是父组件中子标签自定义名字
      type:Array,
      required:true
    }
  }
}
script>

2、 子组件通过 $emit 向父组件传值

// 子组件
<template>
  <header>
    <h1 @click="changeTitle">{{title}}h1>//绑定一个点击事件
  header>
template>
<script>
export default {
  name: 'app-header',
  data() {
    return {
      title:"Vue.js Demo"
    }
  },
  methods:{
    changeTitle() {
      this.$emit("titleChanged","子向父组件传值");//自定义事件  传递值“子向父组件传值”
    }
  }
}
script>
// 父组件
<template>
  <div id="app">
    <app-header v-on:titleChanged="updateTitle" >app-header>//与子组件titleChanged自定义事件保持一致
   // updateTitle($event)接受传递过来的文字
    <h2>{{title}}h2>
  div>
template>
<script>
import Header from "./components/Header"
export default {
  name: 'App',
  data(){
    return{
      title:"传递的是一个值"
    }
  },
  methods:{
    updateTitle(e){   //声明这个函数
      this.title = e;
    }
  },
  components:{
   "app-header":Header,
  }
}
script>

方法二:$emit/ $on

这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地 实现了任何组件间的通信,包括父子、兄弟、跨级 。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。

<div id="itany">
    <my-a>my-a>
    <my-b>my-b>
    <my-c>my-c>
div>
<template id="a">
  <div>
    <h3>A组件:{{name}}h3>
    <button @click="send">将数据发送给C组件button>
  div>
template>
<template id="b">
  <div>
    <h3>B组件:{{age}}h3>
    <button @click="send">将数组发送给C组件button>
  div>
template>
<template id="c">
  <div>
    <h3>C组件:{{name}},{{age}}h3>
  div>
template>
<script>
var Event = new Vue();//定义一个空的Vue实例
var A = {
    template: '#a',
    data() {
      return {
        name: 'tom'
      }
    },
    methods: {
      send() {
        Event.$emit('data-a', this.name);
      }//发送name,给空的父组件,事件名叫data-a
    }
}
var B = {
    template: '#b',
    data() {
      return {
        age: 20
      }
    },
    methods: {
      send() {
        Event.$emit('data-b', this.age);
      }//发送age,给空的父组件,事件名叫data-b
    }
}
var C = {
    template: '#c',
    data() {
      return {
        name: '',
        age: ""
      }
    },
    mounted() {//在模板编译完成后执行
     Event.$on('data-a',name => {
         this.name = name;//箭头函数内部不会产生新的this,这边如果不用=>,this指代Event
     })//通过父组件传name值过来,通过$on来监听data-a事件
     Event.$on('data-b',age => {
         this.age = age;
     })//通过父组件传age值过来,通过$on来监听data-a事件
    }
}
var vm = new Vue({
    el: '#itany',
    components: {
      'my-a': A,
      'my-b': B,
      'my-c': C
    }//新建一个空的父组件,用来传递A、B的值给C(将ABC都设为这个父组件的子组件)
});    
script>

$on 监听了自定义事件 data-a和data-b,因为有时不确定何时会触发事件,一般会在 mounted 或 created 钩子中来监听。

方法三:Vuex

跨级组件之间传递

配置

import Vue from 'vue'
import Vuex from 'vuex'
 
Vue.use(Vuex)
 
export default new Vuex.Store({
  //数据,相当于data
  state: {
  },
  getters: {
  },
  //里面定义方法,操作state方发
  mutations: {
  },
  // 操作异步操作mutation
  actions: {
  },
  modules: {
  },
})

核心概念

vuex中一共有五个状态 State Getter Mutation Action Module

State

提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,相似与data

定义

import Vue from 'vue'
import Vuex from 'vuex'
 
Vue.use(Vuex)
 
export default new Vuex.Store({
  //数据,相当于data
  state: {
    name:"张三",
    age:12,
    count:0
  },
})

调用:

  • 在标签中直接使用
<p>{{$store.state.name}}p>
<p>{{$store.state.age}}p>
  • 在方法中使用
this.$store.state.name
  • mapState 辅助函数
// 从vuex中按需导入mapstate函数
import { mapState } from "vuex";

// 注意:当前组件需要的全局数据,映射为当前组件computed属性
computed: mapState([
  // 映射 this.count 为 store.state.count
  'name','age','sex'
])

//使用
<p>{{name + age}}</p>

Getter

定义

有时候我们需要从 store 中的 state 中派生出一些状态

import Vue from 'vue'
import Vuex from 'vuex'
 
Vue.use(Vuex)
 
export default new Vuex.Store({
  //数据,相当于data
  state: {
    name:"张三",
    age:12,
    count:0
  },
   getters: {
    countTodos(){
    retuen count+1
    },
   // 通过属性访问
   // Getter 接受 state 作为其第一个参数,接受其他 getter 作为第二个参数
    ageTodos (state,getters) {  
      return state.age + getters.countTodos
    }
    //通过方法访问
    getTodoById: (state) => (id) => {
    return state.count.find(todo => count === id)
  }
  }
})

调用:

  • 在标签中直接使用
<p>{{$store.getters.countTodos}}p>
  • 在方法中使用
this.$store.getters.countTodos
  • mapGetters 辅助函数
// 从vuex中按需导入mapGetters函数
import { mapGetters } from "vuex";

// 注意:当前组件需要的全局数据,映射为当前组件computed属性
computed: mapGetters([
  // 映射 this.count 为 store.state.count
  'countTodos'
])

//使用
<p>{{countTodos}}</p>

Mutation

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation

import Vue from 'vue'
import Vuex from 'vuex'
 
Vue.use(Vuex)
 
export default new Vuex.Store({
  //数据,相当于data
  state: {
    name:"张三",
    age:12,
    count:0
  },
   mutations: {
   // 接受 state 作为第一个参数
   addcount(state,num){
     state.count=+state.count+num
   },
   reduce(state){
    state.count--
   }
  }
})

使用

<button @click='btn'>增加数据button>
<button @click='btn1'>减少数据button>
// 方法1
methods:{
//加法
btn(){
this.$store.commit("addcount",10)     //每次加十
}
//减法
btn1(){
this.$store.commit("reduce") 
}
}

// 方法2
methods:{
...mapMutations(["addcount","reduce"])
//加法
btn(){
this.addcount(10)     //每次加十
}
//减法
btn1(){
this.reduce() 
}
}

Action ——进行异步操作

Action和Mutation相似,Mutation 不能进行异步操作,若要进行异步操作,就得使用Action

定义

actions: {
//接受一个与 store 实例具有相同方法和属性的 context 对象
asyncAdd(context) {
    setTimeout(() => {
    context.commit('reduce')
    },1000)
  }
}

使用

  • 直接使用 dispatch触发Action函数
this.$store.dispatch("reduce")

使用辅助函数

...mapActions(["asyncAdd"]),
btn2(){
  this.asyncAdd()
}

Modules

Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter

const moduleA = {
        state: () => ({ ... }),
        mutations: { ... },
        actions: { ... },
        getters: { ... }
    }
    const moduleB = {
        state: () => ({ ... }),
        mutations: { ... },
        actions: { ... }
    }
    const store = new Vuex.Store({
        modules: {
            a: moduleA,
            b: moduleB
        }
    })
    this.store.state.a // -> 获得moduleA 的状态
    this.store.state.b // -> 获得moduleB 的状态

提示

vuex 是 vue 的状态管理器,存储的数据是响应式的。但是并不会保存起来刷新之后就回到了初始状态,具体做法应该在vuex里数据改变的时候把数据拷贝一份保存到localStorage里面,刷新之后,如果localStorage里有保存的数据,取出来再替换store里的state。

方法四: $attrs/ $listeners

  • $attrs–继承所有的父组件属性(除了 prop 传递的属性、class 和 style ),一般用在子组件的子元素上,并且可以通过 v-bind=" $attrs" 传入内部组件。

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

示例

同族跨级组件数据传输

A组件(App.vue)

<template>
  <div id="app">
    
    <child1  :pchild1="child1" :pchild2="child2" :pchild3="child3" @method1="onMethod1" @method2="onMethod2">child1>
  div>
template>

<script>
import Child1 from "./Child1.vue";
export default {
  data() {
    return {
      child1:'1',
      child2: 2,
      child3:{
        name:'child3'
      }
    };
  },
  components: { Child1 },
  methods: {
    onMethod1(msg1) {
      console.log(`${msg1} running`);
    },
    onMethod2(msg2) {
      console.log(`${msg2} running`);
    },
  },
};
script>

B组件(Child1.vue)

<template>
  <div class="child-1">
    <h2>in child1h2>
    <p>props: {{ pchild1 }}p>
    <p>$attrs: {{ $attrs }}p>
    <hr/>
    
    
    <child2 v-bind="$attrs" v-on="$listeners">child2>
  div>
template>

<script>
import Child2 from "./Child2.vue";
export default {
  data() {
    return {
      child1:'child1'  
    };
  },
  components: { Child2 },
  props: {
    pchild1:{
      type:String
    }
  },  
  inheritAttrs: false,
  mounted() {
    this.$emit("method1",this.child1);
  },
};
script>

C 组件 (Child2.vue)

<template>
  <div class="child-2">
    <h2>in child2:h2>
    <p>props: {{ pChild2 }}p>
    <p>$attrs: {{ $attrs }}p>
    <p>pchild3Name: {{ $attrs.pchild3.name }}p>
    <hr/>
  div>
template>

<script>
export default {
  data() {
    return {
      child2:'child2'
    };
  },
  props: {
    pChild2:{
      type:String,
    }
  },
  inheritAttrs: false,
  mounted() {
    this.$emit("method2",this.child2);
  },
};
script>

方法五: provide/inject

provideinject 选项需要一起使用,它允许祖先组件向其所有子孙组件注入依赖,并在其上下游关系成立的时间里始终生效,不论组件层级有多深。

// 祖先.vue
export default {
 provide: {
   name: 'albertos'
 }
}
/**************************************/
// 子孙.vue
export default {
 inject: ['name'],
 mounted () {
   console.log(this.name);  // albertos
 }
}

方法六:$parent/ $children与ref

  • ref:如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子组件上,引用就指向组件实例
// component-a 子组件
export default {
  data () {
    return {
      title: 'Vue.js'
    }
  },
  methods: {
    sayHello () {
      window.alert('Hello');
    }
  }
}

// 父组件
<template>
  <component-a ref="comA">component-a>
template>
<script>
  export default {
    mounted () {
      const comA = this.$refs.comA;//拿到comA这个引用对象
      console.log(comA.title); 
      comA.sayHello();  // 调用comA中的方法
    }
  }
script>
  • $parent/ $children: 访问父/子实例 (不推荐使用,因为不利于维护,一旦组件层次发生了变化,就需要更改其中的层次关系)
  • Vue组件间通信的几种方式_第2张图片

方法六:EventBus

EventBus 又称为事件总线。在Vue中可以使用 EventBus 来作为沟通桥梁的概念,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以上下平行地通知其他组件,但也就是太方便所以若使用不慎,就会造成难以维护的“灾难”,因此才需要更完善的Vuex作为状态管理中心,将通知的概念上升到共享状态层次。

初始化

首先需要创建事件总线并将其导出,以便其它模块可以使用或者监听它。我们可以通过两种方式来处理。先来看第一种,新创建一个 .js 文件,比如 event-bus.js

// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()

使用


<template>
    <button @click="sendMsg()">-button>
template>

<script> 
import { EventBus } from "../event-bus.js";
export default {
  methods: {
    sendMsg() {
      EventBus.$emit("aMsg", '来自A页面的消息');
    }
  }
}; 
script>

<template>
  <p>{{msg}}p>
template>

<script> 
import { 
  EventBus 
} from "../event-bus.js";
export default {
  data(){
    return {
      msg: ''
    }
  },
  mounted() {
    EventBus.$on("aMsg", (msg) => {
      // A发送来的消息
      this.msg = msg;
    });
  }
};
script>

前面提到过,如果使用不善,EventBus会是一种灾难,到底是什么样的“灾难”了?大家都知道vue是单页应用,如果你在某一个页面刷新了之后,与之相关的EventBus会被移除,这样就导致业务走不下去。还要就是如果业务有反复操作的页面,EventBus在监听的时候就会触发很多次,也是一个非常大的隐患。这时候我们就需要好好处理EventBus在项目中的关系。通常会用到,在vue页面销毁时,同时移除EventBus事件监听

import { 
  eventBus 
} from './event-bus.js'
EventBus.$off('aMsg', {})

你也可以使用 EventBus.$off('aMsg') 来移除应用内所有对此某个事件的监听。或者直接调用 EventBus.$off() 来移除所有事件频道,不需要添加任何参数

总结:

父子通信

  • 父向子传递数据是通过 props,子向父是通过 events($ emit
  • 通过父链 / 子链也可以通信($ parent / $ children);
  • ref 也可以访问组件实例;
  • provide / inject API;
  • $attrs/ $listeners

兄弟通信:

  • Bus
  • Vuex

跨级通信

  • Bus
  • Vuex
  • provide / inject API
  • $attrs/ $listeners

你可能感兴趣的:(Vue,vue.js,前端,javascript)