第201集:Vue 3响应式系统源码分析

概述

Vue 3的响应式系统是其核心特性之一,采用了ES6的Proxy API来实现,相比Vue 2的Object.defineProperty具有更强大的功能和更好的性能。本集将深入分析Vue 3响应式系统的源码实现,包括核心概念、关键类和函数、以及响应式系统的工作原理。

核心概念

1. 响应式基础

  • 响应式数据:当数据变化时,依赖该数据的组件会自动更新
  • 依赖收集:跟踪哪些组件依赖于哪些数据
  • 触发更新:当数据变化时,通知所有依赖该数据的组件更新

2. 关键类型

  • Ref:用于包装基本类型数据的响应式对象
  • Reactive:用于包装对象类型数据的响应式代理
  • Computed:用于计算属性的响应式对象
  • Watch:用于监听数据变化的响应式机制

响应式系统架构

Vue 3的响应式系统主要包含以下几个核心模块:

  1. 响应式核心reactive.ts - 实现reactive、ref、computed等核心API
  2. 依赖收集effect.ts - 实现依赖收集和触发更新
  3. 响应式工具baseHandlers.ts - 实现Proxy的各种处理器
  4. 响应式辅助ref.ts - 实现Ref类型的响应式
  5. 计算属性computed.ts - 实现Computed类型的响应式

核心源码分析

1. Reactive API实现

// reactive.ts 核心实现
import { mutableHandlers } from './baseHandlers'
import { isObject } from '@vue/shared'

// 存储响应式代理的WeakMap
const reactiveMap = new WeakMap<any, any>()

// reactive函数:创建响应式代理
export function reactive(target: object) {
  // 如果target不是对象,直接返回
  if (!isObject(target)) {
    return target
  }

  // 如果target已经是响应式代理,直接返回
  if (target.__v_isReactive) {
    return target
  }

  // 检查是否已有对应的响应式代理
  const existingProxy = reactiveMap.get(target)
  if (existingProxy) {
    return existingProxy
  }

  // 创建响应式代理
  const proxy = new Proxy(target, mutableHandlers)
  
  // 缓存代理对象
  reactiveMap.set(target, proxy)
  
  // 标记为响应式对象
  proxy.__v_isReactive = true
  
  return proxy
}

2. Proxy处理器实现

// baseHandlers.ts 核心实现
import { track, trigger } from './effect'
import { isObject } from '@vue/shared'

// mutableHandlers:可变对象的处理器
export const mutableHandlers = {
  // 获取属性值时触发
  get(target: object, key: string | symbol, receiver: object) {
    // 处理特殊情况
    if (key === '__v_isReactive') {
      return true
    }
    
    // 追踪依赖
    track(target, 'get', key)
    
    // 获取原始值
    const res = Reflect.get(target, key, receiver)
    
    // 如果获取的是对象,递归创建响应式
    if (isObject(res)) {
      return reactive(res)
    }
    
    return res
  },
  
  // 设置属性值时触发
  set(target: object, key: string | symbol, value: any, receiver: object) {
    // 获取旧值
    const oldValue = (target as any)[key]
    
    // 设置新值
    const result = Reflect.set(target, key, value, receiver)
    
    // 如果值发生变化,触发更新
    if (oldValue !== value) {
      trigger(target, 'set', key, value, oldValue)
    }
    
    return result
  },
  
  // 删除属性时触发
  deleteProperty(target: object, key: string | symbol) {
    // 检查属性是否存在
    const hadKey = hasOwn(target, key)
    // 删除属性
    const result = Reflect.deleteProperty(target, key)
    
    // 如果属性存在且删除成功,触发更新
    if (hadKey && result) {
      trigger(target, 'delete', key)
    }
    
    return result
  },
  
  // 检查属性是否存在时触发
  has(target: object, key: string | symbol) {
    const result = Reflect.has(target, key)
    // 追踪依赖
    track(target, 'has', key)
    return result
  },
  
  // 获取属性描述符时触发
  getOwnPropertyDescriptor(target: object, key: string | symbol) {
    return Reflect.getOwnPropertyDescriptor(target, key)
  }
}

3. 依赖收集与触发更新

// effect.ts 核心实现

// 存储当前活跃的副作用函数
let activeEffect: ReactiveEffect | undefined

// 依赖映射:target -> key -> effects
const targetMap = new WeakMap<any, Map<string | symbol, Set<ReactiveEffect>>>()

// 副作用类
class ReactiveEffect {
  constructor(
    public fn: () => any,
    public scheduler?: EffectScheduler
  ) {}
  
  // 运行副作用函数
  run() {
    // 保存当前副作用函数
    activeEffect = this
    
    try {
      // 执行副作用函数,触发依赖收集
      return this.fn()
    } finally {
      // 清除当前副作用函数
      activeEffect = undefined
    }
  }
}

// 追踪依赖
export function track(target: object, type: TrackOpTypes, key: string | symbol) {
  // 如果没有活跃的副作用函数,直接返回
  if (!activeEffect) {
    return
  }
  
  // 获取target对应的依赖映射
  let depsMap = targetMap.get(target)
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  
  // 获取key对应的副作用集合
  let dep = depsMap.get(key)
  if (!dep) {
    depsMap.set(key, (dep = new Set()))
  }
  
  // 如果副作用函数不在集合中,添加进去
  if (!dep.has(activeEffect)) {
    dep.add(activeEffect)
  }
}

// 触发更新
export function trigger(target: object, type: TriggerOpTypes, key?: string | symbol, newValue?: any, oldValue?: any) {
  // 获取target对应的依赖映射
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    return
  }
  
  // 收集需要触发的副作用函数
  const effects = new Set<ReactiveEffect>()
  
  // 根据操作类型,获取对应的副作用函数
  if (key !== undefined) {
    const dep = depsMap.get(key)
    if (dep) {
      dep.forEach(effect => effects.add(effect))
    }
  }
  
  // 触发所有副作用函数
  effects.forEach(effect => {
    if (effect.scheduler) {
      // 如果有调度器,使用调度器触发
      effect.scheduler()
    } else {
      // 否则直接运行副作用函数
      effect.run()
    }
  })
}

// watchEffect API
export function watchEffect(fn: () => any) {
  const effect = new ReactiveEffect(fn)
  effect.run()
  return () => effect.stop()
}

4. Ref API实现

// ref.ts 核心实现
import { track, trigger } from './effect'
import { isObject } from '@vue/shared'
import { reactive } from './reactive'

// Ref类型接口
interface Ref<T = any> {
  value: T
}

// RefImpl类:实现Ref类型
class RefImpl<T> implements Ref<T> {
  private _value: T
  private _rawValue: T
  
  constructor(value: T) {
    // 保存原始值
    this._rawValue = value
    // 如果是对象,创建响应式代理
    this._value = isObject(value) ? reactive(value) : value
  }
  
  // getter:获取值时追踪依赖
  get value() {
    track(this, 'get', 'value')
    return this._value
  }
  
  // setter:设置值时触发更新
  set value(newValue: T) {
    if (newValue !== this._rawValue) {
      // 更新原始值
      this._rawValue = newValue
      // 如果是对象,创建响应式代理
      this._value = isObject(newValue) ? reactive(newValue) : newValue
      // 触发更新
      trigger(this, 'set', 'value', newValue)
    }
  }
}

// ref API
export function ref<T>(value: T): Ref<T> {
  return new RefImpl(value)
}

5. Computed API实现

// computed.ts 核心实现
import { ReactiveEffect } from './effect'

// ComputedRef类型接口
interface ComputedRef<T = any> {
  value: T
}

// ComputedRefImpl类:实现Computed类型
class ComputedRefImpl<T> implements ComputedRef<T> {
  private _value: T
  private _dirty = true
  private _effect: ReactiveEffect<T>
  
  constructor(getter: () => T) {
    // 创建副作用函数
    this._effect = new ReactiveEffect(getter, () => {
      // 调度器:当依赖变化时,标记为脏
      if (!this._dirty) {
        this._dirty = true
        // 触发更新
        trigger(this, 'set', 'value')
      }
    })
  }
  
  // getter:获取计算值
  get value() {
    // 如果是脏的,重新计算
    if (this._dirty) {
      this._value = this._effect.run()
      this._dirty = false
    }
    // 追踪依赖
    track(this, 'get', 'value')
    return this._value
  }
}

// computed API
export function computed<T>(getter: () => T): ComputedRef<T> {
  return new ComputedRefImpl(getter)
}

响应式系统工作流程

1. 依赖收集流程

  1. 当组件渲染时,会创建一个副作用函数
  2. 副作用函数执行时,会访问组件依赖的响应式数据
  3. 访问响应式数据时,会触发Proxy的get处理器
  4. get处理器调用track函数,将当前副作用函数添加到依赖集合中
  5. 依赖集合存储在targetMap中,格式为:target -> key -> effects

2. 触发更新流程

  1. 当响应式数据发生变化时,会触发Proxy的set处理器
  2. set处理器调用trigger函数,获取该数据的所有依赖副作用函数
  3. 遍历副作用函数集合,执行每个副作用函数
  4. 副作用函数重新执行,导致组件重新渲染

响应式系统优化

Vue 3的响应式系统相比Vue 2有以下优化:

  1. 使用Proxy API:相比Object.defineProperty,Proxy可以监听更多的对象操作,包括新增属性、删除属性、数组索引变化等
  2. 懒依赖收集:只有当响应式数据被实际访问时,才会进行依赖收集
  3. 更细粒度的更新:可以精确到对象的某个属性,而不是整个对象
  4. 支持Set和Map:可以监听Set和Map类型数据的变化
  5. 更好的性能:Proxy的性能比Object.defineProperty更好,尤其是在大型对象上

实际应用场景

1. 组件中的响应式数据

<template>
  <div>
    <h1>{{ title }}</h1>
    <p>{{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script setup lang="ts">
import { ref, computed } from 'vue'

// 使用ref创建基本类型的响应式数据
const count = ref(0)

// 使用computed创建计算属性
const title = computed(() => `Count: ${count.value}`)

// 点击事件处理函数
const increment = () => {
  count.value++
}
</script>

2. 复杂对象的响应式

import { reactive } from 'vue'

// 创建复杂对象的响应式代理
const user = reactive({
  name: 'John',
  age: 30,
  address: {
    city: 'New York',
    zip: '10001'
  },
  hobbies: ['reading', 'coding']
})

// 直接修改嵌套属性,会触发响应式更新
user.address.city = 'London'

// 添加新属性,会触发响应式更新
user.email = 'john@example.com'

// 删除属性,会触发响应式更新
delete user.age

调试响应式系统

Vue 3提供了一些调试工具来帮助开发者调试响应式系统:

  1. isReactive:检查对象是否是响应式代理
  2. isRef:检查对象是否是Ref对象
  3. isComputed:检查对象是否是Computed对象
  4. toRaw:获取响应式代理的原始对象
  5. markRaw:标记对象为非响应式
import { reactive, isReactive, toRaw, markRaw } from 'vue'

const obj = { name: 'John' }
const reactiveObj = reactive(obj)

console.log(isReactive(reactiveObj)) // true
console.log(isReactive(obj)) // false

console.log(toRaw(reactiveObj) === obj) // true

const nonReactiveObj = markRaw({ name: 'Jane' })
const reactiveNonReactiveObj = reactive(nonReactiveObj)
console.log(reactiveNonReactiveObj === nonReactiveObj) // true

总结

Vue 3的响应式系统是基于ES6 Proxy API实现的,具有强大的功能和良好的性能。通过深入分析其源码,我们可以了解到:

  1. 响应式系统的核心是依赖收集和触发更新
  2. Reactive API使用Proxy创建响应式代理
  3. Ref API用于包装基本类型数据的响应式
  4. Computed API用于实现计算属性的响应式
  5. Watch API用于监听数据变化

理解Vue 3响应式系统的源码实现,有助于我们更好地使用Vue 3进行开发,并在遇到问题时能够快速定位和解决。

扩展阅读

  1. Vue 3官方文档 - 响应式系统
  2. Vue 3源码解析 - 响应式系统
  3. ES6 Proxy API文档
  4. Vue 3响应式系统原理
« 上一篇 Vue 3 运维自动化脚本:提高运维效率与可靠性 下一篇 » Vue 3 响应式系统源码深度解析:核心原理与实现