版本V3.0.0 源码位置: packages -> reactivity -> src -> effect.ts
effect
const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined
export function isEffect(fn: any): fn is ReactiveEffect {
return fn && fn._isEffect === true
}
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
if (!options.lazy) {
effect()
}
return effect
}
主要功能创建一个会产生副作用的函数。
- fn: 传入一个函数
- options
首先判断传入的函数是不是副作用函数,如果是直接返回函数就可以
createReactiveEffect
function cleanup(effect: ReactiveEffect) {
const { deps } = effect
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}
export function enableTracking() {
trackStack.push(shouldTrack)
shouldTrack = true
}
export interface ReactiveEffect<T = any> {
(): T
_isEffect: true
id: number
active: boolean
raw: () => T
deps: Array<Dep>
options: ReactiveEffectOptions
}
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
if (!effect.active) {
return options.scheduler ? undefined : fn()
}
if (!effectStack.includes(effect)) {
cleanup(effect)
try {
enableTracking()
effectStack.push(effect)
activeEffect = effect
return fn()
} finally {
effectStack.pop()
resetTracking()
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++
effect._isEffect = true
effect.active = true
effect.raw = fn
effect.deps = []
effect.options = options
return effect
}
createReactiveEffect函数是响应式系统的核心之一,用于创建和管理响应式副作用
为这个函数添加属性,
- id: 一个唯一标识符,用于区分不同的响应式副作用
- _isEffect: 一个布尔值,标记这个对象是一个响应式副作用。
- active: 一个布尔值,表示这个副作用当前是否处于活动状态。
- raw: 存储原始的副作用函数。
- deps: 存储这个副作用依赖的所有响应式依赖项。
- options: 存储创建副作用时传入的配置选项。
函数内部 首先判断active是否激活,如果没有激活根据是否存在调度器(scheduler)确定返回的数据
接下来依赖收集和清理
- 如果当前的副作用依赖于
effectStack中,则执行清理操作cleanup(effect),移除之前可能存在的依赖 - 通过
enableTracking()开启依赖收集,将当前副作用压入依赖栈effectStack,并将activeEffect设置为当前副作用,然后执行副作用函数fn()。 - 然后删除依赖栈中的当前副作用函数,并且停止依赖收集,设置当前
activeEffect数据为依赖栈的最后一项。
track
依赖收集
const targetMap = new WeakMap<any, KeyToDepMap>()
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 = new Set()))
}
if (!dep.has(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}
- shouldTrack 开始追踪依赖
- targetMap 响应式依赖集合
- 如果暂停依赖追踪或者activeEffect没有值就return。停止后续操作
- 获取当前target的依赖集合,如果没有就创建一个集合并存targetMap中去
- 获取当前key的依赖追踪集合,如果没有就创建一个集合并存入depsMap中
- 判断当前的activeEffect是否收集过,如果没有收集过,就把当前的activeEffect存入到dep中
- 并在activeEffect中的deps中push进dep
- 如果activeEffect的.options.onTrack(在响应属性或引用作为依赖项被跟踪时被调用)存在,就触发onTrack有值并传入相应的数据
trigger
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
}
const effects = new Set<ReactiveEffect>()
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.options.allowRecurse) {
effects.add(effect)
}
})
}
}
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
} else {
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
add(depsMap.get(key))
}
// also run for iteration key on ADD | DELETE | Map.SET
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
// new index added to array -> length changes
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
const run = (effect: ReactiveEffect) => {
if (__DEV__ && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}
- target // 触发变更的目标对象或集合
- type // 触发类型(如添加、删除、设置值等)
- key // 变更的键(对于对象或Map)或索引(对于数组)
- newValue // 变更后的新值
- oldValue // 变更前的旧值
- oldTarget // 旧的目标集合,仅在某些特定操作中使用
- 从targetMap中取出target的依赖集合,如果没有就直接return
- 创建一个effects集合,
- 添加效果,定义一个
add函数,用于将一组副作用添加到effects集合中。它检查每个副作用是否应该被触发 - 根据触发类型处理
- CLEAR:如果触发类型是清除(例如,清空数组或集合),则触发目标对象的所有依赖效果。
- LENGTH变更:如果目标是数组且变更的键是
length,则触发与length相关的效果,以及所有索引大于或等于新length的效果。 - SET | ADD | DELETE:对于设置、添加或删除操作,根据具体的键和类型,可能还需要触发迭代键的效果(例如,当数组添加新元素时,需要触发与数组长度相关的效果)。
- 定义一个
run函数,用于执行每个效果。如果效果定义了onTrigger钩子(在侦听器回调被依赖项的变更触发时被调用),则先调用该钩子;然后,根据效果是否定义了调度器(scheduler),以同步或异步方式执行效果。