在 Vue.js 开发中,组件间的通信是构建复杂应用的核心部分。特别是父子组件之间的数据传递,掌握正确的传参方式对于开发高效、可维护的 Vue 应用至关重要。本文将详细介绍 Vue 3 中父子组件传参的五种常用方式,包括具体的实现步骤、代码示例和最佳实践
一、Props - 父组件像子组件传递数据
Props 是Vue中最基础也是最推荐的父向子组件传参方式,它遵循单向数据流原则
步骤1:父组件中定义数据并传递给子组件
<template>
<div class="parent-component">
<h1>父组件</h1>
<ChildComponent :title="message" :count="number" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
// 定义父组件的数据
const message = ref('Hello Vue 3')
const number = ref(10)
</script>
步骤2:子组件中接受并使用 props
<template>
<div class="parent-component">
<h1>父组件</h1>
<ChildComponent :title="message" :count="number" />
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
// 定义父组件的数据
const message = ref('Hello Vue 3')
const number = ref(10)
</script>
特点与注意事项
① 单向数据流:父组件更新数据,子组件会自动响应
② 不可直接修改:子组件不能直接修改 props,需要通过 emit 通知父组件
③ 类型校验:支持多种验证方式,包括类型、必填、默认值、自定义验证器
二、Emit - 子组件向父组件传递数据
Emit 机制允许子组件通过触发事件的方式向父组件传递数据或通知父组件执行操作
步骤1:子组件中定义并触发事件
<template>
<div class="child-component">
<button @click="handleIncrement">+1</button>
<button @click="handleDecrement">-1</button>
<button @click="handleReset">重置</button>
</div>
</template>
<script setup>
// 定义可以触发的事件
const emit = defineEmits(['update:count', 'reset'])
const handleIncrement = () => {
emit('update:count', 1) // 传递参数1表示增加1
}
const handleDecrement = () => {
emit('update:count', -1) // 传递参数-1表示减少1
}
const handleReset = () => {
emit('reset') // 不传递参数
}
</script>
步骤2:父组件中监听事件并处理
<template>
<div class="parent-component">
<h1>当前计数: {{ count }}</h1>
<ChildComponent
@update:count="handleCountUpdate"
@reset="handleReset"
/>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
const count = ref(0)
const handleCountUpdate = (delta) => {
count.value += delta
}
const handleReset = () => {
count.value = 0
}
</script>
简化写法:v-model
Vue 3 支持使用 v-model 指令来简化 props 和 emit 的组合使用:
<!-- 父组件 -->
<template>
<ChildComponent v-model:count="count" />
</template>
<!-- 子组件 -->
<script setup>
const emit = defineEmits(['update:count'])
// 当需要更新时
emit('update:count', newValue)
</script>
三、Ref / Reactive 透传
通过将响应式对象(ref 或 reactive)直接传递给子组件,可以实现更灵活的数据共享
步骤1:父组件创建响应式对象
<template>
<div class="parent-component">
<h1>父组件数据: {{ state.count }}</h1>
<ChildComponent :state="state" />
</div>
</template>
<script setup>
import { reactive } from 'vue'
import ChildComponent from './ChildComponent.vue'
const state = reactive({
count: 0,
message: 'Hello',
user: {
name: 'John',
age: 25
}
})
</script>
步骤2:子组件接收并使用响应式对象
<template>
<div class="child-component">
<h2>子组件</h2>
<p>{{ state.message }}</p>
<p>计数: {{ state.count }}</p>
<button @click="state.count++">增加计数</button>
<button @click="updateUser">更新用户信息</button>
</div>
</template>
<script setup>
const props = defineProps({
state: {
type: Object,
required: true
}
})
const updateUser = () => {
props.state.user.name = 'Jane'
props.state.user.age = 26
}
</script>
注意:
① 破坏单向数据流:子组件可以直接修改父组件的数据,这可能导致调试困难
② 适用场景:适合简单的父子组件关系,不推荐在复杂应用中过度使用
③ 最佳实践:建议通过 emit 或 v-model 来明确通信意图
四、Provide / Inject - 跨级级传参
Provide / Inject API 专门用于解决深层嵌套组件之间的通信问题,避免了 props 的逐层传递
步骤1:祖先组件提供数据
<!-- Grandparent.vue -->
<template>
<div class="grandparent">
<ParentComponent />
</div>
</template>
<script setup>
import { provide, ref, reactive } from 'vue'
import ParentComponent from './ParentComponent.vue'
// 提供响应式数据
const theme = ref('dark')
const user = reactive({
name: 'Admin',
role: 'superuser'
})
// 提供数据和方法
provide('theme', theme)
provide('user', user)
provide('updateTheme', (newTheme) => {
theme.value = newTheme
})
</script>
步骤2:后代组件注入数据
<!-- Grandchild.vue -->
<template>
<div class="grandchild">
<h3>孙组件</h3>
<p>当前主题: {{ theme }}</p>
<p>用户: {{ user.name }}</p>
<button @click="toggleTheme">切换主题</button>
</div>
</template>
<script setup>
import { inject } from 'vue'
// 注入数据和方法
const theme = inject('theme')
const user = inject('user')
const updateTheme = inject('updateTheme')
const toggleTheme = () => {
const newTheme = theme.value === 'dark' ? 'light' : 'dark'
updateTheme(newTheme)
}
</script>
特点与注意:
① 跨层级传递:可以跳过中间组件直接传递数据
② 响应式支持:需要手动传递 ref 或 reactive 对象来保持响应性
③ 组件耦合:过度使用可能导致组件间耦合度增高
④ 类型安全:建议配合 TypeScript 使用以获得更好的类型提示
五、事件总线 - 非父子组件通信
Vue 3 移除了 Vue 2 中的 on / off API,需要借助第三方库来实现事件总线功能
步骤1:安装并创建事件总线
npm install mitt
// eventBus.js
import mitt from 'mitt'
export const bus = mitt()
步骤2:在发送组件中触发事件
<!-- ComponentA.vue -->
<template>
<div>
<h2>组件A</h2>
<button @click="sendMessage">发送消息</button>
<button @click="updateData">更新数据</button>
</div>
</template>
<script setup>
import { bus } from './eventBus'
const sendMessage = () => {
bus.emit('message', 'Hello from Component A')
}
const updateData = () => {
bus.emit('data-update', {
timestamp: new Date(),
value: Math.random()
})
}
</script>
步骤3:在接收组件中监听事件
<!-- ComponentB.vue -->
<template>
<div>
<h2>组件B</h2>
<p>{{ receivedMessage }}</p>
<p>最后更新: {{ lastUpdate }}</p>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { bus } from './eventBus'
const receivedMessage = ref('')
const lastUpdate = ref('')
const handleMessage = (message) => {
receivedMessage.value = message
}
const handleDataUpdate = (data) => {
lastUpdate.value = data.timestamp.toLocaleString()
}
onMounted(() => {
// 挂载时开始监听
bus.on('message', handleMessage)
bus.on('data-update', handleDataUpdate)
})
onUnmounted(() => {
// 卸载时移除监听,避免内存泄漏
bus.off('message', handleMessage)
bus.off('data-update', handleDataUpdate)
})
</script>
注意:
① 内存管理:需要手动移除事件监听,避免内存泄漏
② 调试困难:事件流比较隐式,调试时需要跟踪事件的触发和监听
③ 适用场景:适合小型应用或简单的跨组件通信
六、各种传参方式对比:
|
方式 |
数据流向 |
响应式 |
适用场景 |
复杂度 |
|
Props |
父 → 子 |
✅ |
基础数据传递 |
简单 |
|
Emit |
子 → 父 |
❌ |
子组件通知父组件 |
简单 |
|
v-model |
双向绑定 |
✅ |
表单组件、状态同步 |
简单 |
|
Ref/Reactive 透传 |
双向 |
✅ |
简单父子组件关系 |
中等 |
|
Provide/Inject |
祖先 → 后代 |
✅ |
深层嵌套组件 |
中等 |
|
事件总线 |
任意方向 |
❌ |
非父子组件通信 |
复杂 |


5980

被折叠的 条评论
为什么被折叠?



