Vue组件通信的五种方式

Vue组件通信的五种方式

一. props/$emit(父子通信)

组件通信中最简单的通信方式,即props/emit,父子通信。

  1. props: 用于子组件获取父组件所传的值

①在子组件标签上采用 属性名=“属性值”

②在props中拿到父组件传过来的属性

使用场景:在父组件中已请求到数据,子组件中需要使用到时,通过props将父组件的数据传递给子组件。

<template>
  <!-- 父组件 -->
  <div class="parent">
    <!-- =====================1. 在子组件标签上采用 属性名="属性值" -->
    <child name="test"></child>
  </div>
</template>

<script>
import child from "./child.vue";
export default {
  components: { child },
};
</script>
<template>
  <!-- 子组件 -->
  <div class="child">{{name}}</div>
</template>

<script>
export default {
  props: {
    // =====================2. 在props中拿到父组件传过来的属性
    name: {
      type: String,
      default: "",
    },
  },
};
</script>
  1. $emit: 用于父组件监听子组件抛出的事件

①子组件通过this.$emit(”事件名“),往上抛出事件

②在子组件标签上采用 @子组件抛出的事件名=“父组件接收到子组件事件后所调用的方法”

使用场景:子组件向后端请求数据完成后,告诉父组件,数据请求成功,可以开始处理了。

<template>
  <!-- 子组件 -->
  <div class="child"></div>
</template>

<script>
export default {
  methods: {
    test() {
      setTimeout(() => {
        // ===================1. 1秒后数据请求成功,告诉父组件requestSuccess, 可以携带参数params
        this.$emit("requestSuccess", params);
      }, 1000);
    },
  },
};
</script>
<template>
  <!-- 父组件 -->
  <div class="parent">
    <!-- =====================2. 在子组件标签上采用 @子组件抛出的事件名="父组件接收到子组件事件后所调用的方法" -->
    <child @requestSuccess="parentReceived"></child>
  </div>
</template>

<script>
import child from "./child.vue";
export default {
  components: { child },
  methods: {
    parentReceived(params) {
      console.log("父组件接收成功", params);
    },
  },
};
</script>

二. vuex(组件之间通信)

  1. 什么是VueX?

官方解释:Vuex是一个专为Vue.js 应用程序开发的状态管理模式。

个人理解:组件存放公共变量的仓库。

上面讲到的props/emit可以理解为"通信",而VueX则更像是一个的仓库,组件们同时使用这个仓库,组件1在VueX中改变一个值后,其他组件中使用到这个变量的地方都会随之改变。总结来说就是:“公共”、“多个页面使用同一个状态”、“响应式”。

使用场景:登录成功后保存用户的登录状态、购物车中的物品等等

*注意!!VueX中的属性在浏览器刷新之后将全部初始化。

// ==========首先介绍一下VueX中的五大属性
import vuex from 'vuex'

const store = vuex.createStore({
  // 1. state:单一状态树,简单理解就是存放VueX中的变量
  // 通过this.$store.state.属性名获取
  state: {
    count: 0
  },
  // 2. getters:类似于vue中的comouted,返回state中的属性做处理之后的结果
  // 通过this.$store.getters.属性名获取
  getters: {
    countAdd(state) {
      return state.count + 1
    }
  },
  // 3. mutations:组件想改变VueX中的state,唯一的方法就是提交mutation
  // 通过this.$store.commit(方法名, payload)    可携带参数payload
  mutations: {
    // 第一个参数通常是state,第二个参数可以是this.$store.commit所携带的参数
    increment(state, payload) {
      state.count++
    }
  },
  // 4. actions:通过提交mutation来改变state,其中可以加入异步操作,可以理解为升级版的异步mutations
  // 通过this.$store.dispatch(方法名, payload)    可携带参数payload
  actions: {
    // 与mutations不同,第一个参数通常是vuex对象,第二个参数可以是this.$store.dispatch所携带的参数
    // 可以理解为context 《=》 this.$store
    increment(context, payload) {
      context.commit('increment')
    },
    // 通常采用对象解构赋值写为:
    increment2({ commit, state, getters }, payload) {
      commit('increment')
    }
  },
  // 5. modules:通常在模块化开发时使用,小项目不推荐使用
  // 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
  modules: {
    modulesA: {
      state: {},
      getters: {},
      mutations: {},
      actions: {},
    },
    modulesB: {
      state: {},
      getters: {},
      mutations: {},
      actions: {},
    },
  }
})
  1. 组件用例
<template>
  <!-- 子组件 -->
  <div class="child">
    <!-- 子组件如果改变VueX中的userName,此处也会立即变化 -->
    {{userName}}
  </div>
</template>

<script>
export default {
  computed: {
    userName() {
      // 通过this.$store.state.属性名获取
      return this.$store.state.userName;
    },
  },
  methods: {
    test() {
      // 通过this.$store.commit提交mutation来修改
      this.$store.commit("setUserName", "测试用户")
    }
  }
};
</script>
<template>
  <!-- 父组件 -->
  <div class="parent">
    <!-- 子组件如果改变VueX中的userName,此处也会立即变化 -->
    {{userName}}
  </div>
</template>

<script>
import child from "./child.vue";
export default {
  components: { child },
  computed: {
    userName() {
      // 通过this.$store.state.属性名获取
      return this.$store.state.userName;
    },
  },
};
</script>

三. 事件总线EventBus(组件之间通信)

EventBus与VueX类似,也可以理解为状态管理仓库,但EventBus并没有state、getters等概念,因此跟VueX相比更像是一个”杂乱“的状态管理仓库。EventBus更像是一个事件中心,组件A向事件中心发送消息,其他组件监听这个消息即可。

使用方法:

  1. 初始化EventBus

①局部事件总线:新创建一个 .js 文件,比如event-bus.js

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

实质上EventBus是一个不具备 DOM 的组件,它具有的仅仅只是它实例方法而已,因此它非常的轻便。

②全局事件总线:可以直接在项目中的 main.js 初始化EventBus

// main.js
Vue.prototype.$EventBus = new Vue()
  1. 使用用例
// 向EventBus发送消息
EventBus.$emit(事件名, 回调函数)

// 监听EventBus接收到的消息
EventBus.$on(事件名, 回调函数)

// 移除监听
EventBus.$off(事件名, 回调函数)
<!-- A.vue -->
<template>
    <button @click="sendMsg()">-</button>
</template>

<script> 
// 导入局部EventBus
import { EventBus } from "../event-bus.js";
export default {
  methods: {
    sendMsg() {
      // ==============1. 通过EventBus.$emit(事件名, 参数),向事件总线中抛出事件
      EventBus.$emit("aMsg", '来自A页面的消息');
    }
  }
}; 
</script>
<!-- B.vue -->
<template>
  <p>{{msg}}</p>
</template>

<script> 
import { EventBus } from "../event-bus.js";
export default {
  data(){
    return {
      msg: ''
    }
  },
  mounted() {
    // ==============2. 在mounted中创建EventBus的监听器,监听对应事件,并添加回调方法
    EventBus.$on("aMsg", (msg) => {
      // A发送来的消息
      this.msg = msg;
    });
  },
  beforeDestroy() {
    // ==============3. 关闭页面时,在beforeDestroy中使用EventBus.$off关闭监听器
    EventBus.$off("aMsg", (msg) => {
      // A发送来的消息
      this.msg = msg;
    });
  }
};
</script>

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

四. provide提供/inject注入(祖先与后代之间通信)

provide与inject通常需要组合使用,以允许一个祖先组件(provide)向其所有子孙后代(inject)注入一个依赖,不论组件层级有多深,后代组件总能拿到祖先所提供的属性。

①在祖先组件中使用provide提供相关属性。

②在后代元素中使用inject拿到祖先组件提供的属性,并注入到当前组件中,即可在当前组件中使用。

*注意:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的。

使用用例:

<template>
  <!-- 祖先组件 -->
  <div class="ancestors">
    <parent></parent>
  </div>
</template>

<script>
import parent from "./parent.vue";
export default {
  components: { parent },
  data() {
    return {
      userName: "test",
    };
  },
  // ==================1. 祖先组件使用provide提供对应属性,相当于export了
  provide() {
    return {
      userName: this.userName,
    };
  },
};
</script>
<template>
  <!-- 父组件 -->
  <div class="parent">
    <child></child>
  </div>
</template>

<script>
import child from "./child.vue";
export default {
  components: { child },
  // =====================2. 后代元素中使用inject注入该属性,相当于import
  inject: ["userName"],
};
</script>
<template>
  <!-- 子组件 -->
  <div class="child">
    {{userName}}
  </div>
</template>

<script>
export default {
  // ====================2. 后代元素中使用inject注入该属性,相当于import
  inject: ["userName"],
};
</script>

五. a t t r s / attrs/ attrs/listeners(父子通信)

使用场景:

①组件传值时使用: 爷爷在父亲组件传递值,父亲组件会通过 a t t r s 获 取 到 不 在 父 亲 p r o p s 里 面 的 所 有 属 性 , 父 亲 组 件 通 过 在 孙 子 组 件 上 绑 定 attrs获取到不在父亲props里面的所有属性,父亲组件通过在孙子组件上绑定 attrspropsattrs 和 $listeners 使孙组件获取爷爷传递的值并且可以调用在爷爷那里定义的方法;

②对一些UI库进行二次封装时使用:比如element-ui,里面的组件不能满足自己的使用场景的时候,会二次封装,但是又想保留他自己的属性和方法,那么这个时候时候 a t t r s 和 attrs和 attrslistners是个完美的解决方案。

  1. $attrs

一个祖先元素需要向后代传递多个元素时,我们通常采用这种写法:

<template>
  <!-- 祖先组件 -->
  <div class="ancestors">
    <!-- =====================1. 使用属性名="属性值"的方式逐个传递值 -->
    <parent name="test" age="20" sex="1"></parent>
  </div>
</template>

<script>
import parent from "./parent.vue";
export default {
  components: { parent },
};
</script>

而后代元素获取这些父元素传递过来的值时,一般情况下都需要使用props属性逐一获取,这种方式在传递属性较多且属性值复杂时,往往容易出错:

<template>
  <!-- 父组件 -->
  <div class="parent">
    {{name}}
    {{age}}
    {{sex}}
    <child></child>
  </div>
</template>

<script>
import child from "./child.vue";
export default {
  components: { child },
  // 2. =================使用props逐一获取对应属性
  props: ["name", "age", "sex"],
};
</script>

a t t r s 实 际 上 就 是 p r o p s 的 升 级 版 , 后 代 元 素 取 值 时 , 可 以 不 通 过 p r o p s 直 接 使 用 attrs实际上就是props的升级版,后代元素取值时,可以不通过props直接使用 attrspropsprops使attrs取到所有传递下来的属性:

<template>
  <!-- 父组件 -->
  <div class="parent">
    {{name}}
    <!-- =====================3. 直接使用$attrs取值 -->
    {{$attrs.age}}
    {{$attrs.sex}}
    <!-- =================4. 可以直接使用v-bind="$attrs"将参数继续向下传递 -->
    <child v-bind="$attrs" head="hhhh"></child>
    <!-- 相当于 <child  name="test" age="20" sex="1" head="hhhh"></child> -->
  </div>
</template>

<script>
import child from "./child.vue";
export default {
  components: { child },
  // 2. 使用props逐一获取对应属性,******注意:props优先级高于$attrs,一旦在props中取了该属性,$attrs便不再有该属性。
  props: ["name"],
};
</script>

*总结:①$attrs可以一次拿到所有属性(class、style、props已经声明的除外)。

​ ②个人理解: a t t r s 只 是 一 种 简 写 , 原 理 与 p r o p s 类 似 , 不 能 跨 层 级 传 递 , 需 要 一 层 一 层 使 用 v − b i n d = " attrs只是一种简写,原理与props类似,不能跨层级传递,需要一层一层使用v-bind=" attrsprops使vbind="attrs"依次传递。

​ ③如果除 a t t r s 外 还 有 其 他 额 外 参 数 需 要 传 递 , 额 外 参 数 与 attrs外还有其他额外参数需要传递,额外参数与 attrsattrs中的参数重复时,额外参数优先级更高。

  1. $listeners

a t t r s 类 似 , attrs类似, attrslisteners包含了所有父组件中的事件监听器(使用.native修饰的事件除外)。

父组件监听子组件事件时,通常采用这种写法:

<template>
  <!-- 祖先组件 -->
  <div class="ancestors">
    <!-- =====================1. 使用@事件名="方法名"的方式逐个监听 -->
    <parent @click.native="clickHandle" @test="testHandle"></parent>
  </div>
</template>

<script>
import parent from "./parent.vue";
export default {
  components: { parent },
};
</script>

而在子组件中,我们只需要$emit出对应事件名既可以触发父组件对应方法:

<template>
  <!-- 父组件 -->
  <div class="parent">
    <child></child>
  </div>
</template>

<script>
import child from "./child.vue";
export default {
  components: { child },
  created() {
    // ===================3. 这里使用this.$listeners就可以直接拿到祖先组件监听的所有事件
    console.log(this.$listeners)  
  },
  methods: {
    test() {
      // ==================2. 使用$emit抛出事件,触发父组件方法。
      this.$emit('test')
    }
  }
};
</script>

a t t r s 一 样 , 可 以 通 过 v − o n = " attrs一样,可以通过 v-on=" attrsvon="listeners" 将事件监听器继续向下传递,后代元素抛出对应事件后,同样可以触发祖先组件中的方法。

如果想要添加其他事件监听器,可继续绑定事件。但要注意的是,继续绑定的事件和 $listeners 中的事件有重复时,不会被覆盖。当 grandson.vue 触发 customEvent 时,child.vue 和 parent.vue 的事件都会被触发,触发顺序类似于冒泡,先到 child.vue 再到 parent.vue。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值