effect
effect 作为 reactive 的核心,主要负责收集依赖,更新依赖
如果你还没看过 reactive , 建议点这里
文档直通车: https://vue3js.cn/vue-composition-api/#reactive
正文
我们还是选择先从定义看起
effect 接收两个参数
fn回调函数options参数
export interface ReactiveEffectOptions {lazy?: boolean // 是否延迟触发 effectcomputed?: boolean // 是否为计算属性scheduler?: (job: ReactiveEffect) => void // 调度函数onTrack?: (event: DebuggerEvent) => void // 追踪时触发onTrigger?: (event: DebuggerEvent) => void // 触发回调时触发onStop?: () => void // 停止监听时触发}export function effect<T = any>(fn: () => T,options: ReactiveEffectOptions = EMPTY_OBJ): ReactiveEffect<T> {// 如果已经是 `effect` 先重置为原始对象if (isEffect(fn)) {fn = fn.raw}// 创建`effect`const effect = createReactiveEffect(fn, options)// 如果没有传入 lazy 则直接执行一次 `effect`if (!options.lazy) {effect()}return effect}
入口很简单,我们继续看一下 effect 是怎么创建的,如果下面这些你觉得相对难理解,建议先去看一遍单元测试,单元测试会助你快速了解整个单元的目的
function createReactiveEffect<T = any>(fn: (...args: any[]) => T,options: ReactiveEffectOptions): ReactiveEffect<T> {const effect = function reactiveEffect(...args: unknown[]): unknown {// 没有激活,说明我们调用了effect stop 函数,if (!effect.active) {// 如果没有调度者,直接返回,否则直接执行fnreturn options.scheduler ? undefined : fn(...args)}// 判断effectStack中有没有effect, 如果在则不处理if (!effectStack.includes(effect)) {// 清除effect依赖,定义在下方cleanup(effect)try {// 开始重新收集依赖enableTracking()// 压入StackeffectStack.push(effect)// 将activeEffect当前effectactiveEffect = effectreturn fn(...args)} finally {// 完成后将effect弹出effectStack.pop()// 重置依赖resetTracking()// 重置activeEffectactiveEffect = effectStack[effectStack.length - 1]}}} as ReactiveEffecteffect.id = uid++ // 自增id, effect唯一标识effect._isEffect = true // 是否是effecteffect.active = true // 是否激活effect.raw = fn // 挂载原始对象effect.deps = [] // 当前 effect 的dep 数组effect.options = options // 传入的options,在effect有解释的那个字段return effect}const effectStack: ReactiveEffect[] = []// 每次 effect 运行都会重新收集依赖, deps 是 effect 的依赖数组, 需要全部清空function cleanup(effect: ReactiveEffect) {const { deps } = effectif (deps.length) {for (let i = 0; i < deps.length; i++) {deps[i].delete(effect)}deps.length = 0}}
看到这里应该会有一个问题了?effect 是如何收集及触发依赖的呢?现在我们回想一下在reactive高频出现的两个函数
- track 收集依赖(get操作)
- trigger 触发依赖(触发更新后执行监听函数之前触发)
track
/*** @description:* @param {target} 目标对象* @param {type} 收集的类型, 函数的定义在下方* @param {key} 触发 track 的 object 的 key*/export function track(target: object, type: TrackOpTypes, key: unknown) {// activeEffect为空代表没有依赖,直接returnif (!shouldTrack || activeEffect === undefined) {return}// targetMap 依赖管理中心,用于收集依赖和触发依赖// 检查targetMap中有没有当前targetlet depsMap = targetMap.get(target)if (!depsMap) {// 没有则新建一个targetMap.set(target, (depsMap = new Map()))}// deps 来收集依赖函数,当监听的 key 值发生变化时,触发 dep 中的依赖函数let dep = depsMap.get(key)if (!dep) {depsMap.set(key, (dep = new Set()))}if (!dep.has(activeEffect)) {dep.add(activeEffect)activeEffect.deps.push(dep)// 开发环境会触发onTrack, 仅用于调试if (__DEV__ && activeEffect.options.onTrack) {activeEffect.options.onTrack({effect: activeEffect,target,type,key})}}}// get、 has、 iterate 三种类型的读取对象会触发 trackexport const enum TrackOpTypes {GET = 'get',HAS = 'has',ITERATE = 'iterate'}
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 trackedreturn}// 对依赖进行分类// effects 代表普通依赖,// computedRunners 为计算属性依赖// 都是 Set 结构,避免重复收集const effects = new Set<ReactiveEffect>()const computedRunners = new Set<ReactiveEffect>()const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {if (effectsToAdd) {effectsToAdd.forEach(effect => {// 避免重复收集if (effect !== activeEffect || !shouldTrack) {// 计算属性依赖if (effect.options.computed) {computedRunners.add(effect)} else {// 普通属性依赖effects.add(effect)}} else {// the effect mutated its own dependency during its execution.// this can be caused by operations like foo.value++// do not trigger or we end in an infinite loop}})}}if (type === TriggerOpTypes.CLEAR) {// collection being cleared// trigger all effects for targetdepsMap.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 | DELETEif (key !== void 0) {add(depsMap.get(key))}// also run for iteration key on ADD | DELETE | Map.SETconst isAddOrDelete =type === TriggerOpTypes.ADD ||(type === TriggerOpTypes.DELETE && !isArray(target))if (isAddOrDelete ||(type === TriggerOpTypes.SET && target instanceof Map)) {add(depsMap.get(isArray(target) ? 'length' : ITERATE_KEY))}if (isAddOrDelete && target instanceof Map) {add(depsMap.get(MAP_KEY_ITERATE_KEY))}}const run = (effect: ReactiveEffect) => {if (__DEV__ && effect.options.onTrigger) {effect.options.onTrigger({effect,target,key,type,newValue,oldValue,oldTarget})}// 如果 scheduler 存在则调用 scheduler,计算属性拥有 schedulerif (effect.options.scheduler) {effect.options.scheduler(effect)} else {effect()}}// Important: computed effects must be run first so that computed getters// can be invalidated before any normal effects that depend on them are run.// 触发依赖函数computedRunners.forEach(run)effects.forEach(run)}
