引言

Vue 3.5带来了更强大的响应式系统,相比Vue 2的Object.defineProperty,Vue 3全面采用ES6 Proxy实现响应式。这一改变不仅解决了Vue 2的诸多限制,还提供了更好的性能和更灵活的API设计。本文将深入剖析Vue 3.5响应式系统的实现原理和使用技巧。

一、Proxy基础与响应式原理

1.1 为什么选择Proxy

Vue 2的局限性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Vue 2 - Object.defineProperty的限制
const vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})

// ❌ 无法检测数组索引直接赋值
vm.items[0] = 'd' // 不会触发更新

// ❌ 无法检测数组长度变化
vm.items.length = 0 // 不会触发更新

// ❌ 无法检测新增属性
vm.newProp = 'value' // 不会触发更新

Vue 3 Proxy的优势

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Vue 3 - Proxy完美解决
import { reactive } from 'vue'

const state = reactive({
  items: ['a', 'b', 'c']
})

// ✅ 支持数组索引赋值
state.items[0] = 'd' // 触发更新

// ✅ 支持数组长度变化
state.items.length = 0 // 触发更新

// ✅ 支持动态属性(虽然需通过响应式API)
state.newProp = 'value' // 通过响应式API会正确追踪

1.2 Proxy核心拦截操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// 简化版响应式实现原理
function reactive(obj) {
  return new Proxy(obj, {
    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 hadKey = key in target
      const result = Reflect.deleteProperty(target, key)

      if (hadKey) {
        trigger(target, key)
      }
      return result
    },

    has(target, key) {
      track(target, key)
      return Reflect.has(target, key)
    },

    ownKeys(target) {
      track(target, ITERATE_KEY)
      return Reflect.ownKeys(target)
    }
  })
}

1.3 Proxy可拦截的13种操作

拦截器触发条件响应式应用
get读取属性依赖收集
set设置属性触发更新
deleteProperty删除属性触发更新
hasin操作符依赖收集
ownKeysObject.keys等迭代追踪
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor依赖收集
definePropertyObject.defineProperty触发更新
getPrototypeOfObject.getPrototypeOf依赖收集
setPrototypeOfObject.setPrototypeOf触发更新
apply函数调用响应式函数
constructnew操作符响应式类

二、Reactivity API详解

2.1 reactive()深度剖析

基本用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import { reactive } from 'vue'

// 创建响应式对象
const state = reactive({
  count: 0,
  user: {
    name: 'Alice',
    age: 25
  }
})

// 嵌套对象自动转为响应式
state.user.name = 'Bob' // ✅ 触发更新

响应式转换限制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ❌ 不支持原始值
const count = reactive(0) // 警告:无法将原始值转为响应式

// ❌ 解构失去响应性
const { count } = state
count++ // 不会触发更新

// ✅ 使用toRefs解构
import { toRefs } from 'vue'
const { count: countRef } = toRefs(state)
countRef.value++ // ✅ 触发更新

2.2 ref()使用场景

ref的本质

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ref实现原理(简化版)
function ref(value) {
  const refObject = {
    get value() {
      track(refObject, 'value')
      return _value
    },
    set value(newValue) {
      _value = newValue
      trigger(refObject, 'value')
    }
  }
  return refObject
}

最佳实践

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import { ref, reactive } from 'vue'

// 场景1:原始值响应式
const count = ref(0)
const message = ref('Hello')

// 场景2:单一对象替代reactive
const user = ref({
  name: 'Alice',
  age: 25
})

// 场景3:模板中自动解包
// <template>{{ count }}</template> // 无需.value

// 场景4: reactive对象中的ref自动解包
const state = reactive({
  count: ref(0)
})
console.log(state.count) // ✅ 直接访问,无需.value

2.3 computed计算属性

缓存机制原理

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

const fullName = computed({
  get() {
    console.log('计算fullName')
    return firstName.value + ' ' + lastName.value
  },
  set(newValue) {
    [firstName.value, lastName.value] = newValue.split(' ')
  }
})

// 首次访问:计算并缓存
console.log(fullName.value) // "计算fullName" "John Doe"

// 再次访问:使用缓存
console.log(fullName.value) // "John Doe" (无日志)

// 依赖变化:重新计算
firstName.value = 'Jane'
console.log(fullName.value) // "计算fullName" "Jane Doe"

可写计算属性

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const count = ref(1)

const double = computed({
  get() {
    return count.value * 2
  },
  set(value) {
    count.value = value / 2
  }
})

double.value = 10
console.log(count.value) // 5

2.4 watch与watchEffect

watchEffect自动追踪

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import { ref, watchEffect } from 'vue'

const count = ref(0)
const doubled = ref(0)

// 自动追踪内部使用的响应式数据
watchEffect(() => {
  doubled.value = count.value * 2
})

count.value++ // 自动触发effect

watch精确监听

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import { ref, watch } from 'vue'

const count = ref(0)
const count2 = ref(0)

// 监听单个源
watch(count, (newValue, oldValue) => {
  console.log(`count: ${oldValue} -> ${newValue}`)
})

// 监听多个源
watch([count, count2], ([newCount, newCount2], [oldCount, oldCount2]) => {
  console.log('多个值变化')
})

// 监听getter函数
watch(
  () => count.value * 2,
  (value) => {
    console.log('double:', value)
  }
)

// 深度监听对象
const state = reactive({ count: 0 })
watch(
  () => state,
  (newValue) => {
    console.log('state changed')
  },
  { deep: true }
)

三、依赖收集与触发机制

3.1 effect与track

effect副作用函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// 全局activeEffect栈
let activeEffect = null
const effectStack = []

function effect(fn) {
  const effectFn = () => {
    try {
      effectStack.push(effectFn)
      activeEffect = effectFn
      return fn()
    } finally {
      effectStack.pop()
      activeEffect = effectStack[effectStack.length - 1]
    }
  }
  effectFn()
  return effectFn
}

依赖收集track

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// targetMap结构:WeakMap<target, Map<key, Set<effect>>>
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)
}

3.2 trigger触发更新

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function trigger(target, key) {
  const depsMap = targetMap.get(target)
  if (!depsMap) return

  const dep = depsMap.get(key)
  if (!dep) return

  // 复制并执行所有effect
  const effects = [...dep]
  effects.forEach(effect => effect())
}

3.3 完整流程示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 1. 创建响应式对象
const state = reactive({ count: 0 })

// 2. 创建副作用
effect(() => {
  console.log('count is:', state.count)
  // 读取state.count时触发track
  // track(state, 'count') -> 将当前effect加入依赖
})

// 3. 修改值
state.count++
// set拦截器触发trigger
// trigger(state, 'count') -> 执行所有依赖的effect

四、响应式进阶API

4.1 toRefs与toRef

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { reactive, toRefs, toRef } from 'vue'

const state = reactive({
  count: 0,
  message: 'Hello'
})

// 解构整个对象为refs
const { count, message } = toRefs(state)

// 解构单个属性
const countRef = toRef(state, 'count')

// 用于函数返回响应式对象
function useFeature() {
  const state = reactive({
    count: 0,
    loading: false
  })
  return toRefs(state) // 保持响应性
}

const { count, loading } = useFeature()

4.2 readonly与shallowReadonly

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { reactive, readonly, shallowReadonly } from 'vue'

const original = reactive({
  count: 0,
  nested: {
    value: 1
  }
})

// 深度只读
const copy = readonly(original)
copy.count++ // ⚠️ 警告:正在修改只读属性

// 浅度只读(顶层只读,嵌套可修改)
const shallow = shallowReadonly(original)
shallow.count++ // ⚠️ 警告
shallow.nested.value++ // ✅ 不报警告

4.3 shallowReactive与markRaw

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import { reactive, shallowReactive, markRaw } from 'vue'

// 浅度响应式(只有顶层是响应式)
const shallow = shallowReactive({
  count: 0,
  nested: {
    value: 1
  }
})

shallow.count++ // ✅ 触发更新
shallow.nested.value++ // ❌ 不触发更新

// 标记对象永不转为响应式
const foo = markRaw({
  value: 1
})

const state = reactive({
  foo // 不会被转为响应式
})

4.4 customRef自定义响应式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { customRef } from 'vue'

// 带防抖的ref
function useDebouncedRef(value, delay = 200) {
  let timeout
  return customRef((track, trigger) => {
    return {
      get() {
        track()
        return value
      },
      set(newValue) {
        clearTimeout(timeout)
        timeout = setTimeout(() => {
          value = newValue
          trigger()
        }, delay)
      }
    }
  })
}

const search = useDebouncedRef('')
search.value = 'test' // 200ms后才触发更新

五、性能优化最佳实践

5.1 避免不必要的响应式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { ref, shallowRef, markRaw } from 'vue'

// 大数据使用shallowRef
const hugeList = shallowRef([])

// 更新时触发更新
hugeList.value = newList

// 静态配置使用markRaw
const staticConfig = markRaw({
  apiKey: 'xxx',
  endpoint: 'https://api.example.com'
})

const state = reactive({
  config: staticConfig // 不会转为响应式
})

5.2 v-memo优化

1
2
3
4
5
6
<template>
  <!-- 仅在item.id变化时重新渲染 -->
  <div v-for="item in list" :key="item.id" v-memo="[item.id]">
    {{ item.name }}
  </div>
</template>

5.3 计算属性缓存

1
2
3
4
5
6
7
8
9
// ✅ 使用computed缓存
const filteredList = computed(() => {
  return list.value.filter(item => item.active)
})

// ❌ 避免在方法中重复计算
function getFilteredList() {
  return list.value.filter(item => item.active) // 每次调用都重新计算
}

六、调试技巧

6.1 devtools插件

1
2
3
4
5
6
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

app.config.devtools = true

6.2 onTrack与onTrigger

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import { ref, watchEffect } from 'vue'

watchEffect(
  () => {
    console.log('effect执行')
  },
  {
    onTrack({ target, key, type }) {
      console.log('追踪:', target, key, type)
    },
    onTrigger({ target, key, type, oldValue, newValue }) {
      console.log('触发:', target, key, type, oldValue, newValue)
    }
  }
)

总结

Vue 3.5的响应式系统通过Proxy和精心设计的API提供了强大的响应式能力:

  1. Proxy优于Object.defineProperty:更全面的拦截能力,更好的性能
  2. reactive与ref各司其职:对象用reactive,原始值用ref
  3. computed自动缓存:减少不必要的计算
  4. watch精确控制:灵活的监听选项
  5. 性能优化关键:合理使用shallow系列API和markRaw

掌握这些原理和技巧,将帮助您构建更高效、更可维护的Vue应用。

相关工具推荐