响应式指的是,当数据发生变化时,视图会跟着变化。以下是以reactive 为例介绍响应式入口、依赖收集和派发更新的实现原理:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script type="module">
import {
reactive,
effect,
ref,
toRef,
toRefs,
computed,
} from "./reactivity.js";
const state = reactive({ count: 1 });
effect(() => {
document.querySelector("#app").innerHTML = state.count;
});
setTimeout(() => {
state.count++;
}, 1000);
</script>
</body>
</html>
以上例子中,定义响应式对象state,初次渲染count的值为0。因为是响应式,所以当点击修改数据进行state属性count的修改时,会触发试图的更新,数值从0到1、2、...依次递增。这里开始介绍响应式的机理。
reactive源码解析
export function reactive(target: object) {
// if trying to observe a readonly proxy, return the readonly version.
if (isReadonly(target)) {
return target //判断是否只读,只读不做处理
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap,
)
}
enum TargetType {
INVALID = 0,
COMMON = 1,
COLLECTION = 2,
}
function targetTypeMap(rawType: string) {
switch (rawType) {
case 'Object':
case 'Array':
return TargetType.COMMON
case 'Map':
case 'Set':
case 'WeakMap':
case 'WeakSet':
return TargetType.COLLECTION
default:
return TargetType.INVALID
}
}
function getTargetType(value: Target) {
return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
? TargetType.INVALID
: targetTypeMap(toRawType(value))
}
// 创建响应式对象
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>,
proxyMap: WeakMap<Target, any>,
) {
// 不是对象会直接返回
if (!isObject(target)) {
return target
}
// 如果已经是响应式对象也直接返回
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 在proxyMap中查找是否存在,如果存在直接返回
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 判断目标类型
// 当前target为Object 所以targetType = TargetType.COMMON
const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
return target
}
// 构建代理对象,通过`targetType === 2`确定当前例子中使用的是collectionHandlers作为handler
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
)
proxyMap.set(target, proxy)
return proxy
}
至此,reactive 函数执行完毕,state 得到了一个 proxy 的实例对象。接着又执行 effect 方法,该方法定义在 packages/reactivity/src/effect.ts 文件中:
export function effect<T = any>(
fn: () => T,
options?: ReactiveEffectOptions,
): ReactiveEffectRunner {
if ((fn as ReactiveEffectRunner).effect instanceof ReactiveEffect) {
fn = (fn as ReactiveEffectRunner).effect.fn
}
// 创建 ReactiveEffect 实例
const _effect = new ReactiveEffect(fn, NOOP, () => {
if (_effect.dirty) {
_effect.run()
}
})
if (options) {
extend(_effect, options)
if (options.scope) recordEffectScope(_effect, options.scope)
}
if (!options || !options.lazy) {
_effect.run()
}
const runner = _effect.run.bind(_effect) as ReactiveEffectRunner
runner.effect = _effect
return runner
}
该函数先声明一个构造函数 ReactiveEffect 的实例对象 _effect,然后执行构造函数中的 run 方法。我们先看下 ReactiveEffect 构造函数:
export enum DirtyLevels {
NotDirty = 0,
QueryingDirty = 1,
MaybeDirty_ComputedSideEffect = 2,
MaybeDirty = 3,
Dirty = 4,
}
function preCleanupEffect(effect: ReactiveEffect) {
effect._trackId++ // 每次执行id 都是+1, 如果当前同一个effect执行,id就是相同的
effect._depsLength = 0
}
// 对比更新的时候,需要删除多余的effect
// [flag,a,b,c]
// [flag] -> effect._depsLength = 1
function postCleanupEffect(effect: ReactiveEffect) {
if (effect.deps.length > effect._depsLength) {
for (let i = effect._depsLength; i < effect.deps.length; i++) {
cleanupDepEffect(effect.deps[i], effect)
}
effect.deps.length = effect._depsLength
}
}
export class ReactiveEffect<T = any> {
active = true
deps: Dep[] = []
/**
* Can be attached after creation
* @internal
*/
computed?: ComputedRefImpl<T>
allowRecurse?: boolean
onStop?: () => void
_dirtyLevel = DirtyLevels.Dirty
// 1._trackId 用于记录执行次数 (防止一个属性在当前effect中多次依赖收集) 只收集一次
// 2.拿到上一次依赖的最后一个和这次的比较
// {flag,age}
_trackId = 0
_runnings = 0
_shouldSchedule = false
_depsLength = 0
constructor(
public fn: () => T,
public trigger: () => void,
public scheduler?: EffectScheduler,
scope?: EffectScope,
) {
recordEffectScope(this, scope)
}
public get dirty() {
if (
this._dirtyLevel === DirtyLevels.MaybeDirty_ComputedSideEffect ||
this._dirtyLevel === DirtyLevels.MaybeDirty
) {
this._dirtyLevel = DirtyLevels.QueryingDirty
pauseTracking()
for (let i = 0; i < this._depsLength; i++) {
const dep = this.deps[i]
if (dep.computed) {
triggerComputed(dep.computed)
if (this._dirtyLevel >= DirtyLevels.Dirty) {
break
}
}
}
if (this._dirtyLevel === DirtyLevels.QueryingDirty) {
this._dirtyLevel = DirtyLevels.NotDirty
}
resetTracking()
}
return this._dirtyLevel >= DirtyLevels.Dirty
}
public set dirty(v) {
this._dirtyLevel = v ? DirtyLevels.Dirty : DirtyLevels.NotDirty
}
run() {
this._dirtyLevel = DirtyLevels.NotDirty
if (!this.active) {
return this.fn()
}
let lastShouldTrack = shouldTrack
let lastEffect = activeEffect // 记录上一次的副作用函数,避免副作用函数嵌套Bug
try {
shouldTrack = true
activeEffect = this
this._runnings++
preCleanupEffect(this) // 避免重复收集,effect重新执行前,需要将上一次的依赖情况 effect.deps
return this.fn()
} finally {
postCleanupEffect(this)
this._runnings--
activeEffect = lastEffect
shouldTrack = lastShouldTrack
}
}
stop() {
if (this.active) {
preCleanupEffect(this)
postCleanupEffect(this)
this.onStop && this.onStop()
this.active = false
}
}
}
通过以上代码可以推断出,例子中的const state = reactive({count: 0})执行后最终返回的是proxy代理对象,目标值target是{count: 0},handler是baseHandlers。
// 定义mutableHandlers(即baseHandlers)
export const mutableHandlers: ProxyHandler<object> =
/*#__PURE__*/ new MutableReactiveHandler()
// 所有ReactiveHandler继承BaseReactiveHandler类,默认实现依赖收集
class BaseReactiveHandler implements ProxyHandler<Target> {
constructor(
protected readonly _isReadonly = false,
protected readonly _isShallow = false,
) {}
get(target: Target, key: string | symbol, receiver: object) {
const isReadonly = this._isReadonly,
isShallow = this._isShallow
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) {
if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
if (key === 'hasOwnProperty') {
return hasOwnProperty
}
}
const res = Reflect.get(target, key, 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)
}
set(
target: object,
key: string | symbol,
value: unknown,
receiver: object,
): boolean {
let oldValue = (target as any)[key]
if (!this._isShallow) {
const isOldValueReadonly = isReadonly(oldValue)
if (!isShallow(value) && !isReadonly(value)) {
oldValue = toRaw(oldValue)
value = toRaw(value)
}
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
if (isOldValueReadonly) {
return false
} else {
oldValue.value = value
return true
}
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
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
}
deleteProperty(target: object, key: string | symbol): boolean {
const hadKey = hasOwn(target, key)
const oldValue = (target as any)[key]
const result = Reflect.deleteProperty(target, key)
if (result && hadKey) {
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
}
return result
}
has(target: object, 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: object): (string | symbol)[] {
track(
target,
TrackOpTypes.ITERATE,
isArray(target) ? 'length' : ITERATE_KEY,
)
return Reflect.ownKeys(target)
}
}
1、依赖收集
export function track(target: object, type: TrackOpTypes, key: unknown) {
if (shouldTrack && activeEffect) {
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(() => depsMap!.delete(key))))
}
trackEffect(activeEffect, dep, void 0)
}
}
// 删除Dep中的副作用
function cleanupDepEffect(dep: Dep, effect: ReactiveEffect) {
const trackId = dep.get(effect)
if (trackId !== undefined && effect._trackId !== trackId) {
dep.delete(effect)
if (dep.size === 0) {
dep.cleanup()
}
}
}
export function trackEffect(
effect: ReactiveEffect,
dep: Dep,
debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
// 收集时一个个收集的
// 需要重新的去收集依赖 , 将不需要的移除掉
// console.log(effect, dep);
if (dep.get(effect) !== effect._trackId) {
dep.set(effect, effect._trackId)
const oldDep = effect.deps[effect._depsLength]
// 如果没有存过
if (oldDep !== dep) {
if (oldDep) {
// 删除掉老的
cleanDepEffect(oldDep, effect);
}
// 换成新的
effect.deps[effect._depsLength++] = dep; // 永远按照本次最新的来存放
} else {
effect._depsLength++;
}
}
}
2、派发更新
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) {
// never been tracked
return
}
let deps: (Dep | undefined)[] = []
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
deps = [...depsMap.values()]
} else if (key === 'length' && isArray(target)) {
const newLength = Number(newValue)
depsMap.forEach((dep, key) => {
if (key === 'length' || (!isSymbol(key) && key >= newLength)) {
deps.push(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
deps.push(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
deps.push(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
deps.push(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
deps.push(depsMap.get(ITERATE_KEY))
}
break
}
}
pauseScheduling()
for (const dep of deps) {
if (dep) {
triggerEffects(dep, DirtyLevels.Dirty, void 0)
}
}
resetScheduling()
}
export function triggerEffects(
dep: Dep,
dirtyLevel: DirtyLevels,
debuggerEventExtraInfo?: DebuggerEventExtraInfo,
) {
pauseScheduling()
for (const effect of dep.keys()) {
// dep.get(effect) is very expensive, we need to calculate it lazily and reuse the result
let tracking: boolean | undefined
if (
effect._dirtyLevel < dirtyLevel &&
(tracking ??= dep.get(effect) === effect._trackId)
) {
effect._shouldSchedule ||= effect._dirtyLevel === DirtyLevels.NotDirty
effect._dirtyLevel = dirtyLevel
}
if (
effect._shouldSchedule &&
(tracking ??= dep.get(effect) === effect._trackId)
) {
effect.trigger()
if (
(!effect._runnings || effect.allowRecurse) &&
effect._dirtyLevel !== DirtyLevels.MaybeDirty_ComputedSideEffect
) {
effect._shouldSchedule = false
if (effect.scheduler) {
queueEffectSchedulers.push(effect.scheduler)
}
}
}
}
resetScheduling()
}
总结
- reactive 函数实际执行了 createReactiveObject 方法。
- createReactiveObject 方法主要创建了一个 proxy 实例对象,给代理对象添加 getter 和 setter 行为,get、set 方法主要在 mutableHandlers 对象中。
- get 方法实际执行了 createGetter 方法,该方法中 track 函数来进行依赖收集,而 set 方法实际执行了 createSetter 方法,该方法中 trigger 进行依赖触发。
- effect 函数实际创建了一个 ReactiveEffect 实例,该构造函数接收一个 fn 函数,等于传进来的匿名函数,该回调函数必须暴露 getter 行为。
- 另外该构造函数还做了两件事,第一是在 run 函数中给 avtiveEffect 赋值,第二是执行 fn 函数。
- 一旦 getter 触发,就会激活 track 方法,构建 WeakMap 即 targetMap 对象,从而完成指定对象指定属性到 effect 的依赖收集的工作。
- 此时已经完成了一个依赖收集,之后进行依赖触发 setter。
- set 方法实际执行了 createSetter 方法,然后触发 trigger 函数进行依赖触发。
- trigger 函数中首先或从之前 targetMap 依赖收集的对象中获取,根据 key 获取到 effect,然后执行 fn 函数,从而完成一个依赖触发的过程。