Vue2实现组件通讯的常用方式

 更新时间:2026年04月15日 09:07:18   作者:霍理迪  
在 Vue 项目开发中,组件通讯是基础也是核心,不同的组件关系(父子、祖先后代、任意组件)有着不同的最优解方案,本文介绍了Vue项目中组件通讯的不同方式,包括父子组件的props、v-model和自定义事件,需要的朋友可以参考下

前言:

在 Vue 项目开发中,组件通讯是基础也是核心。不同的组件关系(父子、祖先后代、任意组件)有着不同的最优解方案。

一、父子通讯

谁在模板中引入了另一个组件,谁就是父组件,被引用的就是子组件。父子组件的通讯是最为频繁的场景。

1、props 配置项

props主要用于让子组件接收外部(父组件)传过来的数据。它可以实现“父传子”,也可以通过传递函数实现“子传父”

代码实现:

父传子:父组件在子组件标签上通过v-bind即:绑定动态数据。

子传父:父组件先定义一个带形参的方法,并将该方法通过属性传给子组件;子组件在合适的时机调用该方法,将自身数据作为实参传入

// 父组件 App.vue
<template>
  <div>
    <!-- 传递属性 money 和 方法 getSonMsg -->
    <Girl :money="money" :getSonMsg="getSonMsg"/>
  </div>
</template>
<script>
import Girl from '@/components/Girl';
export default {
  components: { Girl },
  data() {
    return { money: { num: 1000 } }
  },
  methods: {
    getSonMsg(msg) {
      console.log('接收到了girl组件传递的数据', msg); // 父组件在这里接收到了子组件的数据
    }
  }
}
</script>
// 子组件 Girl.vue
<script>
export default {
  // 推荐使用对象方式接收,可限制类型、必要性及默认值
  props: {
    money: { type: Object, required: true },
    getSonMsg: { type: Function }
  },
  data() { return { girlInfo: '我是girl组件' } },
  methods: {
    sendInfo() {
      // 触发父组件传来的方法,并传入子组件的数据实现子传父
      this.getSonMsg(this.girlInfo);
    }
  }
}
</script>

注意事项: props 是只读的,Vue 底层会监视你对 props 的修改,如果强行修改会发出警告。若业务需要,请将 props 的内容复制一份到子组件的 data 中再进行修改。另外,对于传递的复杂数据类型(如对象),Vue 无法监测到内部属性内容的改变,除非修改了地址值。

2、组件自定义事件

适用于子组件向父组件传递数据。核心逻辑是:谁绑定,谁触发。父组件给子组件绑定自定义事件,子组件内部触发该事件

代码实现与两种绑定方式:

方式一:标签上直接绑定 父组件使用 @事件名="回调函数" 绑定

// 父组件
<HelloWorld @event2="handleEvent"></HelloWorld>

// 子组件 HelloWorld.vue
methods: {
  doSome() {
    // 触发 event2 事件,并可以传递多个参数
    this.$emit('event2', this.msg, 666, 8888); 
  }
}

方式二:代码动态绑定 (使用 ref) 如果想要更灵活的绑定时机,可以使用 ref。注意:不能在 created 钩子中绑定(此时 DOM 未渲染),通常在 mounted 中绑定

// 父组件
<template>
  <HelloWorld ref="hello" />
</template>
<script>
export default {
  mounted() {
    // 使用箭头函数,确保 this 指向父组件实例
    this.$refs.hello.$on('event2', (msg) => {
      console.log('收到数据', msg, this); 
    });
  }
}
</script>

注意事项:

this 指向:如果使用代码绑定(方式二),回调函数一定要写成箭头函数或者配置在父组件的 methods 中。如果写成普通匿名函数 function() {},此时的 this 会指向触发事件的子组件(HelloWorld),而不是父组件。

原生事件与解绑:如果要在组件标签上绑定原生的 DOM 事件(如 click),必须加上 .native 修饰符,否则 Vue 会将其视为自定义事件。组件销毁前,建议使用 this.\$off('事件名') 或 this.\$off() 解绑事件,防止内存泄漏

3、ref /  \$parent /  \$children

ref:使用在普通 HTML 标签上获取的是 DOM 元素;使用在组件标签上获取的是该子组件实例,可以借此直接调用子组件的数据和方法,如this.\$refs.hello.name

 \$parent /  \$childre

子组件通过this.parent.属性即可访问父组件;父组件也可以通过this.\$children访问子组件实例。

这三者都会增加组件的强耦合,通常仅作为一种捷径通信。

二、任意组件通讯

1、 全局事件总线 ($bus)

全局事件总线适用于任何关系的组件通信(尤其是兄弟组件)。它的原理是给项目中所有的组件找一个共享的对象(必须具备 \$on、\$emit、\$off 方法),所有组件都通过它来收发数据。

配置总线的两种底层方式(明确 vc 与 vm): 要创建一个所有组件都能访问的对象,我们可以利用原型链。Vue 的组件实例(vc - VueComponent)和 Vue 实例(vm - Vue)都继承自 Vue 原型。

方式一:创建一个共享的 vc 对象

Vue 底层解析组件标签时会 new VueComponent(),我们可以手动创建一个实例来作为总线。

// 在 main.js 中
// 1. 获取 VueComponent 构造函数
const VueComponentConstructor = Vue.extend({});
// 2. 实例化一个 vc 对象
const globelvc = new VueComponentConstructor();
// 3. 将其挂载到 Vue 原型上
Vue.prototype.$bus = globelvc; 

方式二:直接使用 vm 对象(推荐写法)

在 main.js 创建 Vue 根实例时,在 beforeCreate 钩子中将当前的 this(也就是 vm 实例自身)挂载到原型上。

// 在 main.js 中
new Vue({
  render: (h) => h(App),
  beforeCreate() {
    // 安装全局事件总线,$bus 就是当前应用的 vm
    Vue.prototype.$bus = this; 
  }
}).$mount("#app");

使用规范: 永远记住:A 组件向 B 组件传数据,应该在 B 组件中绑定事件(接),在 A 组件中触发事件(传)

接收方 (B 组件):mounted() { this.\$bus.\$on('receiveMsg', (msg) => { ... }) }

发送方 (A 组件)this.\$bus.\$emit('receiveMsg', 数据)

注意:养成好习惯,接收方组件被销毁前,必须在 beforeDestroy 中调用 this.\$bus.\$off('receiveMsg') 解绑事件

2、消息订阅与发布 (pubsub-js)

借助第三方库实现,适用于任意前端框架。安装 npm i pubsub-js 后,引入 pubsub 对象。接收方使用 pubsub.subscribe('消息名', 回调) 订阅,发送方使用 pubsub.publish('消息名', 数据) 发布

三、Vuex 集中式状态管理

当多个组件依赖于同一状态,或来自不同组件的行为需要变更同一状态时,就必须使用 Vuex。 全局事件总线的关注点在于“数据传来传去”,并没有让数据真正共享;而 Vuex 的关注点在于“数据本身就在 Vuex 上共享”,任何组件修改它,其他组件都会同步更新

1. Vuex 的核心五大属性

Vuex 包含五个核心属性:State、Getters、Mutations、Actions、Modules。

State:存储 Vuex 的基本数据(变量)。

Getters:基于 State 的派生数据,逻辑复杂且复用性高时使用,相当于 State 的计算属性。

Mutations:提交更新数据的唯一途径且必须是同步的。包含事件类型和回调函数,回调接收 state 和传入的值作为参数,使用 this.\$store.commit() 触发

Actions:处理复杂的业务逻辑或包含任意异步操作(如发送 ajax 请求)。Action 拿到结果后提交给 Mutation 进行状态变更,而不是直接修改 State。使用 this.\$store.dispatch() 触发

Modules:如果将所有状态集中在一棵树上会非常臃肿。Modules 允许将 Store 分割成包含自己 State、Mutations、Actions 的多个模块,方便管理。

实现:我们将这四个属性集中在 Vuex 的核心对象配置中(通常在 store/index.js 或类似文件中)

import Vue from "vue";
import Vuex from "vuex";
import axios from "axios"; // 引入网络请求库

Vue.use(Vuex);

// 1. State:定义全局共享的数据
const state = {
  username: "zhangsan", // 基础字符串状态
  studentName: [
    { id: "001", name: "孙悟空" },
    { id: "002", name: "猪八戒" }
  ],
  userList: [] // 准备存放异步请求来的列表数据
};

// 2. Getters:基于 State 加工的计算属性
const getters = {
  // 每一个 getter 方法会自动接收 state 作为第一个参数
  // 需求:将 username 反转展示
  reverseName(state) {
    return state.username.split("").reverse().join("");
  }
};

// 3. Mutations:负责同步更新 State 的方法
const mutations = {
  // 接收 state 作为第一个参数,value(提交的载荷)作为第二个参数
  SAVE_STUDENT(state, value) {
    // 只能在此进行同步操作直接修改 state
    state.studentName.unshift({ id: Date.now(), name: value });
  },
  
  // 用于接收异步操作拿回来的数据并赋值给 state
  GET_USER_INFO(state, value) {
    state.userList = value;
  }
};

// 4. Actions:处理异步操作和复杂逻辑
const actions = {
  // context 相当于一个迷你的 store 对象,包含 commit、dispatch 等方法
  
  // 示例 A:简单的封装,将外部传来的值交给 Mutation
  saveStudent(context, value) {
    // 可以在这里做一些逻辑判断,判断通过后再提交
    context.commit("SAVE_STUDENT", value);
  },

  // 示例 B:真实的异步 Ajax 请求
  async getUserInfo(context) {
    try {
      // 模拟发送异步网络请求获取数据
      let res = await axios.get("api/user"); 
      // 拿到数据后,提交给 Mutations 去更新 State
      context.commit("GET_USER_INFO", res.data); 
    } catch (error) {
      console.log(error.message);
    }
  }
};

// 创建并暴露 store 对象
export default new Vuex.Store({
  state,
  getters,
  mutations,
  actions,
});

2. Vuex 模块化开发与 map 辅助函数

在大型项目中,模块化开发是必选项。开启命名空间 namespaced: true 可以让各个模块的状态内聚。 同时,使用 mapState, mapGetters, mapActions, mapMutations 能够大幅简化组件内部调用 store 的代码。

模块化与辅助函数代码实践:

// store/student.js (学生模块)
export default {
  namespaced: true, // 开启命名空间
  state: { studentNum: 5, studentName: "张三" },
  getters: {
    reverseStudentName(state) { return state.studentName.split("").reverse().join(""); }
  },
  mutations: {
    STU_NUM_FUN(state, value) { state.studentNum += value; }
  },
  actions: {
    stuNumFun(context, value) { context.commit("STU_NUM_FUN", value); }
  }
};

// store/index.js (主文件)
import Vue from "vue";
import Vuex from "vuex";
import student from "./student";
Vue.use(Vuex);
export default new Vuex.Store({
  modules: { student } // 注册模块
});

在组件中使用辅助函数映射:

// 组件内
import { mapState, mapGetters, mapActions } from 'vuex';

export default {
  computed: {
    // 数组形式或对象形式映射模块内的 state 和 getters
    ...mapState('student', ['studentNum', 'studentName']),
    ...mapGetters('student', ['reverseStudentName'])
  },
  methods: {
    // 映射 actions,模板中调用时直接传参 @click="stuNumFun(2)"
    ...mapActions('student', ['stuNumFun'])
  }
}

3. Action 中的异步请求

如果 ajax 请求来的数据需要被多个组件共享复用,应该将请求逻辑封装在 Vuex 的 Action 里,并包装成 Promise。

// test.js 模块
import axios from "axios";
export default {
  namespaced: true,
  state: { list: [] },
  mutations: { GET_USER_INFO(state, value) { state.list = value; } },
  actions: {
    async getUserInfo(context) {
      try {
        let res = await axios.get("api/user");
        context.commit("GET_USER_INFO", res.data); // 请求成功后提交 mutation
      } catch (error) { console.log(error.message); }
    }
  }
}

4. Vuex 数据刷新丢失问题与持久化

痛点:F5 刷新页面时,Vue 实例会被销毁重建,Vuex 写在内存中的 state 数据会重新初始化,导致登录 token 等数据丢失。 解决方案:引入 vuex-persistedstate 插件进行数据持久化,它会自动将数据同步到 localStorage 中。

// 安装: npm install vuex-persistedstate
// store/index.js
import createPersistedState from 'vuex-persistedstate'

export const store = new Vuex.Store({
  modules: { student },
  plugins: [
    createPersistedState({
      // 可选配置:指定需要持久化的模块数据
      reducer(val) {
        return { student: val.student } 
      }
    })
  ]
})

四、其他通讯方式

1. \$attrs 与 \$listeners (爷孙透传)

用于祖先组件向深层后代组件传递数据,解决中间组件仅起到桥梁作用的问题。

\$attrs 包含了祖先传递但在当前组件 props 中未声明的属性。在中间组件设置 inheritAttrs: false 阻止属性自动绑定到根元素,然后通过 v-bind="$attrs" 将其向下透传。

\$listeners 包含祖先传递的所有事件监听器,通过 v-on="$listeners" 向下传递,孙组件即可直接触发爷爷组件的事件

2. Provide 与 Inject (依赖注入)

同样用于祖先向后代传递。祖先组件通过 provide() 返回提供的数据,后代通过 inject: ['属性名'] 接收。 注意:这主要用于单向传递。如果祖先传递的是基础类型,后代修改它会报错;如果是复杂引用类型,修改虽不报错但会失去响应式,破坏单向数据流结构,增加代码维护难度。

3. v-model 通信

在自定义组件上,v-model 本质上是 :value 和 @input 事件的语法糖,用于实现极其方便的父子双向绑定。父组件直接 <Child v-model="data"/>,子组件接收 props: ['value'],数据变化时

this.$ emit('input', 新值) 即可实现同步修改源数据。

4. 插槽 Slots (结构传递)

插槽让父组件可以向子组件指定位置插入完整的 HTML 结构。

默认/具名插槽:父组件向子组件分发结构。

作用域插槽(子传父的变体):数据在子组件自身,但根据数据生成的 HTML 结构由父组件决定。子组件通过 <slot :自定义名="数据"> 传出数据,父组件用 <template scope="接收名"> 拿到数据并构建 UI。

总览

组件关系传递方式
父传子props,v-model,默认插槽,具名插槽, \$parent
子传父props,自定义事件,v-model, \$refs, \$children,作用域插槽
祖先与后代 \$attrs / \$listeners ,provide / inject
任意组件通讯 \$bus,pubsub,vuex

以上就是Vue2实现组件通讯的常用方式的详细内容,更多关于Vue2组件通讯方式的资料请关注脚本之家其它相关文章!

相关文章

  • ant-design-vue导航菜单a-menu的使用解读

    ant-design-vue导航菜单a-menu的使用解读

    这篇文章主要介绍了ant-design-vue导航菜单a-menu的使用解读,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-10-10
  • 详解vue如何使用rules对表单字段进行校验

    详解vue如何使用rules对表单字段进行校验

    这篇文章主要介绍了详解vue如何使用rules对表单字段进行校验,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-10-10
  • Vue3插槽(slot)使用方法详解

    Vue3插槽(slot)使用方法详解

    在VUE开发项目的过程中,插槽<slot>是重要的承载分发内容的出口,下面这篇文章主要给大家介绍了关于Vue3插槽(slot)使用的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-11-11
  • vue长按事件touch示例详解

    vue长按事件touch示例详解

    这篇文章主要介绍了vue长按事件touch,文末给大家补充介绍了Vue长按触摸事件的实现代码,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-07-07
  • VUE路由动态加载实例代码讲解

    VUE路由动态加载实例代码讲解

    在本文里小编给大家整理了关于VUE路由动态加载实例代码以及相关知识点,需要的朋友们学习下。
    2019-08-08
  • Vue路由与a标签链接锚点发生冲突问题及解决

    Vue路由与a标签链接锚点发生冲突问题及解决

    这篇文章主要介绍了Vue路由与a标签链接锚点发生冲突问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-03-03
  • Vue实现可拖拽组件的方法

    Vue实现可拖拽组件的方法

    这篇文章主要为大家详细介绍了Vue实现可拖拽组件的方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • Vue3中watchEffect的用途浅析

    Vue3中watchEffect的用途浅析

    这篇文章主要给大家介绍了关于Vue3中watchEffect用途的相关资料, watch和watchEffect都是监听器,但在写法和使用上有所区别,本文进行了详细的介绍,需要的朋友可以参考下
    2021-07-07
  • Vue项目启动提示Cannot GET /问题

    Vue项目启动提示Cannot GET /问题

    这篇文章主要介绍了Vue项目启动提示Cannot GET /问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • 在vue中:style 的使用方式汇总

    在vue中:style 的使用方式汇总

    在Vue开发中使用:style绑定样式是常见需求,应注意:class与:style的配合使用,错误的使用可能导致样式不生效,正确的方法是使用数组绑定多个样式,这些技巧有助于提高开发效率和保持代码整洁,感兴趣的朋友跟随小编一起看看吧
    2024-09-09

最新评论