响应式系统 #
一、响应式概述 #
Vue的响应式系统是其核心特性之一,当数据变化时,视图会自动更新。
1.1 响应式工作流程 #
text
数据变化 → 触发Setter → 通知依赖 → 更新视图
↑ │
└────── 依赖收集 ←─── 渲染组件 ←┘
1.2 Vue 2 vs Vue 3 #
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 实现方式 | Object.defineProperty | Proxy |
| 数组监听 | 需要特殊处理 | 原生支持 |
| 新增属性 | 需要$set | 自动响应 |
| 性能 | 较好 | 更优 |
二、Vue 2响应式原理 #
2.1 Object.defineProperty #
javascript
// 简化版实现
function defineReactive(obj, key, val) {
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
// 依赖收集
if (Dep.target) {
dep.addSub(Dep.target)
}
return val
},
set(newVal) {
if (newVal === val) return
val = newVal
// 通知更新
dep.notify()
}
})
}
// 依赖管理器
class Dep {
constructor() {
this.subs = []
}
addSub(sub) {
this.subs.push(sub)
}
notify() {
this.subs.forEach(sub => sub.update())
}
}
2.2 Vue 2的局限性 #
javascript
// Vue 2无法检测的变化
// 1. 对象属性的添加/删除
this.obj.newProp = 'value' // 不是响应式的
// 2. 通过索引修改数组
this.arr[0] = 'new value' // 不是响应式的
// 3. 修改数组长度
this.arr.length = 0 // 不是响应式的
// 解决方案
this.$set(this.obj, 'newProp', 'value')
this.$set(this.arr, 0, 'new value')
this.arr.splice(0)
三、Vue 3响应式原理 #
3.1 Proxy实现 #
javascript
// 简化版实现
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
// 依赖收集
track(target, key)
const result = Reflect.get(target, key, receiver)
// 深层响应式
if (typeof result === 'object' && result !== null) {
return reactive(result)
}
return result
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
if (oldValue !== value) {
// 触发更新
trigger(target, key)
}
return result
},
deleteProperty(target, key) {
const result = Reflect.deleteProperty(target, key)
trigger(target, key)
return result
}
})
}
3.2 Proxy的优势 #
javascript
// Vue 3可以检测的变化
// 1. 对象属性的添加/删除
state.newProp = 'value' // 响应式的
delete state.prop // 响应式的
// 2. 通过索引修改数组
state.arr[0] = 'new value' // 响应式的
// 3. 修改数组长度
state.arr.length = 0 // 响应式的
// 4. Map/Set的操作
state.map.set('key', 'value') // 响应式的
state.set.add('item') // 响应式的
四、依赖收集与触发 #
4.1 依赖管理 #
javascript
// 当前活跃的effect
let activeEffect = null
// 依赖映射表
const targetMap = new WeakMap()
// 收集依赖
function track(target, key) {
if (!activeEffect) return
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
dep.add(activeEffect)
}
// 触发更新
function trigger(target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) return
const dep = depsMap.get(key)
if (dep) {
dep.forEach(effect => effect())
}
}
4.2 Effect函数 #
javascript
// effect栈
const effectStack = []
function effect(fn) {
const effectFn = () => {
activeEffect = effectFn
effectStack.push(effectFn)
try {
fn()
} finally {
effectStack.pop()
activeEffect = effectStack[effectStack.length - 1]
}
}
effectFn()
return effectFn
}
// 使用示例
const state = reactive({ count: 0 })
effect(() => {
console.log('count changed:', state.count)
})
state.count++ // 自动触发effect
五、ref与reactive #
5.1 ref的实现 #
javascript
function ref(value) {
return {
__v_isRef: true,
_value: value,
get value() {
track(this, 'value')
return this._value
},
set value(newVal) {
if (newVal !== this._value) {
this._value = newVal
trigger(this, 'value')
}
}
}
}
5.2 reactive的实现 #
javascript
const reactiveMap = new WeakMap()
function reactive(target) {
if (reactiveMap.has(target)) {
return reactiveMap.get(target)
}
const proxy = new Proxy(target, {
get(target, key, receiver) {
if (key === '__v_isReactive') {
return true
}
track(target, key)
const result = Reflect.get(target, key, receiver)
if (typeof result === 'object' && result !== null) {
return reactive(result)
}
return result
},
set(target, key, value, receiver) {
const oldValue = target[key]
const result = Reflect.set(target, key, value, receiver)
const hadKey = Object.prototype.hasOwnProperty.call(target, key)
if (!hadKey) {
// 新增属性
trigger(target, key, 'ADD')
} else if (oldValue !== value) {
// 修改属性
trigger(target, key, 'SET')
}
return result
}
})
reactiveMap.set(target, proxy)
return proxy
}
六、响应式API详解 #
6.1 reactive #
vue
<script setup>
import { reactive, isReactive } from 'vue'
// 创建响应式对象
const state = reactive({
count: 0,
user: {
name: '张三',
profile: {
age: 25
}
}
})
// 深层响应式
state.user.profile.age = 26 // 响应式的
// 检查是否为响应式
console.log(isReactive(state)) // true
// 注意事项
const original = { count: 0 }
const proxy = reactive(original)
console.log(proxy === original) // false
</script>
6.2 ref #
vue
<script setup>
import { ref, isRef, unref } from 'vue'
// 创建响应式引用
const count = ref(0)
const user = ref({ name: '张三' })
// 访问值需要.value
console.log(count.value) // 0
count.value++
// 对象也会被reactive处理
user.value.name = '李四'
// 检查是否为ref
console.log(isRef(count)) // true
// 获取值(如果是ref则返回.value,否则返回本身)
console.log(unref(count)) // 0
</script>
6.3 shallowReactive #
vue
<script setup>
import { shallowReactive } from 'vue'
// 浅层响应式
const state = shallowReactive({
count: 0,
nested: {
value: 1
}
})
state.count++ // 响应式的
state.nested.value++ // 不是响应式的
</script>
6.4 shallowRef #
vue
<script setup>
import { shallowRef, triggerRef } from 'vue'
// 浅层ref
const state = shallowRef({ count: 0 })
// 不会触发更新
state.value.count++
// 整体替换会触发更新
state.value = { count: 1 }
// 手动触发更新
state.value.count++
triggerRef(state)
</script>
6.5 readonly #
vue
<script setup>
import { reactive, readonly, isReadonly } from 'vue'
const original = reactive({ count: 0 })
const copy = readonly(original)
// 无法修改
copy.count++ // 警告
// 原始对象修改会反映到只读对象
original.count++
console.log(copy.count) // 1
// 检查是否只读
console.log(isReadonly(copy)) // true
</script>
6.6 toRef和toRefs #
vue
<script setup>
import { reactive, toRef, toRefs } from 'vue'
const state = reactive({
count: 0,
name: '张三'
})
// toRef - 为单个属性创建ref
const countRef = toRef(state, 'count')
countRef.value++ // 同步更新state.count
// toRefs - 为所有属性创建ref
const { count, name } = toRefs(state)
count.value++ // 同步更新state.count
</script>
七、响应式工具函数 #
7.1 isProxy #
javascript
import { reactive, ref, isProxy } from 'vue'
const state = reactive({})
const count = ref(0)
console.log(isProxy(state)) // true
console.log(isProxy(count)) // false
7.2 toRaw #
javascript
import { reactive, toRaw } from 'vue'
const state = reactive({ count: 0 })
const raw = toRaw(state)
console.log(raw === state) // false
raw.count++ // 不会触发更新
7.3 markRaw #
javascript
import { reactive, markRaw } from 'vue'
const obj = markRaw({ count: 0 })
const state = reactive({ obj })
// obj永远不会被转换为响应式
八、响应式丢失问题 #
8.1 解构导致丢失 #
vue
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0, name: '张三' })
// ❌ 解构会丢失响应性
let { count, name } = state
count++ // 不会触发更新
// ✅ 使用toRefs
import { toRefs } from 'vue'
const { count, name } = toRefs(state)
count.value++ // 会触发更新
</script>
8.2 传递参数丢失 #
vue
<script setup>
import { reactive } from 'vue'
const state = reactive({ count: 0 })
// ❌ 直接传递会丢失响应性
function useCount(count) {
// count不再是响应式的
}
// ✅ 传递整个对象
function useCount(state) {
// state仍然是响应式的
}
// ✅ 使用computed返回
import { computed } from 'vue'
const count = computed(() => state.count)
</script>
九、最佳实践 #
9.1 选择ref还是reactive #
vue
<script setup>
import { ref, reactive } from 'vue'
// 使用ref的场景
const count = ref(0) // 基本类型
const user = ref(null) // 需要整体替换的对象
const list = ref([]) // 需要整体替换的数组
// 使用reactive的场景
const form = reactive({ // 表单对象
username: '',
password: ''
})
const state = reactive({ // 复杂状态对象
user: { name: '' },
settings: { theme: 'dark' }
})
</script>
9.2 避免响应式陷阱 #
vue
<script setup>
import { reactive, ref, toRaw } from 'vue'
// ❌ 直接修改reactive对象的引用
let state = reactive({ count: 0 })
state = { count: 1 } // 丢失响应性
// ✅ 修改属性
state.count = 1
// ✅ 使用ref整体替换
const stateRef = ref({ count: 0 })
stateRef.value = { count: 1 }
// ❌ 在reactive中使用ref
const state = reactive({
count: ref(0) // 不需要这样
})
// ✅ 直接使用值
const state = reactive({
count: 0
})
</script>
十、总结 #
响应式API对比 #
| API | 用途 | 特点 |
|---|---|---|
reactive |
对象响应式 | 深层响应,返回Proxy |
ref |
任意值响应式 | 需要.value访问 |
shallowReactive |
浅层响应式 | 只有顶层响应 |
shallowRef |
浅层ref | 只有.value替换响应 |
readonly |
只读响应式 | 不可修改 |
toRef |
属性转ref | 保持响应连接 |
toRefs |
对象解构 | 保持响应连接 |
响应式要点:
- Vue 3使用Proxy实现响应式
- ref适合基本类型和需要整体替换的场景
- reactive适合复杂对象
- 解构reactive对象会丢失响应性
- 使用toRefs保持解构后的响应性
最后更新:2026-03-26