响应式系统 #

一、响应式概述 #

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