vue组件通信6种方式总结(常问知识点)

news2025/4/9 1:28:52

前言

在Vue组件库开发过程中,Vue组件之间的通信一直是一个重要的话题,虽然官方推出的 Vuex 状态管理方案可以很好的解决组件之间的通信问题,但是在组件库内部使用 Vuex 往往会比较重,本文将系统的罗列出几种不使用 Vuex,比较实用的组件间的通信方式,供大家参考。

组件之间通信的场景

在进入我们今天的主题之前,我们先来总结下Vue组件之间通信的几种场景,一般可以分为如下几种场景:

  1. 父子组件之间的通信
  2. 兄弟组件之间的通信
  3. 隔代组件之间的通信

父子组件之间的通信

父子组件之间的通信应该是 Vue 组件通信中最简单也最常见的一种了,概括为两个部分:父组件通过prop向子组件传递数据,子组件通过自定义事件向父组件传递数据。

父组件通过 prop 向子组件传递数据

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

额外的,每次父级组件发生变更时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。

父组件 ComponentA:

<template>
  <div>
    <component-b title="welcome"></component-b>
  </div>
</template>
<script>
import ComponentB from './ComponentB'

export default {  name: 'ComponentA',  components: {    ComponentB
  }}
</script>

子组件 ComponentB:

<template>
  <div>
    <div>{{title}}</div>
  </div>
</template>
<script>
export default {  name: 'ComponentB',  props: {    title: {      type: String,    }  }} 
</script>

子组件通过自定义事件向父组件传递数据

在子组件中可以通过 $emit 向父组件发生一个事件,在父组件中通过 v-on/@ 进行监听。

子组件 ComponentA:

<template>
  <div>
    <component-b :title="title" @title-change="titleChange"></component-b>
  </div>
</template>
<script>
import ComponentB from './ComponentB'

export default {  name: 'ComponentA',  components: {    ComponentB
  },  data: {    title: 'Click me'
  },  methods: {    titleChange(newTitle) {      this.title = newTitle    }   }}
</script>

子组件 ComponentB:

<template>
  <div>
    <div @click="handleClick">{{title}}</div>
  </div>
</template>
<script>
export default {  name: 'ComponentB',  props: {    title: {      type: String,    }  },  methods: {    handleClick() {      this.$emit('title-change', 'New title !')    }    }} 
</script>

这个例子非常简单,在子组件 ComponentB 里面通过 $emit 派发一个事件 title-change,在父组件 ComponentA 通过 @title-change 绑定的 titleChange 事件进行监听,ComponentB 向 ComponentA 传递的数据在 titleChange 函数的传参中可以获取到。参考 前端进阶面试题详细解答

兄弟组件之间的通信

状态提升

写过 React 的同学应该对组件的 状态提升 概念并不陌生,React 里面将组件按照职责的不同划分为两类:展示型组件(Presentational Component)容器型组件(Container Component)

展示型组件不关心组件使用的数据是如何获取的,以及组件数据应该如何修改,它只需要知道有了这些数据后,组件UI是什么样子的即可。外部组件通过 props 传递给展示型组件所需的数据和修改这些数据的回调函数,展示型组件只是它们的使用者。

容器型组件的职责是获取数据以及这些数据的处理逻辑,并把数据和逻辑通过 props 提供给子组件使用。

因此,参考 React 组件中的 状态提升 的概念,我们在两个兄弟组件之上提供一个父组件,相当于容器组件,负责处理数据,兄弟组件通过 props 接收参数以及回调函数,相当于展示组件,来解决兄弟组件之间的通信问题。

ComponentA(兄弟组件A):

<template>
  <div>
    <div>{{title}}</div>
    <div @click="changeTitle">click me</div>
  </div>
</template>
<script>
export default {  name: 'ComponentA',  props: {    title: {      type: String
    },    changeTitle: Function
  }}
</script>

ComponentB(兄弟组件B):

<template>
  <div>
    <div>{{title}}</div>
    <div @click="changeTitle">click me</div>
  </div>
</template>
<script>
export default {  name: 'ComponentB',  props: {    title: {      type: String
    },    changeTitle: Function
  }}
</script>

ComponentC(容器组件C):

<template>
  <div>
    <component-a :title="titleA" :change-title="titleAChange"></component-a>
    <component-b :title="titleB" :change-title="titleBChange"></component-b>
  </div>
</template>
<script>
import ComponentA from './ComponentA'
import ComponentB from './ComponentB'

export default {  name: 'ComponentC',  components: {    ComponentA,    ComponentB
  },  data: {    titleA: 'this is title A',    titleB: 'this is title B'
  },  methods: {    titleAChange() {      this.titleA = 'change title A'
    },    titleBChange() {      this.titleB = 'change title B'
    }  }}
</script>

可以看到,上述这种 “状态提升” 的方式是比较繁琐的,特别是兄弟组件的通信还要借助于父组件,组件复杂之后处理起来是相当麻烦的。

隔代组件之间的通信

隔代组件之间的通信可以通过如下几种方式实现:

  • $attrs/$listeners
  • rovide/inject
  • 基于 $parent/$children 实现的 dispatchbroadcast

attrs/listeners

Vue 2.4.0 版本新增了 $attrs$listeners 两个方法。先看下官方对 $attrs 的介绍:

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

看个例子:

组件A(ComponentA):

<template>
  <component-b name="Lin" age="24" sex="male"></component-b>
</template>
<script>
import ComponentB from '@/components/ComponentB.vue'

export default {  name: 'ComponentA',  components: {    ComponentB
  }}
</script>

组件B(ComponetB):

<template>
  <div>
    I am component B
    <component-c v-bind="$attrs"></component-c>
  </div>
</template>
<script>
import ComponentC from '@/components/ComponentC.vue'

export default {  name: 'ComponentB',  inheritAttrs: false,  components: {    ComponentC
  }}
</script>

组件C(ComponetC):

<template>
  <div>
    I am component C
  </div>
</template>
<script>

export default {  name: 'ComponentC',  props: {    name: {      type: String
    }  },  mounted: function() {    console.log('$attrs', this.$attrs)  }}
</script>

这里有三个组件,祖先组件(ComponentA)、父组件(ComponentB)和子组件(ComponentC)。这三个组件构成了一个典型的子孙组件之间的关系。

ComponetA 给 ComponetB 传递了三个属性 name、age 和 sex,ComponentB 通过 v-bind="$attrs" 将这三个属性再透传给 ComponentC, 最后在 ComponentC 中打印 $attrs 的值为:

{age: '24', sex: 'male'}

为什么我们一开始传递了三个属性,最后只打印了两个属性 age 和 sex 呢?因为在 ComponentC 的props 中声明了 name 属性,$attrs 会自动排除掉在 props 中声明的属性,并将其他属性以对象的形式输出。

说白了就是一句话,$attrs 可以获取父组件中绑定的非 Props 属性

一般在使用的时候会同时和 inheritAttrs 属性配合使用。

如果你不希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false

在 ComponentB 添加了 inheritAttrs=false 属性后,ComponentB 的dom结构中可以看到是不会继承父组件传递过来的属性:

在这里插入图片描述

如果不加上 inheritAttrs=false 属性,就会自动继承父组件传递过来的属性:

在这里插入图片描述

再看下 $listeners 的定义:

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

$listeners也能把父组件中对子组件的事件监听全部拿到,这样我们就能用一个v-on把这些来自于父组件的事件监听传递到下一级组件。

继续改造 ComponentB 组件:

<template>
  <div>
    I am component B
    <component-c v-bind="$attrs" v-on="$listeners"></component-c>
  </div>
</template>
<script>
import ComponentC from '@/components/ComponentC.vue'

export default {  name: 'ComponentB',  inheritAttrs: false,  components: {    ComponentC
  }}
</script>

这里利用 $attrs$listeners 方法,可以将祖先组件(ComponentA) 中的属性和事件透传给孙组件(ComponentC),这样就可以实现隔代组件之间的通信。

provide/inject

provide/inject 是 Vue 2.2.0 版本后新增的方法。

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

先看下简单的用法:

父级组件:

export default {
  provide: {
    name: 'Lin'
  }
}

子组件:

export default {
  inject: ['name'],
  mounted () {
    console.log(this.name);  // Lin
  }
}

上面的例子可以看到,父组件通过 privide 返回的对象里面的值,在子组件中通过 inject 注入之后可以直接访问到。

但是需要注意的是,provideinject 绑定并不是可响应的,按照官方的说法,这是刻意为之的

也就是说父组件 provide 里面的name属性值变化了,子组件中 this.name 获取到的值不变。

如果想让 provide 和 inject 变成可响应的,有以下两种方式:

  • provide 祖先组件的实例,然后在子孙组件中注入依赖,这样就可以在子孙组件中直接修改祖先组件的实例的属性,不过这种方法有个缺点就是这个实例上挂载很多没有必要的东西比如props,methods
  • 使用 Vue 2.6 提供的 Vue.observable 方法优化响应式 provide

看一下第一种场景:

祖先组件组件(ComponentA):

export default {
  name: 'ComponentA',
  provide() {
    return {
      app: this
    }
  },
  data() {
    return {
       appInfo: {
         title: ''
       }
    }
  },
  methods: {
    fetchAppInfo() {
      this.appInfo = { title: 'Welcome to Vue world'}
    }
  }
}

我们把整个 ComponentA.vue 的实例 this 对外提供,命名为 app。接下来,任何组件只要通过 inject 注入 app 的话,都可以直接通过 this.app.xxx 来访问 ComponentA.vue 的 datacomputedmethods 等内容。

子组件(ComponentB):

<template>
  <div>
    {{ title }}
    <button @click="fetchInfo">获取App信息</button>
  </div>
</template>
<script>
export default {  name: 'ComponentB',  inject: ['app'],  computed: {    title() {      return this.app.appInfo.title
    }  },  methods: {    fetchInfo() {      this.app.fetchAppInfo()    }   }}
</script>

这样,任何子组件,只要通过 inject 注入 app 后,就可以直接访问祖先组件中的数据了,同时也可以调用祖先组件提供的方法修改祖先组件的数据并反应到子组件上。

当点击子组件(ComponentB)的获取App信息按钮,会调用 this.app.fetchAppInfo 方法,也就是访问祖先组件(ComponentA)实例上的 fetchAppInfo 方法,fetchAppInfo 会修改fetchAppInfo的值。同时子组件(ComponentB)中会监听 this.app.appInfo 的变化,并将变化后的title值显示在组件上。

再看一下第二种场景,通过 Vue.observable 方法来实现 provideinject 绑定并可响应。

基于上面的示例,改造祖先组件(ComponentA):

import Vue from 'vue'

const state = Vue.observable({ title: '' });

export default {
  name: 'ComponentA',
  provide() {
    return {
      state
    }
  }
}

使用 Vue.observable 定义一个可响应的对象 state,并在 provide 中返回这个对象。

改造子组件(ComponentB):

<template>
  <div>
    {{ title }}
    <button @click="fetchInfo">获取App信息</button>
  </div>
</template>
<script>
export default {  name: 'ComponentInject',  inject: ['state'],  computed: {    title() {      return this.state.title
    }  },  methods: {    fetchInfo() {      this.state.title = 'Welcome to Vue world22'
    }   }}
</script>

与之前的例子不同的是,这里我们直接修改了 this.state.title 的值,因为 state 被定义成了一个可响应的数据,所以 state.title 的值被修改后,视图上的 title 也会立即响应并更新,从这里看,其实很像 Vuex 的处理方式。

以上两种方式对比可以发现,第二种借助于 Vue.observable 方法实现 provideinject 的可响应更加简单高效,推荐大家使用这种方式。

基于 $parent/$children 实现的 dispatchbroadcast

先了解下 dispatch 和 broadcast 两个概念。

  • dispatch: 派发,指的是从一个组件内部向上传递一个事件,并在组件内部通过 $on 进行监听

  • broadcast: 广播,指的是从一个组件内部向下传递一个事件,并在组件内部通过 $on 进行监听

在实现 dispatch 和 broadcast 方法之前,先来看一下具体的使用方法。有 ComponentA.vueComponentB.vue 两个组件,其中 ComponentB 是 ComponentA 的子组件,中间可能跨多级,在 ComponentA 中向 ComponentB 通信:

组件ComponentA:

<template>
  <button @click="handleClick">派发事件</button>
</template>
<script>
import Emitter from '../mixins/emitter.js';
export default {  name: 'ComponentA',  mixins: [Emitter],  methods: {    handleClick () {      this.dispatch('ComponentB', 'on-message', 'Hello Vue.js')    }  }}
</script>

组件ComponentB:

export default {
  name: 'ComponentB',
  created () {
    this.$on('on-message', this.showMessage)
  },
  methods: {
    showMessage (text) {
      console.log(text)
    }
  }
}

dispatch 的逻辑写在 emitter.js 中,使用的时候通过 mixins 混入到组件中,这样可以很好的将事件通信逻辑和组件进行解耦。

dispatch 的方法有三个传参,分别是:需要接受事件的组件的名字(全局唯一,用来精确查找组件)、事件名和事件传递的参数。

dispatch 的实现思路非常简单,通过 $parent 获取当前父组件对象,如果组件的name和接受事件的name一致(dispatch方法的第一个参数),在父组件上调用 $emit 发射一个事件,这样就会触发目标组件上 $on 定义的回调函数,如果当前组件的name和接受事件的name不一致,就递归地向上调用此逻辑。

dispath:

export default {
  methods: {
    dispatch(componentName, eventName, params) {
      let parent = this.$parent || this.$root;
      let name = parent.$options.name;
      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;
        if (parent) {
          name = parent.$options.name
        }
      }
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    }
  }
}

broadcast逻辑和dispatch的逻辑差不多,只是一个是通过 $parent 向上查找,一个是通过 $children 向下查找,

export default {
  methods: {
    broadcast(componentName, eventName, params) {
      this.$children.forEach(child => {
        const name = child.$options.name
        if (name === componentName) {
          child.$emit.apply(child, [eventName].concat(params))
        } else {
          broadcast.apply(child, [componentName, eventName].concat([params]))
        }
      })
    }
  }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/135964.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

X.509证书详解

概述 X.509是公钥基础设施&#xff08;PKI&#xff09;的标准格式。X.509证书就是基于国际电信联盟&#xff08;ITU&#xff09;制定的X.509标准的数字证书。X.509证书主要用于识别互联网通信和计算机网络中的身份&#xff0c;保护数据传输安全。X.509证书无处不在&#xff0c…

Spring之底层架构核心概念-过滤器

文章目录1.excludeFilters:排除过滤器2.includeFilters:包含过滤器3.问题&#xff1a;spring为什么能通过是否有Component注解来判断是否需要去注册bean呢&#xff1f;3.1 看源码3.2 原因4.总结1.excludeFilters:排除过滤器 用excludeFilters排除UserService 这个类后&#xf…

nature级别图表:单细胞转录组细胞比例统计可视化函数

单细胞转录组细胞比例&#xff1a; 关于单细胞比例的计算和作图我们之前出过3期&#xff0c;单细胞比例的展示是很多单细胞文章必不可少的内容&#xff1a;跟着Cell学单细胞转录组分析(六):细胞比例计算及可视化 相信跟着学习的小伙伴已经掌握了。最近学习一篇《nature medici…

JavaScript 数组-概念,创建数组,遍历数组,新增元素

JavaScript 数组-概念&#xff0c;创建数组&#xff0c;遍历数组&#xff0c;新增元素 目录JavaScript 数组-概念&#xff0c;创建数组&#xff0c;遍历数组&#xff0c;新增元素1. 数组的概念2. 创建数组2.1 数组的创建方式2.2 利用 new 创建数组2.3 利用数组字面量创建数组2.…

模式分类识别 | BiLSTM双向长短期记忆神经网络数据多特征分类预测(Matlab完整程序)

模式分类识别 | BiLSTM双向长短期记忆神经网络数据多特征分类预测(Matlab完整程序) 目录 模式分类识别 | BiLSTM双向长短期记忆神经网络数据多特征分类预测(Matlab完整程序)分类结果基本介绍程序设计参考资料分类结果

深度学习实验(五)——循环神经网络编程

深度学习实验五:循环神经网络编程 本次实验练习使用torch.nn中的类设计一个循环神经网络进行MNIST图像分类。 在本次实验中&#xff0c;你要设计一个CNN&#xff0c;用于将282828 \times 282828的MNIST图像转换为MMDM\times M\times DMMD的特征图&#xff0c;将该特征图看作是…

我,做了两年程序员,存了巨款5000,你们拿什么跟我比?

&#x1f550;没错&#xff0c;标题所见&#xff0c;从21年1月份开始从事程序员工作也过了两年了&#xff0c;今年还是没有存到钱。 今年换了一份工作&#xff0c;四月份的时候&#xff0c;从惠州换到了广州工作&#xff0c;从制造业转行到了政务行业&#xff0c;工资涨了&…

Keithley 2604B数字源表-安泰测试

Keithley 2600B 系列系统 SMU 仪器是业界标准电流-电压源和测量解决方案&#xff0c;适用于高度自动化生产测试应用。 双通道和单通道型号都紧密集成一个精密电源、真正电流源、数字万用表和具有脉冲生成功能的电子负载。 另外&#xff0c;TSP? 技术可运行完整测试程序&#x…

多点双向重发布的应用

目录 1.拓扑图 2.实验思路 3.主要配置 4.测试 5.实验总结 1.拓扑图 2.实验思路 在配置完RIP和OSPF之后&#xff0c;在2&#xff0c;3号设备上进行多点的双向重发布由于在进行了多点的双向重发布之后&#xff0c;会出现大量的负载均衡&#xff0c;导致选路不佳的问题解决办…

前端工程师leetcode算法面试必备-二叉树深度广度遍历

一、前言 Medium 难度主要考察结合二叉树性质的 CRUD 操作&#xff0c;而这一切的基础都离不开遍历二叉树。 二叉树是图的子集&#xff0c;因而同样适用以下两种搜索思想&#xff1a; **DFS&#xff08;深度优先搜索&#xff09;&#xff1a;**沿着根节点递归下去&#xff0c…

普通程序员怎么赚多份钱?解锁更多赚钱新姿势

在当下这个社会&#xff0c;学会如何make money很重要。 咱们是个俗人&#xff0c;赚钱才是社会生存的头等大事。这不是高山流水的世界&#xff0c;而是能力创造财富&#xff0c;对于程序员来说&#xff0c;更是如此。 作为程序员&#xff0c;我们有更多挣钱的姿势&#xff01;…

通过一个测试项目了解EF CORE

首先用vs2019创建一个.net core项目 可以是控制台应用程序,也可以是asp.net core项目 如果你使用控制台应用程序, 则可以在Main方法中直接使用EF Core进行CRUD操作。这通常用于测试或开发时快速进行数据库操作。 如果你使用ASP.NET Core应用程序, 则可以在控制器中使用EF Cor…

低温超导系统中实现液氦温度准确控制的解决方案

摘要&#xff1a;针对目前两种典型低温超导测试系统中存在的液氦压力控制精度较差的问题&#xff0c;本文提出了相应的解决方案。解决方案分别采用了直接压力控制和流量控制两种技术手段和配套数控阀门&#xff0c;结合24位AD和16位DA的超高精度的PID真空压力控制器和压力传感器…

第三十九章 贪心算法——区间问题(下)

第三十九章 贪心算法——区间问题&#xff08;下&#xff09;一、区间问题1&#xff1a;最大不相交区间数量1、思路详解2、代码实现二、区间问题2&#xff1a;区间覆盖1、问题2、思路3、代码一、区间问题1&#xff1a;最大不相交区间数量 1、思路详解 这道题和前一章讲的最后一…

IDEA 使用的小技巧

1、调整 idea 的虚拟内存&#xff1a; 尽管本质都是去改变 .vmoptions 配置文件&#xff0c;但推荐使用Change Memory Settings去调整&#xff0c;选择Edit Custom VM Options 或者在本地磁盘目录更改&#xff0c;通过某些方法破解的 idea 很可能造成 idea 打不开的情况 2、显…

【数据结构-源码分析】HashMap源码分析(超级详细)

文章内容1、HashMap简介2、类结构3、属性4、构造方法5、方法5.1、put方法5.2、resize方法6、jdk1.8的优化1、HashMap简介 HashMap基于哈希表的Map接口实现&#xff0c;是以key-value存储形式存在。&#xff08;除了不同步和允许使用 null 之外&#xff0c;HashMap 类与 Hashta…

Oracle数据库同步复制工具Beedup产品功能(二)

接续...... 8、对象比较 Beedup提供主从库相关对象比较功能&#xff0c;比对结果包含各类对象概要统计及差异详情。 支持Oracle、SQL Server、MySQL、DB2 对象比较。 9、 对象恢复 Beedup在向从库写入数据时会禁用目标表的关联触发器&#xff0c;另外对于Oracle 序列的状态…

MySQL复制技术方案——GTID复制配置

在日常运维中&#xff0c;GTID带来的最方便的作用就是搭建和维护主从复制&#xff0c;这也是DBA日常工作中最经常的操作了。GTID的主从模式替代了MySQL前期版本中利用二进制日志文件的名称和日志位置的做法&#xff0c;使用GTID使操作和维护都变得更加简洁和可靠。 搭建主从时…

SQL SELECT 语句

SELECT 语句用于从数据库中选取数据。 SQL SELECT 语句 SELECT 语句用于从数据库中选取数据。 结果被存储在一个结果表中&#xff0c;称为结果集。 SQL SELECT 语法 SELECT column1, column2, ... FROM table_name; 与 SELECT * FROM table_name; 参数说明&#xff1a; …

SVM训练莺尾花数据集

SVM训练莺尾花数据集 代码在莺尾花数据集上训练SVM&#xff0c;数据集由莺尾花的测量值及其相应的物种标签组成。该模型使用70%数据用于训练&#xff0c;然后剩余部分进行测试。其中′fit′fit′fit′方法在训练集上训练数据&#xff0c;′score′score′score′数据在返回模型…