Vue 3 的父子组件传值主要遵循单向数据流 的原则:父传子 和 子传父 。
以下是详细的实现方式、代码示例以及注意事项。
1. 父传子
父组件通过自定义属性 传递数据,子组件通过 defineProps 接收数据。
核心步骤:
- 父组件 :在子组件标签上绑定属性(如
:title="msg")。 - 子组件 :使用
defineProps声明并接收属性。
代码示例:
父组件
html
<template>
<div>
<h2>父组件</h2>
<!-- 1. 传递静态值 -->
<ChildComponent title="静态标题" />
<!-- 2. 传递动态变量 -->
<ChildComponent :title="parentMsg" :age="18" />
</div>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue'
import { ref } from 'vue'
const parentMsg = ref('来自父组件的动态消息')
</script>
子组件
html
<template>
<div class="child-box">
<p>子组件接收到的标题:{{ title }}</p>
<p>年龄:{{ age }}</p>
</div>
</template>
<script setup>
// 2. 使用 defineProps 接收
// 写法一:数组写法(简单,但不直观)
// defineProps(['title', 'age'])
// 写法二:对象写法(推荐,可以限制类型和默认值)
const props = defineProps({
title: {
type: String,
required: true, // 必填
default: '默认标题' // 默认值
},
age: {
type: Number,
default: 0
}
})
// 注意:在 JS 中使用 props 变量需要通过 props.title
console.log(props.title)
</script>
⚠️ 注意:
- 在
<template>模板中直接使用变量名(如title)。- 在
<script>代码中,需要通过props.title访问。- 不要在子组件中直接修改 props 传来的值 (如
props.title = 'new'),这会违反单向数据流,导致报错。
2. 子传父
父组件通过自定义事件 接收数据,子组件通过 defineEmits 触发事件并传参。
核心步骤:
- 父组件 :在子组件标签上监听事件(如
@change-msg="handleMsg")。 - 子组件 :使用
defineEmits声明事件,并在合适时机调用emit('事件名', 数据)。
代码示例:
父组件
html
<template>
<div>
<h2>父组件收到的消息:{{ msgFromChild }}</h2>
<!-- 1. 绑定自定义事件 change-msg -->
<ChildComponent @change-msg="handleChange" />
</div>
</template>
<script setup>
import ChildComponent from './ChildComponent.vue'
import { ref } from 'vue'
const msgFromChild = ref('等待子组件发送...')
// 3. 定义处理函数,接收子组件传来的参数
const handleChange = (newMsg) => {
msgFromChild.value = newMsg
}
</script>
子组件
html
<template>
<button @click="sendToParent">点击向父组件传值</button>
</template>
<script setup>
// 1. 声明要触发的事件
const emit = defineEmits(['change-msg'])
const sendToParent = () => {
const message = '你好父组件,我是子组件'
// 2. 触发事件并传递数据
emit('change-msg', message)
}
</script>
💡 技巧 :
Vue 官方推荐事件命名使用 kebab-case (短横线命名) ,如
@update-value,因为在 HTML 模板中不区分大小写,@updateValue可能会被解析成@updatevalue。
3. 双向绑定
如果你想要实现"父传子,子修改后自动传回父"的效果(类似 v-model),Vue 3 提供了更简便的方式。
方式一:使用 v-model (推荐)
Vue 3 的 v-model 默认绑定了 modelValue 属性和 update:modelValue 事件。
父组件
html
<template>
<!-- 等同于 :modelValue="text" @update:modelValue="text = $event" -->
<ChildInput v-model="text" />
</template>
子组件
html
<template>
<!-- 必须绑定 value 并触发 input 事件 -->
<input
type="text"
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
<script setup>
defineProps(['modelValue'])
defineEmits(['update:modelValue'])
</script>
方式二:defineModel 宏 (Vue 3.4+ 最新语法)
这是一个编译器宏,大大简化了双向绑定的代码。
子组件
html
<script setup>
// 自动定义 props 和 emit,返回一个可写的 ref
const modelValue = defineModel()
// 修改 modelValue.value 会自动同步到父组件
</script>
<template>
<input v-model="modelValue" />
</template>
4. 父组件直接操作子组件
如果父组件想要直接调用子组件的方法或访问其数据,可以通过 ref 获取子组件实例。
父组件
html
<template>
<ChildComponent ref="childRef" />
<button @click="callChildMethod">调用子组件方法</button>
</template>
<script setup>
import { ref } from 'vue'
import ChildComponent from './ChildComponent.vue'
// 1. 定义与 ref 同名的变量
const childRef = ref(null)
const callChildMethod = () => {
// 3. 通过 value 访问子组件暴露的方法
childRef.value.someMethod()
}
</script>
子组件
html
<script setup>
const someMethod = () => {
console.log('子组件方法被调用了!')
}
// 2. 必须使用 defineExpose 暴露方法或属性,父组件才能通过 ref 调用
defineExpose({
someMethod
})
</script>
总结图表
| 传输方向 | 核心语法 | 父组件操作 | 子组件操作 |
|---|---|---|---|
| 父传子 | Props | <Child :msg="data" /> |
const props = defineProps(['msg']) |
| 子传父 | Emits | <Child @update="fn" /> |
const emit = defineEmits(['update']) |
| 双向绑定 | v-model | <Child v-model="data" /> |
defineProps + defineEmits 或 defineModel |
| 直接调用 | Ref | <Child ref="cRef" /> |
defineExpose({ method }) |