实现vue3源码-stop & onStop

157 阅读2分钟

继上次完成 scheduler 之后,这次也继续完善相关代码

stop

清空依赖

停止依赖收集

结语

stop

调用stop适用于需要进行停止响应式数据的监控,看看如何实现

这是期望结果

it('stop', () => {
        const NUM = 2
        const CHECKNUM = 3
        let dunmmy
        const obj = reactive({ prop: 1 })
        const runner = effect(() => {
            dunmmy = obj.prop
        })
        obj.prop = NUM
        expect(dunmmy).toBe(NUM)
        stop(runner)
        obj.prop = CHECKNUM
        expect(dunmmy).toBe(NUM)

        // 调用 runner 函数
        runner()
        expect(dunmmy).toBe(CHECKNUM)

    })

清空依赖

class reactiveEffect {
    private _fn: any
    //用于将那个依赖存进去
    deps = []
    constructor(fn) {
        this._fn = fn
    }
    run() {
        activeEffect = this
        return this._fn()
    }
    stop() {
        // set结构,用delete
        this.deps.forEach((dep: any) => {
            // set用del
            dep.delete(this)
        })
    }
}

let activeEffect
export function effect(fn) {
    const _effect = new reactiveEffect(fn)
    const runner: any = _effect.run.bind(_effect)
    runner._effect = _effect
    return runner
}

const targetMap = new WeakMap()
export function track(target, key) {
    const depMap = targetMap.get(target)
    if (!depMap) {
        const depMap = new Map()
        targetMap.set(target, depMap)
    }
    const dep = depMap.get(key)
    if (!dep) {
        const dep = new Set()
        depMap.set(key, dep)
    }
    dep.add(activeEffect)
    //这里我们将依赖存一份
    activeEffect.deps.push(dep)
}
export function stop(runner) {
    runner._effect.stop()
}

停止依赖收集

当再次更改值的时候会导致收集依赖,后续操作会导致浪费不必要的性能,reactive单测也过不去

if(!activeEffect) return
dep.add(activeEffect)
activeEffect.deps.push(dep)
class reactiveEffect {
    private _fn: any
    // 用于将那个依赖存进去
    deps = []
    active = true
    onStop?: () => void
    constructor(fn, public schduler?: Function) {
        this._fn = fn
        this.schduler = schduler
    }
    run() {
        if (!this.active) {
            return this._fn()
        }
        shouldTrack = true
        activeEffect = this
        const res = this._fn()
        shouldTrack = false
        return res
    }
    stop() {
        if (this.active) {
            this.active = false
            // set结构,用delete
            clearEffect(this)
            if (this.onStop) {
                this.onStop()
            }
        }
    }
}

function clearEffect(effect) {
    effect.deps.forEach((dep: any) => {
        dep.delete(effect)
    })
}

优化<补充>

直接赋值可以替换为 Object.assign,但是语义化不足,所以可以抽离到share文件下存放公共的工具

export const extend = Object.assign

import { extend } from "../shared"
export function effect(fn, options: any = {}) {
    // fn 
    const _effect = new ReactiveEffect(fn, options.scheduler)
    extend(_effect, options)
    _effect.run()
    const runner: any = _effect.run.bind(_effect)
    runner.effect = _effect
    return runner
}

obj.prop++会导致重新收集依赖

it('stop', () => {
        let dunmmy
        const obj = reactive({ prop: 1 })
        const runner = effect(() => {
            dunmmy = obj.prop
        })
        obj.prop = 2
        expect(dunmmy).toBe(2)
        stop(runner)
        // obj.prop = 3
        obj.prop++
        expect(dunmmy).toBe(2)

        // 调用 runner 函数
        runner()
        expect(dunmmy).toBe(3)

    })

解决也非常简单,既然重新收集依赖那就在track上做文章

这里用shouldTrack变量控制就行了,run控制变量,track使用变量

// 全局变量收集effect fn
let activeEffect
let shouldTrack
class ReactiveEffect {
    private _fn: any
    public deps = []
    cleanupEffectActive = true
    onStop?: () => void
    public scheduler: Function | undefined
    constructor(fn, scheduler?: Function) {
        this._fn = fn
        this.scheduler = scheduler
    }
    run() {
        if (!this.cleanupEffectActive) {
            return this._fn()
        }
        shouldTrack = true
        // 全局变量收集effect fn
        activeEffect = this
        const res = this._fn()
        // reset
        shouldTrack = false
        return res
    }
    stop() {
        if (this.cleanupEffectActive) {
            cleanupEffect(this)
            if (this.onStop) {
                this.onStop()
            }
            this.cleanupEffectActive = false
        }
    }
}

function cleanupEffect(effect) {
    effect.deps.forEach(dep => {
        dep.delete(effect)
    });
    // 这里没有东西,直接清空就行了
    effect.deps.length = 0
}

const targetMap = new Map() 
export function track(target, key) {
    if (!activeEffect) return
    if (!shouldTrack) return

    // target -> key -> dep
    let depsMap = targetMap.get(target)
    // 初始化判断
    if (!depsMap) {
        depsMap = new Map()
        targetMap.set(target, depsMap)
    }

    let dep = depsMap.get(key)
    if (!dep) {
        dep = new Set()
        depsMap.set(key, dep)
    }

    if(dep.has(activeEffect)) return
    dep.add(activeEffect)
    activeEffect.deps.push(dep)

}

结语

这里测试也不是很直观,后续会把测试一起放进来,尽量提高代码的稳定性,也能更直观的感受vue3源码