# effect 方法剖析与实现
# 完整代码
export type EffectFn = { | |
(): any; | |
deps: Array<Set<EffectFn>>; | |
options?: Options; | |
}; | |
type Options = { | |
scheduler?: (fn: EffectFn) => void; | |
lazy?: boolean; | |
}; | |
let activeEffect: EffectFn; | |
const effectStarck: Array<EffectFn> = []; | |
const targetMap: WeakMap< | |
object, | |
Map<any, Set<EffectFn> | undefined> | undefined | |
> = new WeakMap(); | |
export function effect(fn: () => any, options?: Options) { | |
const effectFn: EffectFn = () => { | |
clearUp(effectFn); | |
activeEffect = effectFn; | |
effectStarck.push(effectFn); | |
const res = fn(); | |
effectStarck.pop(); | |
activeEffect = effectStarck[effectStarck.length - 1]; | |
return res; | |
}; | |
effectFn.deps = [] as Array<Set<EffectFn>>; | |
effectFn.options = options; | |
if (effectFn.options?.lazy) { | |
return effectFn; | |
} | |
effectFn(); | |
} | |
function clearUp(fn: EffectFn) { | |
fn.deps?.forEach((deps) => { | |
deps.delete(fn); | |
}); | |
} |
# effect 概述
effect 是一个用来注册副作用的函数,它默认会将注册的副作用立即执行一次,并支持传递一个选项参数 options ,可以配置是否立即执行,将副作用函数返回以便于手动执行 and 配置调度器 scheduler
# 部分变量,属性,方法介绍
activeEffect: 用来存储当前的副作用函数effectStrack: 因为effect可能会嵌套effect,所以需要用effectStrack来存储这些嵌套的副作用函数,当内层的effect执行完毕后,将内层的副作用函数弹出,将外层的副作用函数赋值给用来存储当前的副作用函数的activeEffect。targetMap: 一种WeakMap结构,用来存储target,key, 副作用之间的对应关系。![关系图]()
effectFn.deps: 类型为Array<Set>,用来存储保存的有该副作用的target对应的key对应的Set, 以便于在副作用函数再次执行的时候调用的clearUp方法来将effectFn从这些Set中移除。clearUp: 利用effectFn.deps将effectFn从其所在的Set中移除。
# 详细步骤
- 定义
effectFn增强版副作用函数,以便于增强它,并做进一步处理effectFn函数将做的事情- 调用
clearUp来将effectFn从其所在的Set中移除,从而避免trigger触发没必要的副作用函数。 - 将
effectFn赋值给activeEffect, 并将effectFn放进effectStack。 - 调用
fn副作用函数,将返回值保存进res。 - 将
effectFn从activeEffect中弹出,并将activeEffect的最后一个元素赋值给activeEffect(让activeEffect指向外层的副作用函数) - 返回
res
- 调用
- 在
effectFn函数上添加属性,增强它- 在
effectFn上添加deps属性,用来存储保存有该副作用函数的target对应的key的对应的Set, 以提供给clearUp函数使用。 - 在
effectFn函数上添加options属性,保存副作用函数的配置选项。
- 在
- 根据
effectFn函数的配置选项options属性,来进行下一步的操作- 如果
effectFn.options.lazy为true,则不立即执行effectFn, 而是将effectFn返回,手动调用。 - 如果为
false, 则立即执行。
- 如果
# track 方法剖析与实现
# 完整代码
export function track(target: {}, key: any) { | |
if (!activeEffect) return; | |
let depsMap = targetMap.get(target); | |
if (!depsMap) targetMap.set(target, (depsMap = new Map())); | |
let deps = depsMap.get(key); | |
if (!deps) depsMap.set(key, (deps = new Set())); | |
deps.add(activeEffect); | |
activeEffect.deps.push(deps); | |
} |
# track 概述
用来收集副作用,将副作用存储进对应的 target 对应的 key 对应的 Set 。
# 详细步骤
- 边界处理
- 如果
activeEffect == undefined,则直接返回
- 如果
- 将副作用函数存储进对应的
Set- 调用
targetMap.get(target)取出target对应的Map(里面存储的是key和对应的Set)并赋值给depsMap。如果depsMao为空,则调用targetMap.set(target, (depsMap = new Map))新建一个Map存进targetMap。 - 调用
depsMap.get(key)取出key对应的Set(里面存储的是key对应的副作用函数) 并赋值给deps。如果deps不存在,则调用depsMap.set(key, (deps = new Set()))新建一个Set存进depsMap。 - 调用
deps.add(activeEffect)将副作用函数存储进Set.
- 调用
- 将存有该副作用函数的
Set存储进activeEffect.deps
# trigger 方法剖析与实现
# 完整代码
export function trigger(target: {}, key: any) { | |
const depsMap = targetMap.get(target); | |
if (!depsMap) return; | |
const effects = depsMap.get(key); | |
if (!effects) return; | |
const effectsToRun: Set<EffectFn> = new Set(); | |
effects?.forEach((effectFn) => { | |
if (effectFn !== activeEffect) { | |
effectsToRun.add(effectFn); | |
} | |
}); | |
effectsToRun.forEach((effectFnToRun) => { | |
if (effectFnToRun.options?.scheduler) { | |
effectFnToRun.options.scheduler(effectFnToRun); | |
} else { | |
effectFnToRun(); | |
} | |
}); | |
} |
# trigger 概述
用来调用 track 收集的副作用,如果有调度器,则调用调度器。
# 详细步骤
- 取出对应的
target的对应的key的对应的Set, 并做边界处理- 调用
targetMap.get(target)取出target对应的Map赋值给depsMap。如果depsMap不存在,则直接返回 - 调用
depsMap.get(key)取出key对应的Set并复制给effects,如果effects不存在,则直接返回。
- 调用
- 定义一个
effectsToRun, 将effects存储的副作用函数全部添加到effectsToRun, 以等待遍历effectsToRun执行存储的所有副作用函数。(之所以这么做是因为,副作用函数在执行的时候会先调用clearUp将其从Set中删除,执行fn的时候又会将effectFn添加到Set, 这样就会陷入无限循环, 所以定义一个新的Set来避免这个问题) - 调用
effectsToRun.forEach来遍历它,并调用存储的副作用函数,但是在调用前会做判断,如果effectToRun.options.scheduler存在的话,会调用effectToRun.options.sheduler而不是调用effectToRun
