前言:Vue3 提供了多种数据通信方式,适用于不同场景的组件间通信需求。以下是8种主要通信方式的详细说明和示例:
1. Props & Emits (父子组件通信)
适用场景:直接的父子组件通信
<!-- 父组件 -->
<template>
<ChildComponent
:title="parentTitle"
@update-title="handleUpdate"
/>
</template>
<script setup>
import { ref } from 'vue';
import ChildComponent from './Child.vue';
const parentTitle = ref('初始标题');
const handleUpdate = (newTitle) => {
parentTitle.value = newTitle;
};
</script>
<!-- 子组件 Child.vue -->
<template>
<div>
<h1>{{ title }}</h1>
<button @click="$emit('update-title', '新标题')">更改标题</button>
</div>
</template>
<script setup>
defineProps(['title']);
defineEmits(['update-title']);
</script>
2. Provide & Inject (跨层级组件通信)
适用场景:祖先组件向后代组件传递数据,无需逐层传递props
<!-- 祖先组件 -->
<script setup>
import { provide, ref } from 'vue';
const counter = ref(0);
provide('counter', {
counter,
increment: () => counter.value++
});
</script>
<!-- 后代组件 -->
<script setup>
import { inject } from 'vue';
const { counter, increment } = inject('counter');
</script>
3. $attrs (非props属性传递)
适用场景:传递未被props接收的属性
<!-- 父组件 -->
<template>
<ChildComponent class="child-class" data-test="123" />
</template>
<!-- 子组件 -->
<template>
<div v-bind="$attrs">将接收所有非props属性</div>
</template>
<script setup>
import { useAttrs } from 'vue';
const attrs = useAttrs();
console.log(attrs); // { class: 'child-class', 'data-test': '123' }
</script>
4. $refs & $parent (组件实例访问)
适用场景:直接访问组件实例(谨慎使用,破坏封装性)
<!-- 父组件 -->
<template>
<ChildComponent ref="childRef" />
<button @click="callChildMethod">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue';
const childRef = ref(null);
const callChildMethod = () => {
childRef.value.childMethod();
};
</script>
<!-- 子组件 -->
<script setup>
const childMethod = () => {
console.log('子组件方法被调用');
};
// 暴露方法给模板ref
defineExpose({
childMethod
});
</script>
5. Template Refs(模板引用)
适用场景:在 Vue 组件的模板中标记一个 DOM 元素或子组件
<!-- 父组件 -->
<template>
<ChildComponent ref="child" />
<button @click="child.method()">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue';
const child = ref(null);
</script>
<!-- 子组件 -->
<script setup>
const method = () => console.log('方法被调用');
defineExpose({ method });
</script>
6. v-model (双向绑定)
适用场景:简化父子组件双向数据绑定
<!-- 父组件 -->
<template>
<ChildComponent v-model="message" />
<p>父组件消息: {{ message }}</p>
</template>
<script setup>
import { ref } from 'vue';
const message = ref('初始消息');
</script>
<!-- 子组件 -->
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue']);
defineEmits(['update:modelValue']);
// Vue 3.4 开始就很简单不用定义emit和props,使用 defineModel('名字')
// const visible = defineModel('visible')
// 可以.value进行修改
// visible.value = false
</script>
vue2中使用
<!-- 父组件 visibleQuery:false -->
<Query v-model="visibleQuery" />
<!-- 子组件 -->
<template>
<a-modal title="查看" :visible="value"@cancel="$emit('input', false)">
123
</a-modal>
</template>
<script>
export default { name: 'Query', props: ['value'] }
</script>
7. Slot (插槽通信)
适用场景:内容分发和子向父传递数据
默认插槽、具名插槽...
作用域插槽
<!-- 父组件 -->
<ChildComponent v-slot="{ user }">
{{ user.name }}
</ChildComponent>
<!-- 子组件 -->
<template>
<div>
<slot :user="userData"></slot>
</div>
</template>
<script setup>
const userData = { name: '张三', age: 30 };
</script>
8. Pinia (状态管理)
适用场景:全局状态管理
// store/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
getters: {
doubleCount: (state) => state.count * 2
},
actions: {
increment() {
this.count++;
}
}
});
// 组件中使用
<script setup>
import { useCounterStore } from '@/stores/counter';
const counter = useCounterStore();
</script>
<template>
<div>{{ counter.count }}</div>
<div>{{ counter.doubleCount }}</div>
<button @click="counter.increment()">增加</button>
</template>
通信方式选择指南
通信方式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
Props & Emits | 父子组件直接通信 | 明确的数据流,易于理解 | 多层组件需逐层传递 |
Provide/Inject | 跨层级组件通信 | 避免props逐层传递 | 数据来源不够透明 |
$attrs | 传递非props属性 | 灵活传递属性 | 可能造成属性混乱 |
$refs & $parent | 直接访问组件实例 | 可直接调用方法 | 破坏组件封装性 |
ref共享 | 简单状态共享 | 轻量级实现 | 不适合大型应用 |
v-model | 父子组件双向绑定 | 语法简洁 | 仅限于简单数据绑定 |
Slot | 内容分发和子向父通信 | 灵活的内容组合 | 复杂逻辑可能难以维护 |
Pinia | 全局状态管理 | 集中式状态管理,功能强大 | 增加项目复杂度 |
后记:根据具体场景选择合适的通信方式,简单场景优先使用Props/Emits和Provide/Inject,复杂应用建议使用Pinia进行状态管理。