在 Vue 3 中,ref、reactive 和 defineModel(需配合 <script setup> 使用)是三种常用的响应式 API,它们有不同的适用场景和区别:
1. ref
特点
- 用于包装 基本类型(
string/number/boolean) 或 引用类型(object/array) ,使其变成响应式。 - 通过
.value访问或修改值(在<template>中自动解包,无需.value)。 - 适用于 独立变量 或需要明确控制响应式更新的场景。
示例
import { ref } from 'vue';
const count = ref(0); // 基本类型
const user = ref({ name: 'Alice' }); // 对象
// 修改值
count.value++;
user.value.name = 'Bob';
适用场景
✅ 基本类型(如数字、字符串、布尔值)
✅ 需要显式控制响应式更新的变量
✅ 需要直接替换整个对象/数组(如 user.value = { name: 'Bob' })
2. reactive
特点
- 仅适用于 对象或数组,返回一个响应式代理对象。
- 直接访问/修改属性,无需
.value。 - 如果直接替换整个对象(
state = { ... }),会 失去响应性。
示例
import { reactive } from 'vue';
const state = reactive({
count: 0,
user: { name: 'Alice' }
});
// 修改属性
state.count++;
state.user.name = 'Bob';
适用场景
✅ 复杂对象或嵌套数据(如表单数据、全局状态)
✅ 需要直接修改属性(无需 .value)
❌ 不适用于基本类型(如 reactive(0) 会报错)
❌ 不能直接替换整个对象(否则失去响应性)
3. defineModel(Vue 3.4+ 新增)
特点
- 用于 简化父子组件的双向绑定(
v-model) ,替代props+emit模式。 - 必须用在
<script setup>中。 - 底层仍然是
ref,但语法更简洁。
示例
父组件 (Parent.vue) :
<template>
<Child v-model="count" />
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
</script>
子组件 (Child.vue) :
<template>
<button @click="modelValue++">{{ modelValue }}</button>
</template>
<script setup>
const modelValue = defineModel(); // 默认 `modelValue`
</script>
自定义修饰符
<!-- 子组件 -->
<script setup>
const [model, modifiers] = defineModel({
set(value) {
if (modifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
})
</script>
<!-- 父组件使用 -->
<Child v-model.capitalize="text" />
多v-model绑定
<!-- 子组件 -->
<script setup>
const firstName = defineModel('firstName')
const lastName = defineModel('lastName')
</script>
<!-- 父组件 -->
<Child
v-model:firstName="first"
v-model:lastName="last"
/>
带默认值
<script setup>
const model = defineModel({
default: '默认值'
})
</script>
与组合式API结合
与watch结合
<script setup>
const model = defineModel()
watch(model, (newVal) => {
console.log('值变化:', newVal)
})
</script>
与自定义逻辑结合
<script setup>
const model = defineModel()
function reset() {
model.value = ''
}
</script>
实际应用示例
表单输入组件
<!-- TextInput.vue -->
<script setup>
const model = defineModel<string>({
default: '',
set(value) {
// 自动去除首尾空格
return value.trim()
}
})
</script>
<template>
<input
v-model="model"
class="input"
:placeholder="placeholder"
>
</template>
自定义选择器
<!-- CustomSelect.vue -->
<script setup>
const model = defineModel<any>()
const options = [
{ value: 'opt1', label: '选项1' },
{ value: 'opt2', label: '选项2' }
]
</script>
<template>
<select v-model="model">
<option
v-for="opt in options"
:value="opt.value"
>
{{ opt.label }}
</option>
</select>
</template>
适用场景
✅ 需要 父子组件双向绑定(替代 v-model + emit)
✅ 简化 props + emit 的代码
❌ 仅适用于 Vue 3.4+ 版本