【Vue3源码解析】响应式原理
源码环境搭建
【Vue3源码解析】应用实例创建及页面渲染-CSDN博客
写文章时的Vue 版本:
"version": "3.5.13",
针对单个包进行开发环境打包、测试。
pnpm run dev reactivity
reactive 创建响应式对象
packages/reactivity/src/reactive.ts
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target
}
/**
* 通过调用 createReactiveObject 函数,创建一个响应式对象,并返回该对象。
* 第一个参数是 目标对象
* 第二个参数用来判断该对象是否是只读的
* mutableHandlers 是一个对象,包含了响应式对象的一些操作方法,如get、set等。
* mutableCollectionHandlers 是一个对象,包含了响应式集合对象的一些操作方法,如get、set等。
* reactiveMap 是一个全局 WeakMap 对象,用于缓存 target 到 响应式对象的映射关系。
*/
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
if (!isObject(target)) {
if (__DEV__) {
warn(
`value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(
target,
)}`,
)
}
return target
}
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
// 如果已经是 Proxy 代理对象,则直接返回
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// target already has corresponding Proxy
// 如果 ProxyMap 中有 target 对应的 proxy 对象 则直接返回
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// only specific value types can be observed.
// 类型无效 则直接返回 target
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 创建 target 对应的 Proxy 代理对象 并传入 baseHandlers
const proxy = new Proxy(
target,
// COLLECTION => Map/Set/WeakMap/WeakSet
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
}
effect 和它的函数参数fn的作用
packages/reactivity/src/effect.ts
/**
* 创建一个响应式副作用函数
*
* 此函数用于追踪函数内的响应式数据访问和修改,以便在数据变化时自动重新执行该函数
* 它是响应式系统的核心部分,使得开发者可以轻松创建响应式的应用程序
*
* @param fn 要追踪的副作用函数,它会接收一个用于触发副作用的函数作为参数
* @param options 可选的副作用选项,允许开发者自定义副作用的行为,例如是否应该停止追踪等
* @returns 返回一个绑定了副作用函数的runner函数,用于手动触发副作用
*/
export function effect<T = any>(
fn: () => T,
// 可以由用户自定义,用于定义副作用effect的行为,例如shouldTrack、onTrack、onTrigger等
// 这样可以覆盖ReactiveEffect的默认scheduler而执行自己的scheduler
options?: ReactiveEffectOptions,
): ReactiveEffectRunner<T> {
// 检查传入的函数是否已经是一个ReactiveEffect的副作用函数,如果是,则使用其内部的副作用函数
if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
// 调用一次effect函数,根据传入的fn创建一个新的ReactiveEffect实例对象:_effect
// 一个fn 对应一个 _effect对象
// fn 同时成为_effect 对象的fn属性(形成闭包)
/**
* effect(fn)
* fn.effect => ReactiveEffect对象
* ReactiveEffect对象.fn => fn
*/
// 创建一个新的ReactiveEffect实例
/**
* 当内部执行scheduler的时候,它会回头调用effect的run,·而run方法内部会调用fn
* 意味着:scheduler()=>run()=>fn()
* 如何执行:那么之后我们如果想要重新执行fn函数,·只需要执行scheduler就可以了
*/
const e = new ReactiveEffect(fn)
// 如果提供了用户自定义的配置,则将配置合并到副作用实例中
if (options) {
extend(e, options)
}
// 尝试运行副作用函数,如果在执行过程中抛出错误,则停止追踪并重新抛出错误
try {
e.run()
} catch (err) {
e.stop()
throw err
}
// 创建并返回一个副作用函数的runner,它绑定了当前的副作用实例
const runner = e.run.bind(e) as ReactiveEffectRunner
runner.effect = e
// 返回副作用函数的runner,允许手动触发副作用 e.run()=> fn()
return runner
}
/**
* 执行当前的副作用函数
*
* 此函数负责执行副作用操作,并在执行前后处理一些清理和准备依赖项的工作
* 它还管理副作用的执行状态,确保在执行过程中不会被中断,并在完成后清理依赖项
*
* @returns 返回副作用函数的执行结果
*/
run(): T {
// TODO cleanupEffect
// 检查副作用是否处于非激活状态,如果是,则直接执行副作用函数
if (!(this.flags & EffectFlags.ACTIVE)) {
// stopped during cleanup
return this.fn()
}
// 设置当前副作用为正在运行状态
this.flags |= EffectFlags.RUNNING
// 执行清理上一次执行的副作用
cleanupEffect(this)
// 准备依赖项收集
prepareDeps(this)
// 保存当前正在执行的副作用和是否应该跟踪的全局状态
const prevEffect = activeSub
const prevShouldTrack = shouldTrack
// 设置当前执行的副作用和全局跟踪状态
activeSub = this
shouldTrack = true
try {
// 执行副作用函数
return this.fn()
} finally {
// 在开发环境下,确保当前执行的副作用被正确恢复
if (__DEV__ && activeSub !== this) {
warn(
'Active effect was not restored correctly - ' +
'this is likely a Vue internal bug.',
)
}
// 清理当前执行的副作用的依赖项
cleanupDeps(this)
// 恢复之前保存的正在执行的副作用和是否应该跟踪的全局状态
activeSub = prevEffect
shouldTrack = prevShouldTrack
// 清除当前副作用的正在运行状态
this.flags &= ~EffectFlags.RUNNING
}
}
执行fn后响应式变量依赖的收集和触发
Vue3.4 版本之前最后的一层,也就是 nameDepMap 这一层是 Set 结构,但是从 3.4 版本开始,变成了 Map ,用来记录 track 的 id,然而Vue3.5对该部分又进行了重构,不再记录id,只能说学的速度永远跟不上技术更新的速度~
packages/reactivity/src/baseHandlers.ts
/**
* BaseReactiveHandler 类实现了 ProxyHandler 接口,用于处理响应式对象的代理操作。
* 它主要负责拦截和处理对响应式对象的访问和操作,根据是否只读和是否浅层次来决定如何处理这些操作。
*
* @param _isReadonly 是否只读,如果为 true,则该响应式对象不可被修改。
* @param _isShallow 是否浅层次,如果为 true,则仅对外层属性进行响应式处理,不递归处理内部属性。
*/
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false,
protected readonly _isShallow = false,
) {}
/**
* 拦截对目标对象属性的获取操作。
* 此方法负责处理各种情况下的属性获取,包括处理特殊的 ReactiveFlags、数组的特殊情况、
* 属性的追踪、以及返回值的处理等。
*
* @param target 目标对象,即被代理的响应式对象。
* @param key 要访问的属性名。
* @param receiver 代理对象或继承链上的其他对象。
* @returns 属性值或处理后的值。
*/
get(target: Target, key: string | symbol, receiver: object): any {
// 处理特殊的 ReactiveFlags 属性
if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]
const isReadonly = this._isReadonly,
isShallow = this._isShallow
// 根据不同的 ReactiveFlags 返回相应的值
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
} else if (key === ReactiveFlags.IS_SHALLOW) {
return isShallow
} else if (key === ReactiveFlags.RAW) {
if (
receiver ===
(isReadonly
? isShallow
? shallowReadonlyMap
: readonlyMap
: isShallow
? shallowReactiveMap
: reactiveMap
).get(target) ||
// receiver is not the reactive proxy, but has the same prototype
// this means the receiver is a user proxy of the reactive proxy
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
) {
return target
}
// early return undefined
return
}
const targetIsArray = isArray(target)
// 处理非只读情况下的特殊属性和方法
if (!isReadonly) {
let fn: Function | undefined
if (targetIsArray && (fn = arrayInstrumentations[key])) {
return fn
}
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}
// 获取属性值,考虑内置符号和非跟踪键的情况
const res = Reflect.get(
target,
key,
// if this is a proxy wrapping a ref, return methods using the raw ref
// as receiver so that we don't have to call `toRaw` on the ref in all
// its class methods
isRef(target) ? target : receiver,
)
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res
}
// 在非只读情况下,跟踪属性的访问
if (!isReadonly) {
// 依赖追踪
track(target, TrackOpTypes.GET, key)
}
// 根据是否浅层次和返回值的类型,决定是否需要进一步处理返回值
if (isShallow) {
return res
}
if (isRef(res)) {
// ref unwrapping - skip unwrap for Array + integer key.
return targetIsArray && isIntegerKey(key) ? res : res.value
}
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
class MutableReactiveHandler extends BaseReactiveHandler {
constructor(isShallow = false) {
super(false, isShallow)
}
/**
* 设置一个响应式对象的属性。
*
* 该方法重写了 `Proxy` 对象的默认 `set` 行为,实现了自定义的属性设置逻辑。
* 它处理了各种情况,例如浅层响应式、只读属性和 ref 解包,以确保响应式系统的正确行为。
*
* @param target - 被代理的原始对象。
* @param key - 要设置的属性的名称或符号。
* @param value - 属性的新值。
* @param receiver - 代理对象本身。
* @returns 返回设置属性的结果。
*/
set(
target: Record<string | symbol, unknown>,
key: string | symbol,
value: unknown,
receiver: object,
): boolean {
let oldValue = target[key]
// 如果不是浅层响应式模式,需要进一步处理属性和值。
if (!this._isShallow) {
const isOldValueReadonly = isReadonly(oldValue)
// 如果新旧值都不是浅层或只读的,将它们转换为原始状态。
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
// 如果目标不是数组且旧值是 ref 而新值不是 ref,需要特殊处理。
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
// 如果旧值是只读的,属性不能被设置。
if (isOldValueReadonly) {
return false
} else {
// 否则,直接设置旧值 ref 的值。
oldValue.value = value
return true
}
}
} else {
// 在浅层模式下,对象按原样设置,无论是否是响应式的。
}
// 确定键是否已经存在。
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
// 使用 Reflect.set 尝试设置属性。
const result = Reflect.set(
target,
key,
value,
isRef(target) ? target : 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
}
deleteProperty(
target: Record<string | symbol, unknown>,
key: string | symbol,
): boolean {
const hadKey = hasOwn(target, key)
const oldValue = target[key]
const result = Reflect.deleteProperty(target, key)
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
has(target: Record<string | symbol, unknown>, key: string | symbol): boolean {
const result = Reflect.has(target, key)
if (!isSymbol(key) || !builtInSymbols.has(key)) {
track(target, TrackOpTypes.HAS, key)
}
return result
}
ownKeys(target: Record<string | symbol, unknown>): (string | symbol)[] {
track(
target,
TrackOpTypes.ITERATE,
isArray(target) ? 'length' : ITERATE_KEY,
)
return Reflect.ownKeys(target)
}
}
packages/reactivity/src/dep.ts
/**
* 跟踪对响应式属性的访问。
*
* 该函数会检查当前正在运行的效果(effect),并将其记录为依赖项(dep),
* 这些依赖项记录了所有依赖于该响应式属性的效果。
*
* @param target - 包含响应式属性的对象。
* @param type - 定义对响应式属性的访问类型。
* @param key - 要跟踪的响应式属性的标识符。
*/
export function track(target: object, type: TrackOpTypes, key: unknown): void {
// 仅在启用了跟踪且存在活动效果时进行跟踪。activeSub在3.4版本及之前为activeEffect
if (shouldTrack && activeSub) {
// 获取目标对象的依赖映射,如果不存在则创建一个新的映射。
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 Dep()))
dep.map = depsMap
dep.key = key
}
// 在开发模式下记录依赖项,并包含详细信息。
if (__DEV__) {
dep.track({
target,
type,
key,
})
} else {
dep.track()
}
}
}
/**
* 查找与目标对象(或特定属性)关联的所有依赖项,并触发存储在其中的效果。
*
* @param target - 响应式对象。
* @param type - 定义需要触发效果的操作类型。
* @param key - 可用于指定目标对象中的特定响应式属性。
* @param newValue - 新值,用于某些操作类型(如 SET)。
* @param oldValue - 旧值,用于某些操作类型(如 SET)。
* @param oldTarget - 旧的目标对象,用于某些操作类型(如 CLEAR)。
*/
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>,
): void {
const depsMap = targetMap.get(target)
if (!depsMap) {
// 从未被追踪过
globalVersion++
return
}
const run = (dep: Dep | undefined) => {
if (dep) {
if (__DEV__) {
dep.trigger({
target,
type,
key,
newValue,
oldValue,
oldTarget,
})
} else {
dep.trigger()
}
}
}
startBatch()
if (type === TriggerOpTypes.CLEAR) {
// 集合被清除
// 触发目标对象的所有效果
depsMap.forEach(run)
} else {
const targetIsArray = isArray(target)
const isArrayIndex = targetIsArray && isIntegerKey(key)
if (targetIsArray && key === 'length') {
const newLength = Number(newValue)
depsMap.forEach((dep, key) => {
if (
key === 'length' ||
key === ARRAY_ITERATE_KEY ||
(!isSymbol(key) && key >= newLength)
) {
run(dep)
}
})
} else {
// 调度 SET | ADD | DELETE 的运行
if (key !== void 0 || depsMap.has(void 0)) {
run(depsMap.get(key))
}
// 调度 ARRAY_ITERATE 对于任何数字键的变化(长度已在上方处理)
if (isArrayIndex) {
run(depsMap.get(ARRAY_ITERATE_KEY))
}
// 也对 ADD | DELETE | Map.SET 运行迭代键
switch (type) {
case TriggerOpTypes.ADD:
if (!targetIsArray) {
run(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
run(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isArrayIndex) {
// 新索引添加到数组 -> 长度变化
run(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!targetIsArray) {
run(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
run(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
run(depsMap.get(ITERATE_KEY))
}
break
}
}
}
endBatch()
}
ITERATE_KEY))
}
} else if (isArrayIndex) {
// 新索引添加到数组 -> 长度变化
run(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!targetIsArray) {
run(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
run(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
run(depsMap.get(ITERATE_KEY))
}
break
}
}
}
endBatch()
}