搜索
首页web前端Vue.jsVue组件间的通信方式详析

本篇文章给大家带来了关于vue的相关知识,主要介绍了Vue组件间通信方式,Vue组件间通信一直是个重要的话题,虽然官方推出的Vuex状态管理方案可以很好的解决组件之间的通信问题,但是在组件库内部使用Vuex往往会比较重,下面一起来看一下,希望对大家有帮助。

【相关推荐:javascript视频教程vue.js教程

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

组件之间通信的场景

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

  • 父子组件之间的通信
  • 兄弟组件之间的通信
  • 隔代组件之间的通信

父子组件之间的通信

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

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

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

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

父组件 ComponentA:

<template>
  <p>
    <component-b title="welcome"></component-b>
  </p>
</template>
<script>
import ComponentB from &#39;./ComponentB&#39;

export default {
  name: &#39;ComponentA&#39;,
  components: {
    ComponentB
  }
}
</script>

子组件 ComponentB:

<template>
  <p>
    <p>{{title}}</p>
  </p>
</template>
<script>
export default {
  name: &#39;ComponentB&#39;,
  props: {
    title: {
      type: String,
    }
  }
} 
</script>

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

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

子组件 ComponentA:

<template>
  <p>
    <component-b :title="title" @title-change="titleChange"></component-b>
  </p>
</template>
<script>
import ComponentB from &#39;./ComponentB&#39;

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

子组件 ComponentB:

<template>
  <p>
    <p @click="handleClick">{{title}}</p>
  </p>
</template>
<script>
export default {
  name: &#39;ComponentB&#39;,
  props: {
    title: {
      type: String,
    }
  },
  methods: {
    handleClick() {
      this.$emit(&#39;title-change&#39;, &#39;New title !&#39;)
    }  
  }
} 
</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>
  <p>
    <p>{{title}}</p>
    <p @click="changeTitle">click me</p>
  </p>
</template>
<script>
export default {
  name: &#39;ComponentA&#39;,
  props: {
    title: {
      type: String
    },
    changeTitle: Function
  }
}
</script>

ComponentB(兄弟组件B):

<template>
  <p>
    <p>{{title}}</p>
    <p @click="changeTitle">click me</p>
  </p>
</template>
<script>
export default {
  name: &#39;ComponentB&#39;,
  props: {
    title: {
      type: String
    },
    changeTitle: Function
  }
}
</script>

ComponentC(容器组件C):

<template>
  <p>
    <component-a :title="titleA" :change-title="titleAChange"></component-a>
    <component-b :title="titleB" :change-title="titleBChange"></component-b>
  </p>
</template>
<script>
import ComponentA from &#39;./ComponentA&#39;
import ComponentB from &#39;./ComponentB&#39;

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

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

隔代组件之间的通信

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

  • $attrs/$listeners
  • rovide/inject
  • 基于 $parent/$children 实现的 dispatch 和 broadcast

attrs/attrs/listeners

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

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

看个例子:

组件A(ComponentA):

<template>
  <component-a name="Lin" age="24" sex="male"></component-a>
</template>
<script>
import ComponentB from &#39;@/components/ComponentB.vue&#39;

export default {
  name: &#39;App&#39;,
  components: {
    ComponentA
  }
}
</script>

组件B(ComponetB):

<template>
  <p>
    I am component B
    <component-c v-bind="$attrs"></component-c>
  </p>
</template>
<script>
import ComponentC from &#39;@/components/ComponentC.vue&#39;

export default {
  name: &#39;ComponentB&#39;,
  inheritAttrs: false,
  components: {
    ComponentC
  }
}
</script>

组件C(ComponetC):

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

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

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

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

{age: &#39;24&#39;, sex: &#39;male&#39;}

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

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

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

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

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

image.png

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

image.png

再看下 $listeners 的定义:

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

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

继续改造 ComponentB 组件:

<template>
  <p>
    I am component B
    <component-c v-bind="$attrs" v-on="$listeners"></component-c>
  </p>
</template>
<script>
import ComponentC from &#39;@/components/ComponentC.vue&#39;

export default {
  name: &#39;ComponentB&#39;,
  inheritAttrs: false,
  components: {
    ComponentC
  }
}
</script>

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

provide/inject

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

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

先看下简单的用法:

父级组件:

export default {
  provide: {
    name: &#39;Lin&#39;
  }
}

子组件:

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

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

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

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

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

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

看一下第一种场景:

祖先组件组件(ComponentA):

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

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

子组件(ComponentB):

<template>
  <p>
    {{ title }}
    <button @click="fetchInfo">获取App信息</button>
  </p>
</template>
<script>
export default {
  name: &#39;ComponentB&#39;,
  inject: [&#39;app&#39;],
  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 方法来实现 provide 和 inject 绑定并可响应。

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

import Vue from &#39;vue&#39;

const state = Vue.observable({ title: &#39;&#39; });
export default {
  name: &#39;ComponentA&#39;,
  provide() {
    return {
      state
    }
  }
}

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

改造子组件(ComponentB):

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

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

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

基于 $parent/$children 实现的 dispatch 和 broadcast

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

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

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

组件ComponentA:

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

组件ComponentB:

export default {
  name: &#39;ComponentB&#39;,
  created () {
    this.$on(&#39;on-message&#39;, 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]))
        }
      })
    }
  }
}

【相关推荐:javascript视频教程vue.js教程

以上是Vue组件间的通信方式详析的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文转载于:脚本之家。如有侵权,请联系admin@php.cn删除
vue.js vs.后端框架:澄清区别vue.js vs.后端框架:澄清区别Apr 25, 2025 am 12:05 AM

Vue.js是前端框架,后端框架用于处理服务器端逻辑。1)Vue.js专注于构建用户界面,通过组件化和响应式数据绑定简化开发。2)后端框架如Express、Django处理HTTP请求、数据库操作和业务逻辑,运行在服务器上。

vue.js和前端堆栈:了解连接vue.js和前端堆栈:了解连接Apr 24, 2025 am 12:19 AM

Vue.js与前端技术栈紧密集成,提升开发效率和用户体验。1)构建工具:与Webpack、Rollup集成,实现模块化开发。2)状态管理:与Vuex集成,管理复杂应用状态。3)路由:与VueRouter集成,实现单页面应用路由。4)CSS预处理器:支持Sass、Less,提升样式开发效率。

Netflix:探索React(或其他框架)的使用Netflix:探索React(或其他框架)的使用Apr 23, 2025 am 12:02 AM

Netflix选择React来构建其用户界面,因为React的组件化设计和虚拟DOM机制能够高效处理复杂界面和频繁更新。1)组件化设计让Netflix将界面分解成可管理的小组件,提高了开发效率和代码可维护性。2)虚拟DOM机制通过最小化DOM操作,确保了Netflix用户界面的流畅性和高性能。

vue.js和前端:深入研究框架vue.js和前端:深入研究框架Apr 22, 2025 am 12:04 AM

Vue.js被开发者喜爱因为它易于上手且功能强大。1)其响应式数据绑定系统自动更新视图。2)组件系统提高了代码的可重用性和可维护性。3)计算属性和侦听器增强了代码的可读性和性能。4)使用VueDevtools和检查控制台错误是常见的调试技巧。5)性能优化包括使用key属性、计算属性和keep-alive组件。6)最佳实践包括清晰的组件命名、使用单文件组件和合理使用生命周期钩子。

vue.js在前端的力量:关键特征和好处vue.js在前端的力量:关键特征和好处Apr 21, 2025 am 12:07 AM

Vue.js是一个渐进式的JavaScript框架,适用于构建高效、可维护的前端应用。其关键特性包括:1.响应式数据绑定,2.组件化开发,3.虚拟DOM。通过这些特性,Vue.js简化了开发过程,提高了应用性能和可维护性,使其在现代Web开发中备受欢迎。

vue.js比反应好吗?vue.js比反应好吗?Apr 20, 2025 am 12:05 AM

Vue.js和React各有优劣,选择取决于项目需求和团队情况。1)Vue.js适合小型项目和初学者,因其简洁和易上手;2)React适用于大型项目和复杂UI,因其丰富的生态系统和组件化设计。

vue.js的功能:增强前端的用户体验vue.js的功能:增强前端的用户体验Apr 19, 2025 am 12:13 AM

Vue.js通过多种功能提升用户体验:1.响应式系统实现数据即时反馈;2.组件化开发提高代码复用性;3.VueRouter提供平滑导航;4.动态数据绑定和过渡动画增强交互效果;5.错误处理机制确保用户反馈;6.性能优化和最佳实践提升应用性能。

vue.js:定义其在网络开发中的作用vue.js:定义其在网络开发中的作用Apr 18, 2025 am 12:07 AM

Vue.js在Web开发中的角色是作为一个渐进式JavaScript框架,简化开发过程并提高效率。1)它通过响应式数据绑定和组件化开发,使开发者能专注于业务逻辑。2)Vue.js的工作原理依赖于响应式系统和虚拟DOM,优化性能。3)实际项目中,使用Vuex管理全局状态和优化数据响应性是常见实践。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

mPDF

mPDF

mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

ZendStudio 13.5.1 Mac

ZendStudio 13.5.1 Mac

功能强大的PHP集成开发环境