Vue组件间通信

本文详细介绍了Vue组件间的几种常见通信方式,包括props和emit、refs、插槽、EventBus、Vuex以及父子组件直接访问等,并对比了它们的用途和适用场景,强调了状态管理的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Vue组件间通信

Vue组件间的通信可以通过多种方式实现:

  1. props 和 emit
  2. refs (引用组件或元素实例)
  3. 通过插槽 (slot) 传递内容
  4. Event Bus (使用 Vue 实例作为中央事件总线)
  5. Vuex (使用集中状态管理)
  6. parent 和 children (直接访问组件实例)
  7. attrs 和 listeners (传递属性和事件)
  8. provide 和 inject (祖先与后代组件间通信)
  9. 全局状态 (如: window.globalState)
  10. localStorage/sessionStorage
  11. 自定义事件 (在 DOM 中)
  12. 通过直接修改 props (不推荐)

对比表格

通信方法用途说明
props 和 emit父传子、子传父父组件通过props传递数据,子组件通过emit回传消息
refs父传子父组件通过refs直接访问子组件实例
插槽 (slot)父传子父组件插入内容到子组件模板中
Event Bus兄弟间通信使用Vue实例作为事件总线来发布和订阅事件
Vuex全局通信用于集中状态管理,所有组件都可以访问和修改状态
parent 和 children父传子、子传父通过 p a r e n t 或 parent或 parentchildren直接访问组件实例
attrs 和 listeners父传子、子传父非prop的属性和事件的传递
provide 和 inject祖先与后代间祖先组件提供变量,后代组件注入
window.globalState全局通信全局变量,任何组件都可以访问和修改
localStorage/sessionStorage全局通信数据持久化存储,在多个组件间共享状态
自定义事件兄弟间通信使用DOM的事件系统
直接修改 props父传子不推荐使用,可能导致不可预测的行为

1. props和emit

父组件 -> 子组件: 通过 props 传递数据。

子组件 -> 父组件: 通过 $emit 触发父组件的方法。

ParentComponent.vue

<template>
  <div>
    <ChildComponent :parentData="data" @childEvent="handleChildEvent"></ChildComponent>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  data() {
    return {
      data: "Data from Parent"
    }
  },
  methods: {
    handleChildEvent(payload) {
      console.log("Received data from child:", payload);
    }
  }
}
</script>

ChildComponent.vue

<template>
  <div>
    <p>{{ parentData }}</p>
    <button @click="sendToParent">Send Data to Parent</button>
  </div>
</template>

<script>
export default {
  props: {
    parentData: {
      type: String,
      required: true
    }
  },
  methods: {
    sendToParent() {
      this.$emit("childEvent", "Data from Child");
    }
  }
}
</script>

2. ref

通过引用来调用子组件的方法或访问其数据。

ParentComponent.vue

<template>
  <div>
    <ChildComponent ref="childRef"></ChildComponent>
    <button @click="useChildRef">Use Child Ref</button>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  methods: {
    useChildRef() {
      // 使用 $refs 调用子组件的方法
      this.$refs.childRef.childMethod();
    }
  }
}
</script>

ChildComponent.vue

<template>
    <div>
        <!-- 子组件内容 -->
    </div>
</template>
  
<script>
export default {
    methods: {
        childMethod() {
            console.log("Child method called from parent!");
        }
    }
}
</script>

3. 通过插槽 (slot) 传递内容

这是一个更灵活的方式,允许你在父组件中定义一些默认内容,而子组件则可以选择是否使用这些内容。

ParentComponent.vue:

<template>
  <div>
    <ChildComponent>
      <template v-slot:default>
        {{ message }}
      </template>
    </ChildComponent>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: { ChildComponent },
  data() {
    return {
      message: 'Hello from parent!'
    };
  }
}
</script>

ChildComponent.vue:

<template>
  <div>
    <slot></slot> <!-- 这里显示父组件传入的内容 -->
  </div>
</template>

<script>
export default {}
</script>

使用插槽,你可以灵活地分发父组件的内容,使得组件复用更为简单和灵活。

基础插槽

ChildComponent.vue

<template>
  <div>
    <slot></slot> <!-- 默认插槽 -->
  </div>
</template>

ParentComponent.vue

<template>
  <ChildComponent>
    Hello from Parent! <!-- 这段内容会被放入 ChildComponent 的 <slot></slot> 中 -->
  </ChildComponent>
</template>

具名插槽

有时,你可能希望子组件有多个插槽,并且每个插槽都有其特定的名称。

ChildComponent.vue

<template>
  <div>
    <header>
      <slot name="header"></slot> <!-- header 插槽 -->
    </header>
    <main>
      <slot></slot> <!-- 默认插槽 -->
    </main>
    <footer>
      <slot name="footer"></slot> <!-- footer 插槽 -->
    </footer>
  </div>
</template>

ParentComponent.vue

<template>
  <ChildComponent>
    <template v-slot:header>
      This is the header.
    </template>

    This is the default content.

    <template v-slot:footer>
      This is the footer.
    </template>
  </ChildComponent>
</template>

作用域插槽

有时,子组件希望将一些数据“传递”回父组件。这可以通过作用域插槽实现。

子组件 (ChildComponent.vue):

<template>
  <div>
    <slot myMessage="Hello from child!"></slot> <!-- 提供给插槽的数据 -->
  </div>
</template>

ParentComponent.vue

<template>
  <ChildComponent>
    <template v-slot:default="slotProps">
      {{ slotProps.myMessage }}
    </template>
  </ChildComponent>
</template>

在上述示例中,子组件提供了一个myMessage属性,父组件可以通过插槽的slotProps来访问这个属性。

4. Event Bus

Event Bus 是一个使用 Vue 实例作为中央事件总线的模式。通过它,不相关的组件可以进行通信。

EventBus.js

首先创建一个 EventBus。

import Vue from 'vue';
export const EventBus = new Vue();

ParentComponent.vue

在父组件中,你可以监听来自子组件的事件。

<template>
  <div>
    <ChildComponent></ChildComponent>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';
import { EventBus } from './EventBus.js';

export default {
  components: {
    ChildComponent
  },
  created() {
    // 监听子组件发送的事件
    EventBus.$on('childEvent', this.handleChildEvent);
  },
  beforeDestroy() {
    // 销毁监听器,避免内存泄漏
    EventBus.$off('childEvent', this.handleChildEvent);
  },
  methods: {
    handleChildEvent(payload) {
      console.log("Received data from child via EventBus:", payload);
    }
  }
}
</script>

ChildComponent.vue

在子组件中,你可以发送事件到 EventBus。

<template>
  <div>
    <button @click="sendToParent">Send Data to EventBus</button>
  </div>
</template>

<script>
import { EventBus } from './EventBus.js';

export default {
  methods: {
    sendToParent() {
      // 使用 EventBus 发送事件
      EventBus.$emit('childEvent', 'Data from Child via EventBus');
    }
  }
}
</script>

5. Vuex

Vuex 是一个状态管理库,用于 Vue.js 应用程序。通过 Vuex, 任何组件都可以访问或更改状态。

安装Vuex
首先得安装Vuex

npm install vuex --save

这里注意安装vuex的版本,如果是vue2需要写成:

npm install vuex@3 --save

store.js

设置 Vuex store。

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    message: ''
  },
  mutations: {
    // 设定消息
    setMessage(state, message) {
      state.message = message;
    }
  }
});

main.js

import Vue from 'vue'
import App from './App.vue'
import store from './store' // 从你的store.js导入

Vue.config.productionTip = false

new Vue({
  store, // 这里
  render: h => h(App),
}).$mount('#app')

ParentComponent.vue

在父组件中,你可以设置并从 Vuex store 中获取数据。

<template>
  <div>
    <ChildComponent></ChildComponent>
    <p>Message from Vuex: {{ messageFromVuex }}</p>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: {
    ChildComponent
  },
  computed: {
    // 从 Vuex store 获取数据
    messageFromVuex() {
      return this.$store.state.message;
    }
  }
}
</script>

ChildComponent.vue

在子组件中,你可以更改 Vuex store 的状态。

<template>
  <div>
    <button @click="sendMessage">Send Data to Vuex</button>
  </div>
</template>

<script>
export default {
  methods: {
    sendMessage() {
      // 使用 mutation 更改 Vuex store 的状态
      this.$store.commit('setMessage', 'Data from Child to Vuex');
    }
  }
}
</script>

6. parent 和 children

通过直接访问组件实例来实现通信。

ParentComponent.vue

<template>
    <div>
      <ChildComponent></ChildComponent>
      <button @click="callChildMethod">Call Child Method</button>
    </div>
</template>
  
<script>
import ChildComponent from './ChildComponent.vue';

export default {
    components: {
        ChildComponent
    },
    methods: {
        parentMethod() {
            console.log("Parent method called from child!");
        },
        callChildMethod() {
            // 调用子组件的方法
            this.$children[0].childMethod();
        }
    }
}
</script>

ChildComponent.vue

<template>
  <div>
    <button @click="callParentMethod">Call Parent Method</button>
  </div>
</template>

<script>
export default {
  methods: {
    childMethod() {
      console.log("Child method called from parent!");
    },
    callParentMethod() {
      // 调用父组件的方法
      this.$parent.parentMethod();
    }
  }
}
</script>

7. attrs 和 listeners

用于在组件间传递属性和监听器。

ParentComponent.vue

<template>
    <div>
      <p>Parent Color: {{ parentColor }}</p>
      <ChildComponent :color="parentColor" @changeColor="changeColor"/>
    </div>
</template>
  
<script>
import ChildComponent from './ChildComponent.vue';

export default {
    components: {
        ChildComponent
    },
    data() {
        return {
            parentColor: 'red'
        }
    },
    methods: {
        changeColor(newColor) {
            this.parentColor = newColor;
        }
    }
}
</script>

ChildComponent

<template>
    <div 
      v-bind="$attrs" 
      :style="{ backgroundColor: color }" 
      @click="changeMyColor" 
      v-on="$listeners">
        Click me to change color
    </div>
</template>
  
<script>
export default {
    props: {
        color: {
            type: String,
            default: ''
        }
    },
    methods: {
        changeMyColor() {
            let newColor = this.color === 'red' ? 'blue' : 'red';
            this.$emit('changeColor', newColor);
        }
    }
}
</script>

attrs:当父组件传递给子组件的属性没有在子组件的props定义时,它们会被视为“绑定的属性”并存在于$attrs中。
listeners:它包含了传递给子组件的事件监听器。

在以上例子中,父组件向子组件传递一个属性,并监听子组件触发一个事件。

  • 父组件传递给子组件一个color属性,其值为parentColor。
  • 当子组件内部div被点击时,changeMyColor方法被调用,它改变颜色并通过$emit发送一个自定义事件changeColor。
  • 这个changeColor事件被父组件监听,并执行内部的changeColor方法来更新parentColor的值。

扩展对比
我们来设计一个简单的 BaseInput 组件作为例子。这个组件旨在替代普通的 <input> 元素,并带有一些自定义样式或功能。

1. 使用 attrslisteners

BaseInput.vue

<template>
  <!-- 使用$attrs来透传任何传入的属性(除props外) -->
  <!-- 使用$listeners来透传所有传入的事件监听器 -->
  <input v-bind="$attrs" v-on="$listeners" class="custom-input">
</template>

<script>
export default {
  inheritAttrs: false,  // 防止Vue将属性绑定到根元素
  props: {
    value: String  // 作为例子,我们仅定义了一个value prop
  }
}
</script>

在这个设置中,你可以传递任何属性和监听器到 BaseInput,而不需要在其内部明确定义。例如:

<BaseInput type="text" placeholder="Enter text" @input="handleInput" />

2. 不使用 attrslisteners

BaseInput.vue

<template>
  <!-- 必须为每一个可能的属性和事件明确定义绑定和监听 -->
  <input :type="type" :placeholder="placeholder" @input="onInput" class="custom-input">
</template>

<script>
export default {
  props: {
    value: String,
    type: {
      type: String,
      default: 'text'
    },
    placeholder: String  // 必须为每一个可能的属性定义一个prop
  },
  methods: {
    onInput(event) {
      // 对于事件,我们需要手动emit
      this.$emit('input', event.target.value);
    }
  }
}
</script>

在这个设置中,每当你需要传递一个新的属性或监听器到 BaseInput,你必须在其内部明确定义。例如,如果你想添加一个 maxlength 属性,你需要更新组件的 props。如果你想监听 focus 事件,你需要在组件内部添加一个新的方法。

总结:

  • 使用 $attrs$listeners 的版本可以透传任何未明确定义为 props 的属性和所有事件监听器,使得组件更加灵活和通用。
  • 不使用它们的版本要求你为每一个可能的属性和事件在组件内部明确定义,这样会使组件的维护和扩展变得更加困难。

8. provide 和 inject

用于祖先组件向其所有后代组件提供变量。

ParentComponent.vue

<template>
  <div>
    <ChildComponent></ChildComponent>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  provide() {
    return {
      message: 'Message from Parent'
    };
  },
  components: {
    ChildComponent
  }
}
</script>

ChildComponent.vue

<template>
  <div>
    {{ message }}
  </div>
</template>

<script>
export default {
  inject: ['message']
}
</script>

9. 全局状态

使用全局变量实现组件间通信是一个简单但不推荐的方法,因为它违反了组件的封装原则,容易导致状态管理的混乱。但在某些简单的场景或快速原型开发中,它确实可以很快地实现组件间的通信。

在提供的例子中,我们在 main.js 里创建了一个全局变量 globalState,然后在任何 Vue 组件中都可以访问它。

让我们举一个例子来说明如何在两个组件之间使用这种方法进行通信。

1. 设置全局状态

main.js

window.globalState = { message: 'Hello from Global!' };

2. 创建一个组件修改这个全局状态

UpdateGlobalMessage.vue

<template>
    <button @click="updateMessage">Update Global Message</button>
</template>
  
<script>
export default {
    methods: {
        updateMessage() {
            window.globalState.message = "Updated Global Message!";
            console.log(window.globalState.message)
        }
    },
    created() {
        console.log(window.globalState.message)
    }
}
</script>

现在,当你点击 “Update Global Message” 按钮时,全局状态的 message 会被更新,这个更新会立即反映在console上。

尽管这种方法简单明了,但在大型应用中可能会导致很多问题。状态的来源和修改可能变得难以追踪,而且容易产生意外的副作用。对于大型应用,推荐使用专门的状态管理库,如 Vuex,以更有组织、可预测的方式管理状态。

10. localStorage/sessionStorage

使用 Web Storage API 来存储和检索数据。

ParentComponent.vue

<template>
    <div>
      {{ retrievedMessage }}
      <ChildComponent @message-stored="refreshComponent"></ChildComponent>
    </div>
  </template>
  
  <script>
import ChildComponent from './ChildComponent.vue';

  export default {
    components: {
        ChildComponent
    },
    data() {
        return { 
            retrievedMessage: localStorage.getItem('message') || 'Default Message'
        }
    },
    methods: {
        refreshComponent() {
            this.retrievedMessage = localStorage.getItem('message')
            this.$forceUpdate()
        }
    }
  }
  </script>

ChildComponent.vue

<template>
    <div>
      <button @click="storeMessage">Store Message</button>
    </div>
  </template>
  
  <script>
  export default {
    methods: {
      storeMessage() {
        localStorage.setItem('message', 'Stored Message');
        this.$emit('message-stored')
      }
    }
  }
  </script>

11. 自定义事件

使用 DOM 的自定义事件机制来实现组件通信。

ParentComponent.vue

<template>
<div @custom-message="handleMessage">
    <ChildComponent />
    <p>{{ message }}</p>
</div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
    name: 'ParentComponent',
    components: {
        ChildComponent
    },
    data() {
        return {
            message: 'I havent received any information yet'
        };
    },
    methods: {
        handleMessage(event) {
            this.message = event.detail;
            console.log("Received custom meessage event:", event.detail)
        }
    }
}
</script>

ChildComponent.vue

<template>
    <button @click="sendToParent">click me</button>
</template>
  
<script>
export default {
name: 'ChildComponent',
methods: {
    sendToParent() {
        // 创建自定义事件
        const event = new CustomEvent('custom-message', { detail: 'This is information from the chlidComponent' , bubbles: true});
        // 触发该事件
        this.$el.dispatchEvent(event);
    }
}
}
</script>

12. 通过直接修改 props (不推荐)

ParentComponent.vue:

<template>
  <div>
    <ChildComponent :message="message"/>
    <button @click="changeMessage">Change Message</button>
  </div>
</template>

<script>
import ChildComponent from './ChildComponent.vue';

export default {
  components: { ChildComponent },
  data() {
    return {
      message: 'Hello from parent!'
    };
  },
  methods: {
    changeMessage() {
      // 不建议这样做!
      this.$children[0].message = 'Changed message!';
    }
  }
}
</script>

ChildComponent.vue:

<template>
  <div>
    {{ message }}
  </div>
</template>

<script>
export default {
  props: ['message']
}
</script>

虽然上述方法可以工作,但直接修改子组件的props可能会导致不可预测的行为,特别是当子组件依赖props来触发某些生命周期钩子或计算属性时。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值