vue3的defineModel和useModel

一、defineModel

defineModel 是 Vue 3.4+ 引入的语法糖,用于简化双向绑定(v-model)的实现。它本质上是对 props 和 emit 的封装,使得在组件中使用双向绑定更加简洁和直观。

一、基本用法

父组件

<template> 
<ChildComponent v-model="message" /> 
</template> 
<script setup> 
import { ref } from 'vue' 
const message = ref('Hello') 
</script>

子组件(ChildComponent.vue)

<script setup> 
const model = defineModel() 
</script> 
<template> 
<input v-model="model" />
 </template>
<script setup>
const model = defineModel()
</script>

<template>
  <input :value="model" @input="model = $event.target.value" />
</template>

✅ 效果:子组件中的 input 输入值变化会自动同步到父组件的 message 上。

二、带参数的用法

你可以为 defineModel 指定参数,控制绑定的属性名和类型:

<script setup> 
// 使用自定义 prop 名称和类型 
const model = defineModel({ required: true, default: 'default value' }) 
</script>

或指定非默认的 prop/emit 名称:

const model = defineModel('customValue', { emit: 'update:customValue' })

等价于:

defineProps(['customValue']) defineEmits(['update:customValue'])

三、底层原理

defineModel 实际上是组合了以下两个操作:

  1. 定义了一个名为 modelValue 的 prop(默认情况下)
  2. 定义了一个名为 update:modelValue 的 emit(用于更新父组件)

因此下面这段代码:

const model = defineModel()

相当于:

const props = defineProps(['modelValue'])

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

const model = computed({ 
get() { return props.modelValue }, 
set(value) { emit('update:modelValue', value) } })

四、适用场景

场景推荐方式
简单的父子双向绑定✅ defineModel
多个双向绑定需求❌ 不适合,应使用 defineProps + defineEmits
自定义 v-model 行为✅ 可通过参数自定义 prop/emit 名称
需要复杂验证逻辑✅ 可结合 computed 属性使用

五、注意事项

  • defineModel 只能在 <script setup> 中使用
  • 默认 prop 名为 modelValue,默认 emit 名为 update:modelValue
  • 不支持多个 v-model 绑定(如 v-model:title 和 v-model:content 同时存在)
  • 适用于快速开发和简单表单组件封装,不建议在复杂业务逻辑中使用

六、与传统 v-model 的区别

特性Vue2Vue3(defineModel)
v-model 实现方式props.value + $emit('input')props.modelValue + $emit('update:modelValue')
支持多 v-model❌(需手动命名)✅(可命名多个)
语法糖✅ defineModel()
在 <script setup> 中✅ 支持

七、总结

defineModel 是 Vue3.4+ 提供的一个便捷 API,适合用于快速实现组件间的双向绑定,尤其适用于表单组件封装。虽然简化了代码,但在需要多个双向绑定或复杂逻辑时,仍推荐使用 defineProps 和 defineEmits 显式管理数据流。

 二、useModel

useModel 是一个运行时 API(适用于 <script setup> 和 Composition API),用于访问某个特定 v-model 的值。它比 defineModel 更灵活,可以支持非 modelValue 的命名方式(即自定义 v-model:name)。

 示例:

<script setup>
import { useModel } from 'vue'

// 默认使用 modelValue / update:modelValue
const model = useModel('modelValue')
</script>

<template>
  <input :value="model" @input="model = $event.target.value" />
</template>

 自定义名称(对应 v-model:name

// 假设父组件使用 v-model:title="xxx" const title = useModel('title')

此时相当于:

props: ['title'], emits: ['update:title']

 一、defineModel vs useModel

特性defineModeluseModel
类型推导✅ 完美支持✅ 支持
是否是编译器宏✅ 是(仅 <script setup>❌ 否(可在任何 setup 风格中使用)
支持自定义名称❌ 否(只能绑定 modelValue✅ 是
返回值类型Ref<any>Ref<any>
使用场景快速绑定默认 v-model处理自定义 v-model:name

二、注意事项

  1. 不能同时使用多个 defineModel()
    只能有一个默认的 v-model,如果你需要多个双向绑定,请使用 useModel('name')

  2. 不建议混合使用 props + defineModel
    因为 defineModel 本质上也是基于 props 和 emit 实现的,应避免手动操作同名 prop。

  3. 与 TypeScript 配合良好

    const model = defineModel<string>({ default: 'default value' })

    三、完整示例

父组件:

<template> 
<ChildComponent v-model="text" /> 
</template> 
<script setup> 
import { ref } from 'vue' const text = ref('')
 </script>

子组件(ChildComponent.vue):

<script setup> 
const model = defineModel()
 </script> 
<template>
 <input :value="model" @input="model = $event.target.value" /> 
</template>

四、总结

功能推荐 API
快速绑定默认 v-modeldefineModel()
绑定自定义 v-model:nameuseModel('name')
获取双向绑定的 ref✅ 两者都返回 ref
支持类型推导(TS)✅ 支持
运行时灵活性useModel 更高
编译时优化defineModel 更简洁

如果你正在使用 Vue 3.4+ 并采用 <script setup> 语法,推荐优先使用 defineModel 来简化 v-model 的处理。对于需要多 v-model 或自定义命名的情况,则使用 useModel

### 问题分析 在 Vue 3 中,`v-model` 是 `:modelValue` 与 `@update:modelValue` 的语法糖,用于实现双向绑定。然而,Vue 的设计原则是单向数据流,即父组件通过 `props` 向子组件传递数据,子组件不能直接修改父组件传入的 `prop` 值[^2]。因此,当尝试在子组件的 `prop` 上直接使用 `v-model` 时,Vite 会抛出错误: ``` [vite] Internal server error: v-model cannot be used on a prop, because local prop bindings are not writable. ``` 该错误提示明确指出不能对 `prop` 使用 `v-model`,因为 `prop` 是只读的,不能被直接赋值。正确的做法是使用 `v-model` 时,子组件应通过 `emit` 事件向上传递更新,而不是直接修改 `prop`。 --- ### 解决方案 #### 1. 使用 `v-model` 的替代方式:`v-model` + `emit` 在子组件中定义 `modelValue` 作为 `prop`,并通过 `emit` 事件触发更新: ```vue <script setup> const props = defineProps([&#39;modelValue&#39;]); const emit = defineEmits([&#39;update:modelValue&#39;]); </script> <template> <input :value="modelValue" @input="emit(&#39;update:modelValue&#39;, $event.target.value)" /> </template> ``` 在父组件中使用 `v-model` 绑定数据: ```vue <template> <ChildComponent v-model="inputValue" /> </template> ``` #### 2. 使用 `defineModel`(适用于 Vue 3.4+) 如果项目使用的是 Vue 3.4 及以上版本,可以使用 `defineModel` 来简化 `v-model` 的实现: ```vue <script setup> const model = defineModel(); </script> <template> <input v-model="model" /> </template> ``` #### 3. 使用 `v-bind` + `v-on` 替代 `v-model` 如果不想使用 `v-model`,可以使用 `v-bind` `v-on` 显式绑定值并监听输入事件: ```vue <template> <input :value="modelValue" @input="onInput" /> </template> <script setup> const props = defineProps([&#39;modelValue&#39;]); const emit = defineEmits([&#39;update:modelValue&#39;]); function onInput(event) { emit(&#39;update:modelValue&#39;, event.target.value); } </script> ``` 父组件中写法: ```vue <template> <ChildComponent :model-value="inputValue" @update:model-value="inputValue = $event" /> </template> ``` --- ### 特殊情况:`v-model` 与 `v-slot` 结合使用 在某些组件结构中,如使用 `v-slot` 插槽传递数据,若尝试在插槽内容中使用 `v-model`,也可能触发类似错误。例如: ```vue <two> <template v-slot:header="{ data, data1, data2 }"> <three :data="data" v-model:data1="data1" v-model:data2="data2" /> </template> </two> ``` 此时,`data1` `data2` 是插槽作用域内的变量,不能直接作为 `prop` 被 `v-model` 修改。应使用 `v-model` 的替代写法,或确保插槽变量是响应式的(如使用 `ref` 或 `reactive`)。 --- ### 总结 - `v-model` 不能直接用于 `prop`,因为 `prop` 是只读的,违背了 Vue 的单向数据流原则[^2]。 - 子组件应通过 `emit` 事件触发更新,而不是直接修改 `prop`。 - 可使用 `defineModel` 简化 `v-model` 的实现。 - 在插槽中使用 `v-model` 需注意变量作用域响应性。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值