第201集:Vue 3响应式系统源码深度解析
概述
在本集中,我们将深入剖析Vue 3响应式系统的源码实现,理解其核心原理和设计思想。Vue 3响应式系统是基于ES6 Proxy实现的,相比Vue 2的Object.defineProperty有了显著的改进和优化。
响应式系统核心架构
Vue 3响应式系统主要包含以下核心模块:
- 响应式核心:基于Proxy的响应式代理实现
- 依赖收集:Track(追踪)机制
- 依赖触发:Trigger(触发)机制
- 副作用处理:Effect(副作用)系统
- 响应式工具:ref、reactive、computed、watch等API
源码目录结构
Vue 3响应式系统的源码主要位于packages/reactivity/src/目录下:
packages/reactivity/src/
├── index.ts # 入口文件,导出所有API
├── reactive.ts # 响应式核心实现
├── ref.ts # Ref实现
├── computed.ts # 计算属性实现
├── watcher.ts # 观察者实现
├── effect.ts # 副作用系统
├── track.ts # 依赖收集
├── trigger.ts # 依赖触发
├── baseHandlers.ts # 基础处理器
├── collectionHandlers.ts # 集合类型处理器
├── utils.ts # 工具函数
└── constants.ts # 常量定义核心源码解析
1. 响应式核心:Proxy实现
让我们首先看一下reactive.ts中的核心实现:
// packages/reactivity/src/reactive.ts
import { mutableHandlers } from './baseHandlers'
import { isObject } from './utils'
// 存储响应式对象的WeakMap,避免重复代理
const reactiveMap = new WeakMap<any, any>()
/**
* 创建响应式对象
* @param target 要代理的目标对象
* @returns 响应式代理对象
*/
export function reactive(target: object) {
// 如果不是对象或者是只读对象,直接返回
if (!isObject(target) || (target as any)[ReactiveFlags.IS_READONLY]) {
return target
}
// 如果已经是响应式对象,直接返回
if ((target as any)[ReactiveFlags.IS_REACTIVE]) {
return target
}
// 检查是否已经有代理对象,有则直接返回
const existingProxy = reactiveMap.get(target)
if (existingProxy) {
return existingProxy
}
// 创建代理对象
const proxy = new Proxy(target, mutableHandlers)
// 缓存代理对象
reactiveMap.set(target, proxy)
return proxy
}2. 基础处理器:mutableHandlers
baseHandlers.ts中定义了Proxy的各种处理器,包括get、set、deleteProperty等:
// packages/reactivity/src/baseHandlers.ts
import { track, trigger } from './effect'
import { reactive, toRaw } from './reactive'
import { isObject } from './utils'
// 拦截get操作
export const get = createGetter()
// 拦截set操作
export const set = createSetter()
// 拦截delete操作
export const deleteProperty = createDeleteProperty()
// 创建get处理器
function createGetter(isReadonly = false, shallow = false) {
return function get(target: object, key: string | symbol, receiver: object) {
// 处理特殊key,如__v_isReactive
const res = Reflect.get(target, key, receiver)
// 非只读情况下,进行依赖收集
if (!isReadonly) {
track(target, TrackOpTypes.GET, key)
}
// 如果是对象,递归创建响应式代理
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
// 创建set处理器
function createSetter(shallow = false) {
return function set(target: object, key: string | symbol, value: unknown, receiver: object): boolean {
const oldValue = (target as any)[key]
const hadKey = hasOwn(target, key)
// 执行set操作
const result = Reflect.set(target, key, value, receiver)
// 如果目标对象是响应式对象的原型,不触发更新
if (target === toRaw(receiver)) {
// 新增属性
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
}
// 修改属性
else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}3. 依赖收集:Track机制
effect.ts中的track函数负责依赖收集:
// packages/reactivity/src/effect.ts
import { activeEffect, ReactiveEffect } from './effect'
/**
* 依赖收集
* @param target 目标对象
* @param type 操作类型
* @param key 属性键
*/
export function track(target: object, type: TrackOpTypes, key: unknown) {
// 如果没有活跃的副作用或者不是追踪模式,直接返回
if (!shouldTrack || activeEffect === undefined) {
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 = createDep()))
}
// 将当前活跃的副作用添加到依赖集合中
trackEffects(dep)
}
/**
* 追踪副作用
* @param dep 依赖集合
*/
export function trackEffects(dep: Dep) {
// 如果当前副作用已经在依赖集合中,直接返回
if (dep.has(activeEffect!)) {
return
}
// 添加副作用到依赖集合
dep.add(activeEffect!)
// 副作用添加依赖集合
activeEffect!.deps.push(dep)
}4. 依赖触发:Trigger机制
trigger函数负责触发依赖更新:
// packages/reactivity/src/effect.ts
/**
* 依赖触发
* @param target 目标对象
* @param type 操作类型
* @param key 属性键
* @param newValue 新值
* @param oldValue 旧值
* @param oldTarget 旧目标
*/
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
// 获取目标对象的依赖映射
const depsMap = targetMap.get(target)
if (!depsMap) {
// 没有依赖,直接返回
return
}
// 收集需要触发的副作用
const effects = new Set<ReactiveEffect>()
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
// 根据操作类型处理不同的依赖
if (type === TriggerOpTypes.CLEAR) {
// 清除操作,触发所有依赖
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
// 数组长度变化,处理特殊情况
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// 处理单个属性
if (key !== void 0) {
add(depsMap.get(key))
}
// 根据操作类型处理其他依赖
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
} else if (isIntegerKey(key)) {
// 数组新增元素,触发length依赖
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
case TriggerOpTypes.SET:
// 修改操作,对于Map类型需要特殊处理
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
// 触发所有收集到的副作用
const run = (effect: ReactiveEffect) => {
scheduleRun(effect)
}
effects.forEach(run)
}5. 副作用系统:Effect实现
Effect是响应式系统的核心,负责管理副作用函数:
// packages/reactivity/src/effect.ts
/**
* 副作用类
*/
export class ReactiveEffect<T = any> {
// 副作用函数
fn: () => T
// 依赖数组
deps: Dep[] = []
// 是否允许递归
allowRecurse?: boolean
// 调度器
scheduler?: EffectScheduler
// 是否激活
active = true
// 父副作用
parent: ReactiveEffect | undefined = undefined
constructor(
fn: () => T,
scheduler?: EffectScheduler,
scope?: EffectScope | null
) {
this.fn = fn
this.scheduler = scheduler
recordEffectScope(this, scope)
}
/**
* 运行副作用
*/
run() {
// 如果副作用未激活,直接执行函数
if (!this.active) {
return this.fn()
}
let parent: ReactiveEffect | undefined = activeEffect
let lastShouldTrack = shouldTrack
// 处理嵌套副作用
while (parent) {
if (parent === this) {
return
}
parent = parent.parent
}
try {
// 设置父副作用
this.parent = activeEffect
// 激活副作用
activeEffect = this
// 开启追踪
shouldTrack = true
// 清理之前的依赖
cleanupEffect(this)
// 执行副作用函数
return this.fn()
} finally {
// 恢复之前的状态
activeEffect = this.parent
shouldTrack = lastShouldTrack
this.parent = undefined
}
}
/**
* 停止副作用
*/
stop() {
if (this.active) {
// 清理依赖
cleanupEffect(this)
// 标记为非激活
this.active = false
}
}
}
/**
* 创建副作用
* @param fn 副作用函数
* @param options 选项
* @returns 副作用实例
*/
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions
): ReactiveEffectRunner {
// 如果已经是副作用函数,直接使用其原始函数
if ((fn as ReactiveEffectRunner).effect) {
fn = (fn as ReactiveEffectRunner).effect!.fn
}
// 创建副作用实例
const _effect = new ReactiveEffect(fn)
// 应用选项
if (options) {
extend(_effect, options)
if (options.scope) {
recordEffectScope(_effect, options.scope)
}
}
// 运行副作用
_effect.run()
// 返回runner函数
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}Ref实现源码
ref.ts实现了Ref API,用于处理基本类型的响应式:
// packages/reactivity/src/ref.ts
/**
* Ref类型接口
*/
export interface Ref<T = any> {
value: T
}
/**
* 创建Ref
* @param value 初始值
* @returns Ref对象
*/
export function ref<T extends object>(value: T): ToRef<T>
export function ref<T>(value: T): Ref<T>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
return createRef(value, false)
}
/**
* 创建Ref实例
* @param rawValue 原始值
* @param shallow 是否浅层
* @returns Ref对象
*/
function createRef(rawValue: unknown, shallow: boolean) {
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
/**
* Ref实现类
*/
class RefImpl<T> {
// 依赖集合
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
// 保存原始值
this._rawValue = __v_isShallow ? value : toRaw(value)
// 转换值(如果是对象,创建响应式代理)
this._value = __v_isShallow ? value : convert(value)
}
/**
* 获取值,触发依赖收集
*/
get value() {
trackRefValue(this)
return this._value
}
/**
* 设置值,触发依赖更新
*/
set value(newVal) {
const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
// 如果值发生变化
if (hasChanged(newVal, this._rawValue)) {
// 更新原始值
this._rawValue = newVal
// 更新值
this._value = useDirectValue ? newVal : convert(newVal)
// 触发依赖
triggerRefValue(this, newVal)
}
}
}Computed实现源码
computed.ts实现了计算属性,具有缓存机制:
// packages/reactivity/src/computed.ts
/**
* 创建计算属性
* @param getterOrOptions getter函数或选项对象
* @returns 计算属性Ref
*/
export function computed<T>(
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
): ComputedRef<T> {
let getter: ComputedGetter<T>
let setter: ComputedSetter<T>
// 处理选项
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
setter = () => {
console.warn('Write operation failed: computed value is readonly')
}
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
// 创建计算属性实例
return new ComputedRefImpl(getter, setter, isFunction(getterOrOptions) || !getterOrOptions.set)
}
/**
* 计算属性实现类
*/
class ComputedRefImpl<T> {
private _value!: T
private _dirty = true
public dep?: Dep = undefined
public readonly effect: ReactiveEffect<T>
public readonly __v_isRef = true
public readonly [ReactiveFlags.IS_READONLY]: boolean
constructor(
getter: ComputedGetter<T>,
private readonly _setter: ComputedSetter<T>,
isReadonly: boolean
) {
// 创建副作用,用于追踪依赖
this.effect = new ReactiveEffect(getter, () => {
if (!this._dirty) {
this._dirty = true
// 依赖变化,触发更新
triggerRefValue(this)
}
})
this.effect.computed = this
this.effect.active = true
this[ReactiveFlags.IS_READONLY] = isReadonly
}
/**
* 获取计算属性值
*/
get value() {
// 触发依赖收集
trackRefValue(this)
// 如果值脏了,重新计算
if (this._dirty) {
this._dirty = false
this._value = this.effect.run()!
}
return this._value
}
/**
* 设置计算属性值
*/
set value(newValue: T) {
this._setter(newValue)
}
}响应式系统工作流程
现在我们可以总结Vue 3响应式系统的完整工作流程:
- 创建响应式对象:通过
reactive或ref创建响应式代理 - 依赖收集:
- 当访问响应式对象的属性时,触发
get处理器 get处理器调用track函数进行依赖收集track函数将当前活跃的副作用添加到依赖集合中
- 当访问响应式对象的属性时,触发
- 依赖触发:
- 当修改响应式对象的属性时,触发
set处理器 set处理器调用trigger函数触发依赖更新trigger函数遍历并执行所有相关的副作用
- 当修改响应式对象的属性时,触发
- 副作用执行:
- 副作用函数执行,可能会访问其他响应式对象
- 重复依赖收集过程,形成依赖关系网
性能优化
Vue 3响应式系统相比Vue 2有以下性能优化:
- 基于Proxy:可以拦截所有对象操作,包括新增属性、删除属性等
- 惰性依赖收集:只收集实际使用的属性依赖
- WeakMap缓存:避免重复创建响应式代理
- 副作用清理机制:自动清理不再使用的依赖
- 更精确的依赖触发:只触发相关的副作用,避免不必要的更新
实际应用场景
了解响应式系统源码可以帮助我们:
- 更好地使用响应式API:理解API的设计意图和使用场景
- 排查响应式相关问题:快速定位和解决响应式数据不更新等问题
- 优化性能:根据响应式系统的工作原理,写出更高效的代码
- 扩展响应式系统:自定义响应式行为,满足特殊需求
总结
本集深入剖析了Vue 3响应式系统的源码实现,从核心架构到具体模块,详细讲解了其工作原理和设计思想。理解响应式系统的源码对于掌握Vue 3至关重要,可以帮助我们更好地使用Vue 3,写出高效、可靠的代码。
在后续的源码解析系列中,我们将继续深入探讨Vue 3的其他核心模块,包括编译原理、虚拟DOM、组件系统等。