Vue3 响应式 ref 和 reactive 原理详解及选择建议
核心概念理解
什么是响应式?
响应式就是当数据发生变化时,使用这个数据的地方会自动更新。
javascript
// 普通 JavaScript - 数据变化不会自动更新页面
let count = 0;
count = 1; // 页面不会自动更新
// Vue 响应式 - 数据变化会自动更新页面
const count = ref(0);
count.value = 1; // 页面会自动更新
ref 和 reactive 的本质区别
ref - 包装盒子原理
javascript
// ref 就像是给数据套了一个"包装盒子"
const count = ref(0);
// 实际结构:{ value: 0 }
const name = ref('Alice');
// 实际结构:{ value: 'Alice' }
reactive - 对象代理原理
javascript
// reactive 直接对对象进行"魔法改造"
const user = reactive({
name: 'Alice',
age: 25
});
// 直接修改属性:user.name = 'Bob' - 页面自动更新
详细用法对比
1. 基本数据类型
vue
<script setup>
import { ref } from 'vue'
// ref 可以包装任何类型的数据
const count = ref(0) // 数字
const name = ref('Alice') // 字符串
const isShow = ref(true) // 布尔值
const empty = ref(null) // null
// 修改值时必须通过 .value
console.log(count.value) // 读取:0
count.value = 10 // 修改:10
</script>
2. 对象类型
vue
<script setup>
import { ref, reactive } from 'vue'
// 用 ref 包装对象
const userRef = ref({
name: 'Alice',
age: 25
})
// 用 reactive 包装对象
const userReactive = reactive({
name: 'Bob',
age: 30
})
// 修改方式不同:
userRef.value.name = 'Charlie' // 需要 .value
userReactive.name = 'David' // 直接修改属性
</script>
完整示例对比
vue
<template>
<div>
<h2>ref 示例</h2>
<p>计数器: {{ count }}</p>
<p>用户名: {{ userInfo.name }}</p> <!-- 这里不可写作 userInfo.value.name -->
<button @click="increment">增加</button>
<button @click="changeUser">改变用户</button>
<h2>reactive 示例</h2>
<p>计数器: {{ state.count }}</p>
<p>用户名: {{ state.user.name }}</p>
<button @click="incrementReactive">增加</button>
<button @click="changeUserReactive">改变用户</button>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue'
// ===== ref 方式 =====
// 基本类型用 ref
const count = ref(0)
// 对象类型也可以用 ref
const userInfo = ref({
name: 'Alice',
age: 25
})
const increment = () => {
count.value++ // 注意:需要 .value
}
const changeUser = () => {
userInfo.value.name = 'Bob' // 修改对象属性也需要 .value
}
// ===== reactive 方式 =====
// reactive 只能用于对象
const state = reactive({
count: 0,
user: {
name: 'Charlie',
age: 30
}
})
const incrementReactive = () => {
state.count++ // 直接修改,不需要 .value
}
const changeUserReactive = () => {
state.user.name = 'David' // 直接修改属性
}
</script>
原理深入理解
ref 的实现原理(简化版)
javascript
// ref 的核心思想
function myRef(value) {
return {
_isRef: true, // 标识这是 ref
get value() {
track() // 依赖收集
return value
},
set value(newVal) {
value = newVal
trigger() // 触发更新
}
}
}
reactive 的实现原理(简化版)
javascript
// reactive 使用 Proxy 实现
function myReactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track() // 依赖收集
return Reflect.get(target, key, receiver)
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver)
trigger() // 触发更新
return result
}
})
}
使用场景推荐
什么时候用 ref?
vue
<script setup>
import { ref, computed } from 'vue'
// ✅ 推荐:基本数据类型
const count = ref(0)
const name = ref('Alice')
const isLoading = ref(false)
// ✅ 推荐:需要整体替换的对象
const formData = ref({
username: '',
password: ''
})
// 整体替换时 ref 更方便
const resetForm = () => {
formData.value = { // 直接替换整个对象
username: '',
password: ''
}
}
</script>
什么时候用 reactive?
vue
<script setup>
import { reactive } from 'vue'
// ✅ 推荐:复杂的嵌套对象
const state = reactive({
user: {
profile: {
name: 'Alice',
age: 25,
address: {
city: 'Beijing',
street: 'Main St'
}
}
},
ui: {
loading: false,
dialogVisible: false
}
})
// 修改嵌套属性更简洁
const updateUserCity = () => {
state.user.profile.address.city = 'Shanghai'
}
</script>
常见陷阱和注意事项
1. ref 的 .value 陷阱
vue
<script setup>
import { ref } from 'vue'
const count = ref(0)
// ❌ 错误:模板中不需要 .value
// <p>{{ count.value }}</p>
// ✅ 正确:模板中直接使用
// <p>{{ count }}</p>
// ✅ 正确:JavaScript 中需要 .value
const doubleCount = computed(() => count.value * 2)
const increment = () => count.value++
</script>
2. reactive 的替换陷阱
vue
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
// ❌ 错误:这样会破坏响应式
const resetState = () => {
state = { count: 0 } // 这样做会失去响应式
}
// ✅ 正确:修改属性
const resetState = () => {
state.count = 0
}
// ✅ 或者使用 Object.assign
const resetState = () => {
Object.assign(state, { count: 0 })
}
</script>
3. 解构的陷阱
vue
<script setup>
import { ref, reactive } from 'vue'
const count = ref(0)
const state = reactive({ name: 'Alice' })
// ❌ 错误:解构会丢失响应式
const { name } = state // name 不再是响应式的
// ✅ 正确:使用 toRefs
import { toRefs } from 'vue'
const { name } = toRefs(state) // name 仍然是响应式的
// ❌ 错误:ref 解构也需要小心
const { value } = count // value 不再是响应式的
</script>
总结
特性 | ref | reactive |
---|---|---|
数据类型 | 任何类型 | 只能是对象/数组 |
访问方式 | .value |
直接访问 |
模板使用 | 自动解包 | 直接使用 |
嵌套对象 | 需要多层 .value |
直接访问属性 |
替换对象 | 简单 | 需要特殊处理 |
简单记忆法则:
- 基本类型 → 用
ref
- 复杂对象 → 用
reactive
- 不确定 → 用
ref
(更安全)