菜鸟-创作你的创作

Vue 3 中父子组件双向绑定的 4 种方式(最新推荐)

在 Vue 3 中,父子组件之间想要 “双向绑定”(parent ↔ child)时,常见有好几种方式/思路。以下是4 种常用 / 推荐的方式 — 并说明它们适合什么场景 ✅


🔄 四种父子双向/近似双向绑定方式

方式 1 — v-model(传统 props + emit)

这是 Vue 3 最基础、兼容性也最好的做法。

优点:清晰、明确,兼容性好。
适合:大部分父子组件之间需要 “简单值/表单项 + 双向同步” 的场景。


方式 2 — v-model + 自定义 prop/event 名(multiple v-models)

如果子组件不仅仅是一个 value,而是有多个可双向绑定的字段,Vue 3 支持给 v-model 指定名字,从而让一个子组件同时支持多个双向绑定。 (v3-migration.vuejs.org)

例如:

<ChildComponent v-model:firstName="first" v-model:lastName="last" />

子组件内部就分别定义 props: ['firstName', 'lastName'],并 emit('update:firstName', …) / emit('update:lastName', …)。 (vue.uz)

优点:组件更灵活,可以一次管理多个字段。
适合:复杂表单场景,或需要在一个子组件中双向绑定多个数据字段。


方式 3 — 新语法 defineModel()(Vue 3.3+)

从 Vue 3.3+ 开始,引入了宏 defineModel(),可以简化 v-model 在子组件里的定义 —— 不需要手动写 props + emit。 (ar.vuejs.org)

举例(子组件):

<script setup>
const value = defineModel<string>()
</script>

<template>
  <input v-model="value" />
</template>

父组件仍然使用 <Child v-model="someRef" />。这样 value 的变化会自动触发父组件的更新。 (Vue School)

优点:代码简洁,减少 boilerplate;对新项目尤其推荐。
适合:新项目/用 Composition API + <script setup> 的场景。


方式 4 — 父通过 props 下发 + 子通过 $emit 自定义事件 通知父 (非 v-model)

有时候你不想、或不需要使用 v-model,只是想让子组件“动作”通知父组件。

这个其实不是严格意义上的“双向绑定” —— 更准确地说是「单向 props + 子 → 父 事件通知」。但在很多 UI 场景下,这种方式能达到类似“父子同步 + 双向更新”的效果。

优点:语义明确,行为清晰,适合复杂交互,不局限于“值同步”。
适合:子组件做复杂操作(比如触发某些动作、带上额外参数调用父方法)而不仅是简单输入/状态同步。


✅ 推荐选择 & 使用建议


⚠️ 注意事项 / 常见坑

下面直接给你4 种方式可直接复制运行的示例代码(父组件 + 子组件),全部基于 Vue 3 + <script setup>


方式 1:最常用的 v-model(modelValue + update:modelValue)

Child.vue

&lt;!-- Child.vue -->
&lt;script setup>
defineProps({
  modelValue: String
})

const emit = defineEmits(['update:modelValue'])

function onInput(e) {
  emit('update:modelValue', e.target.value)
}
&lt;/script>

&lt;template>
  &lt;input :value="modelValue" @input="onInput" placeholder="方式1:请输入…" />
&lt;/template>

Parent.vue

&lt;!-- Parent.vue -->
&lt;script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const text = ref('')
&lt;/script>

&lt;template>
  &lt;h3>方式 1:v-model(标准)&lt;/h3>
  &lt;Child v-model="text" />
  &lt;p>父组件接收到:{{ text }}&lt;/p>
&lt;/template>


方式 2:多个 v-model(自定义 v-model 名)

ChildMulti.vue

&lt;script setup>
defineProps({
  firstName: String,
  lastName: String
})

const emit = defineEmits(['update:firstName', 'update:lastName'])
&lt;/script>

&lt;template>
  &lt;input :value="firstName" @input="e => emit('update:firstName', e.target.value)" placeholder="姓…" />
  &lt;input :value="lastName" @input="e => emit('update:lastName', e.target.value)" placeholder="名…" />
&lt;/template>

Parent.vue

&lt;script setup>
import { ref } from 'vue'
import ChildMulti from './ChildMulti.vue'

const first = ref('')
const last = ref('')
&lt;/script>

&lt;template>
  &lt;h3>方式 2:多个 v-model&lt;/h3>
  &lt;ChildMulti v-model:firstName="first" v-model:lastName="last" />
  &lt;p>姓:{{ first }},名:{{ last }}&lt;/p>
&lt;/template>


方式 3:defineModel(Vue 3.3+ 最推荐)

ChildDefineModel.vue

&lt;script setup>
const value = defineModel()   // 自动生成 props + emit
&lt;/script>

&lt;template>
  &lt;input v-model="value" placeholder="方式3:请输入…" />
&lt;/template>

Parent.vue

&lt;script setup>
import { ref } from 'vue'
import ChildDefineModel from './ChildDefineModel.vue'

const msg = ref('')
&lt;/script>

&lt;template>
  &lt;h3>方式 3:defineModel(推荐)&lt;/h3>
  &lt;ChildDefineModel v-model="msg" />
  &lt;p>父组件接收到:{{ msg }}&lt;/p>
&lt;/template>


方式 4:props 下发 + emit 通知父(非严格双向,但常用)

ChildEmit.vue

&lt;script setup>
const props = defineProps({
  count: Number
})

const emit = defineEmits(['increment'])
&lt;/script>

&lt;template>
  &lt;button @click="emit('increment')">
    子组件按钮(当前:{{ props.count }})
  &lt;/button>
&lt;/template>

Parent.vue

&lt;script setup>
import { ref } from 'vue'
import ChildEmit from './ChildEmit.vue'

const count = ref(0)

function add() {
  count.value++
}
&lt;/script>

&lt;template>
  &lt;h3>方式 4:props + emit(事件上报)&lt;/h3>
  &lt;ChildEmit :count="count" @increment="add" />
  &lt;p>父组件 count:{{ count }}&lt;/p>
&lt;/template>

退出移动版