Vue 3响应式原理深入解析:从Proxy到Composition API

Vue 3的发布带来了许多激动人心的特性,其中最核心的变化之一就是全新的响应式系统。本文将深入探讨Vue 3响应式系统的工作原理,从底层实现到上层应用。

Vue 2 vs Vue 3响应式系统对比

Vue 2的Object.defineProperty

Vue 2使用Object.defineProperty来实现响应式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function defineReactive(obj, key, value) {
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get() {
      // 依赖收集
      return value
    },
    set(newValue) {
      // 派发更新
      value = newValue
    }
  })
}

限制:

  • 无法检测数组索引和长度的变化
  • 无法检测对象属性的添加或删除
  • 深层嵌套对象需要递归遍历,性能开销大

Vue 3的Proxy

Vue 3使用ES6的Proxy来重写响应式系统:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function reactive(target) {
  return new Proxy(target, {
    get(target, key, receiver) {
      const result = Reflect.get(target, key, receiver)
      // 依赖收集
      track(target, key)
      return result
    },
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver)
      // 派发更新
      trigger(target, key)
      return result
    }
  })
}

优势:

  • 可以拦截所有类型的对象操作
  • 无需预先遍历所有属性
  • 支持数组、Map、Set等集合类型
  • 性能更优,按需代理

响应式核心实现

1. 依赖收集(Dependency Collection)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 全局依赖收集栈
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 deps = depsMap.get(key)
  if (!deps) {
    depsMap.set(key, (deps = new Set()))
  }

  deps.add(activeEffect)
}

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 deps = depsMap.get(key)
  if (deps) {
    deps.forEach(effect => {
      effect()
    })
  }
}

3. Effect副作用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function effect(fn, options = {}) {
  const effect = () => {
    try {
      activeEffect = effect
      return fn()
    } finally {
      activeEffect = null
    }
  }

  effect()
  return effect
}

Ref和Reactive的区别

ref的实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class RefImpl {
  constructor(value) {
    this._value = value
    this._rawValue = value
    this._v_isRef = true
  }

  get value() {
    track(this, 'value')
    return this._value
  }

  set value(newVal) {
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = newVal
      trigger(this, 'value')
    }
  }
}

function ref(value) {
  return new RefImpl(value)
}

reactive的实现

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function reactive(target) {
  if (!isObject(target)) {
    return target
  }

  return createReactiveObject(target)
}

function createReactiveObject(target) {
  if (!Object.isExtensible(target)) {
    return target
  }

  const proxy = new Proxy(target, baseHandlers)
  return proxy
}

Composition API的设计思想

1. 函数式编程思想

Composition API采用了函数式编程的思想,将相关的逻辑组织在一起:

 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
// Vue 2 Options API
export default {
  data() {
    return {
      count: 0,
      name: 'Vue'
    }
  },
  methods: {
    increment() {
      this.count++
    }
  },
  computed: {
    doubleCount() {
      return this.count * 2
    }
  }
}

// Vue 3 Composition API
import { ref, computed } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const name = ref('Vue')

    const increment = () => count.value++
    const doubleCount = computed(() => count.value * 2)

    return {
      count,
      name,
      increment,
      doubleCount
    }
  }
}

2. 更好的逻辑复用

Composition API让逻辑复用变得更加简单:

 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
// 自定义Hook
function useCounter(initialValue = 0) {
  const count = ref(initialValue)

  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue

  return {
    count,
    increment,
    decrement,
    reset
  }
}

// 在组件中使用
export default {
  setup() {
    const { count, increment } = useCounter(10)

    return {
      count,
      increment
    }
  }
}

性能优化策略

1. 懒响应式(Lazy Reactivity)

1
2
3
4
5
6
7
8
9
// 只有在访问时才会创建代理
function shallowReactive(target) {
  return createReactiveObject(target, true)
}

// 只对第一层属性进行响应式处理
function readonly(target) {
  return createReactiveObject(target, false, true)
}

2. 批量更新优化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 将多个更新合并为一个批次
const queue = []
let isFlushing = false

function queueJob(job) {
  if (!queue.includes(job)) {
    queue.push(job)
  }

  if (!isFlushing) {
    isFlushing = true
    Promise.resolve().then(flushJobs)
  }
}

function flushJobs() {
  for (let i = 0; i < queue.length; i++) {
    queue[i]()
  }
  queue.length = 0
  isFlushing = false
}

实际应用场景

1. 复杂表单的状态管理

 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
import { reactive, computed } from 'vue'

export function useFormState(initialState) {
  const state = reactive(initialState)

  const errors = reactive({})

  const validate = () => {
    // 验证逻辑
    Object.keys(state).forEach(key => {
      if (!state[key]) {
        errors[key] = `${key} is required`
      } else {
        delete errors[key]
      }
    })

    return Object.keys(errors).length === 0
  }

  return {
    state,
    errors,
    validate
  }
}

2. 数据获取和缓存

 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
import { ref, reactive } from 'vue'

export function useApi(url) {
  const data = ref(null)
  const loading = ref(false)
  const error = ref(null)
  const cache = reactive(new Map())

  const fetch = async () => {
    if (cache.has(url)) {
      data.value = cache.get(url)
      return
    }

    loading.value = true
    error.value = null

    try {
      const response = await fetch(url)
      const result = await response.json()
      data.value = result
      cache.set(url, result)
    } catch (err) {
      error.value = err.message
    } finally {
      loading.value = false
    }
  }

  return {
    data,
    loading,
    error,
    fetch
  }
}

总结

Vue 3的响应式系统通过Proxy带来了以下改进:

  1. 更好的性能:按需代理,避免了不必要的性能开销
  2. 更强的功能:支持更多数据类型和操作
  3. 更简洁的API:Composition API提供了更灵活的代码组织方式
  4. 更好的TypeScript支持:类型推断更加准确

理解Vue 3响应式原理不仅能帮助我们更好地使用Vue,也能启发我们在日常开发中设计出更优秀的响应式系统。