【VUE】ref 和 reactive 详解及使用场景
ref
和 reactive
详解及使用场景
在 Vue 3 的 Composition API 中,ref
和 reactive
是两种最常用的响应式数据声明方式,但它们的使用场景和特性有所不同。
1. ref
作用
- 用于声明一个响应式的基本类型(如
number
、string
、boolean
)或引用类型(如object
、array
)。 - 返回一个可变的 ref 对象,其值存储在
.value
属性中。
语法
import { ref } from 'vue'
const count = ref(0) // 基本类型
const user = ref({ name: 'Alice' }) // 引用类型
- 访问/修改值:
console.log(count.value) // 0 count.value++ // 修改值
特点
.value
访问:在<script>
中必须用.value
访问,但在<template>
中自动解包(无需.value
)。- 适用于所有数据类型(基本类型 + 引用类型)。
- 可以重新赋值(
ref
本身是可变的)。
使用场景
- 基本类型数据(如
boolean
、number
、string
)。 - 需要重新赋值的变量(如切换状态、计数器)。
- 模板直接使用的变量(自动解包)。
示例
<script setup>
import { ref } from 'vue'
const isVisible = ref(false) // 布尔值
const count = ref(0) // 数字
function toggle() {
isVisible.value = !isVisible.value
count.value++
}
</script>
<template>
<button @click="toggle">
{{ isVisible ? 'Hide' : 'Show' }} ({{ count }})
</button>
</template>
2. reactive
作用
- 用于声明一个响应式的对象或数组(仅适用于引用类型)。
- 返回一个 Proxy 代理对象,可以直接修改属性(不需要
.value
)。
语法
import { reactive } from 'vue'
const state = reactive({
name: 'Alice',
age: 25,
hobbies: ['reading', 'coding']
})
- 访问/修改值:
console.log(state.name) // 'Alice' state.age = 26 // 直接修改属性
特点
- 无需
.value
:直接访问属性(比ref
更简洁)。 - 仅适用于对象/数组(不能用于基本类型)。
- 不能重新赋值(
reactive
返回的对象是固定的,不能state = {...}
)。
使用场景
- 复杂对象或表单数据(如
form
、filter
数据)。 - 嵌套数据结构的响应式管理(如 API 返回的 JSON 数据)。
- 需要直接修改属性的情况(避免
.value
的繁琐)。
示例
<script setup>
import { reactive } from 'vue'
const form = reactive({
username: '',
password: '',
remember: false
})
function submit() {
console.log(form.username, form.password)
}
</script>
<template>
<input v-model="form.username" placeholder="Username" />
<input v-model="form.password" placeholder="Password" />
<button @click="submit">Submit</button>
</template>
3. ref
vs reactive
对比
特性 | ref |
reactive |
---|---|---|
适用数据类型 | 基本类型 + 引用类型 | 仅对象/数组 |
访问方式 | .value (<script> ) |
直接访问 |
模板自动解包 | ✅(无需 .value ) |
✅(直接访问) |
重新赋值 | ✅(ref.value = ... ) |
❌(不能 obj = {...} ) |
适用场景 | 基本类型、需要重新赋值的变量 | 复杂对象、表单数据 |
4. 如何选择?
-
用
ref
的情况:- 基本类型(
boolean
、number
、string
)。 - 需要重新赋值的变量(如
isLoading = true
→isLoading = false
)。 - 在
<template>
中直接使用(自动解包)。
- 基本类型(
-
用
reactive
的情况:- 对象或数组(如
form
、filter
数据)。 - 需要直接修改属性(避免
.value
)。 - 嵌套数据结构(如 API 返回的 JSON)。
- 对象或数组(如
总结
ref
:适用于基本类型或需要重新赋值的变量(如isVisible
、count
)。reactive
:适用于复杂对象或表单数据(如form
、filterValue
)。- 组合使用:Vue 3 推荐混合使用,例如:
const loading = ref(false) // 布尔值 const user = reactive({ name: 'Alice', age: 25 }) // 对象
Vue 3 响应式 API 综合对比(defineModel
、defineEmits
、ref
、reactive
)
为了更清晰地区分这四种 API,我们从 用途、语法、适用场景、核心区别 四个方面进行对比,并给出记忆技巧。
1. 核心功能对比
API | 作用 | 数据类型 | 是否双向绑定 | 是否替代传统写法 |
---|---|---|---|---|
defineModel |
声明 v-model 绑定的数据 |
任意 | ✅(自动同步父组件) | ✅(替代 props + emit('update:xxx') ) |
defineEmits |
声明自定义事件 | - | ❌ | ✅(替代 emits 选项) |
ref |
声明响应式数据(基本/引用类型) | 任意 | ❌(需手动 .value ) |
❌(Vue 2 无直接对应) |
reactive |
声明响应式对象/数组 | 仅对象/数组 | ❌ | ❌(Vue 2 类似 data() ) |
2. 语法对比
(1)defineModel
const modelValue = defineModel() // 默认 `modelValue`
const username = defineModel("username", { default: "Guest" }) // 自定义 prop 名
- 修改数据:直接赋值(自动同步父组件)
username.value = "Alice" // 触发父组件的 `v-model:username` 更新
(2)defineEmits
const emit = defineEmits(["submit", "cancel"])
- 触发事件:
emit("submit", { data: 123 }) // 父组件监听 @submit
(3)ref
const count = ref(0) // 基本类型
const user = ref({ name: "Alice" }) // 引用类型
- 访问/修改:
console.log(count.value) // 0 count.value++ // 修改
(4)reactive
const state = reactive({ name: "Alice", age: 25 })
- 访问/修改:
console.log(state.name) // "Alice" state.age = 26 // 直接修改属性
3. 适用场景对比
API | 典型使用场景 |
---|---|
defineModel |
父子组件双向绑定(如自定义输入框、开关组件) |
defineEmits |
子组件向父组件通信(如提交表单、关闭弹窗) |
ref |
基本类型数据(boolean /number /string )或需要重新赋值的变量 |
reactive |
复杂对象或表单数据(如 form 、filter 数据) |
4. 核心区别总结
特性 | defineModel |
defineEmits |
ref |
reactive |
---|---|---|---|---|
是否响应式 | ✅ | ❌ | ✅ | ✅ |
是否需要 .value |
✅(<script> 中) |
❌ | ✅(<script> 中) |
❌ |
是否支持基本类型 | ✅ | ❌ | ✅ | ❌ |
是否支持重新赋值 | ✅ | ❌ | ✅ | ❌(需用 Object.assign ) |
是否自动同步父组件 | ✅ | ❌ | ❌ | ❌ |
5. 记忆技巧
(1)defineModel
vs defineEmits
defineModel
:双向数据流(父子组件数据同步)。- 记忆:
v-model
的简化版。
- 记忆:
defineEmits
:单向事件流(子组件通知父组件)。- 记忆:替代
this.$emit()
。
- 记忆:替代
(2)ref
vs reactive
ref |
reactive |
|
---|---|---|
数据类型 | 所有类型 | 仅对象/数组 |
访问方式 | .value |
直接属性访问 |
重新赋值 | ✅(ref.value = ... ) |
❌(不能 obj = {...} ) |
适用场景 | 基本类型、模板变量 | 复杂对象、表单数据 |
记忆口诀:
- “基本用
ref
,对象用reactive
” - “要改
.value
,ref
不能少”
6. 综合示例
<script setup>
// 1. 双向绑定(defineModel)
const username = defineModel("username", { default: "Guest" })
// 2. 事件通信(defineEmits)
const emit = defineEmits(["submit"])
// 3. 基本类型(ref)
const isLoading = ref(false)
// 4. 复杂对象(reactive)
const form = reactive({
email: "",
password: ""
})
function handleSubmit() {
emit("submit", { username: username.value, ...form })
isLoading.value = true
}
</script>
<template>
<input v-model="username" />
<input v-model="form.email" />
<button @click="handleSubmit" :disabled="isLoading">
{{ isLoading ? "Submitting..." : "Submit" }}
</button>
</template>
总结
defineModel
:简化v-model
双向绑定。defineEmits
:声明子组件事件。ref
:管理基本类型或需要重新赋值的变量。reactive
:管理复杂对象或表单数据。
一句话记忆:
“双向用 defineModel
,事件用 defineEmits
,简单用 ref
,复杂用 reactive
” 🚀