继上次完成 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源码