完善effect
实现effect的runner、scheduler、stop、onStop功能
当前的effect的runner、scheduler、stop、onStop,都还算简单就写在一起了,篇幅相对长一点,不过每一模块的实现逻辑并不复杂。
已实现的reactive&effect部分代码
// reactice - 响应式对象
function reactive (raw) {
return new Proxy(
raw,
{
get (target, key) {
// todo 收集依赖
track(target, key)
return Reflect.get(target, key)
},
set (target, key, value) {
// todo 触发依赖
trigger(target, key)
return Reflect.set(target, key, value)
}
}
)
}
// effect - 包装
class ReactiveEffect {
private _fn: any
constructor (fn) {
this._fn = fn
}
run () {
activeEffect = this
this._fn()
}
}
// effect - 首次调用
function effect (fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
}
// track - 收集依赖
const targetMap = new Map()
let activeEffect
function track (target, key) {
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)
}
dep.add(activeEffect)
}
// trigger - 触发依赖
function trigger (target, key) {
const depsMap = targetMap.get(target)
const dep = depsMap.get(key)
for (const effect of dep) {
effect.run()
}
}
正文开始
1.返回runner
测试用例
it('runner', () => {
let foo = 10
// 调用effect(传入fn)返回一个function,成为runner
const runner = effect(() => {
foo++
return 'foo'
})
// 开始会调用fn,foo === 11
expect(foo).toBe(11)
// 调用runner再次执行fn,并把fn的返回值return出来
const r = runner()
// foo === 12
expect(foo).toBe(12)
// r === 'foo'
expect(r).toBe('foo')
})
功能描述
调用effect会返回一个function(返回的function称之为runner),当调用runner,会再次执行传给effect的fn,并且调用fn的时候,会把fn的返回值return出来。
- 主要实现三个功能
- 1.调用effect会返回一个runner -
return _effect.run.bind(_effect) - 2.调用runner再次执行effect的参数fn -
_effect.run.bind(_effect)() - 3.调用runner的时候,会返回fn的返回值 -
return this._fn()
- 1.调用effect会返回一个runner -
实现runner
// 执行fn实际上就是调用activeEffect实例的run方法,所以effect返回activeEffect的run方法
function effect (fn) {
const _effect = new ReactiveEffect(fn)
_effect.run()
// 因为run方法中涉及到this指针问题,所以绑定this为activeEffect实例对象
return _effect.run.bind(_effect)
}
// 更新run
run () {
activeEffect = this
// 将fn的返回值return出去
return this._fn()
}
2.scheduler
测试用例
it('scheduler', () => {
let dummy
let run: any
const scheduler = jest.fn(() => {
run = runner
})
const obj = reactive({ foo: 1 })
const runner = effect(
() => {
dummy = obj.foo
},
{ scheduler } // 传入配置参数 scheduler: function
)
// scheduler一开始的时候不会被调用
expect(scheduler).not.toHaveBeenCalled()
// dummy === 1意味着一开始执行了fn
expect(dummy).toBe(1)
// 响应式对象的值发生变化
obj.foo++
// 传入的scheduler方法被调用
expect(scheduler).toHaveBeenCalledTimes(1)
// 且dummy没有变
expect(dummy).toBe(1)
// 执行run
run()
// dummy === 2意味着fn被调用了
expect(dummy).toBe(2)
})
功能描述
-
四个功能点 - 主要是触发依赖(trigger)的时候执行的函数可能会发生变化
-
1.传入effect的第二个参数 - 一个scheduler的fn
function effect (fn, options: any = {}) {} -
2.effect第一次执行的时候还会调用fn - 无需修改
-
3.当响应式对象发生变化的时候(触发set),不会执行fn,而是scheduler
// 如果有scheduler,则执行scheduler if (effect.scheduler) { effect.scheduler() } else { // 没有就执行run effect.run() } -
4.如果执行runner的时候,会再次执行fn - 无需修改
-
实现scheduler
// 更新effect
function effect (fn, options: any = {}) {
// 将scheduler添加到reacttiveEffect实例上
_effect = new ActiveEffect(fn, options.scheduler)
_effect.run()
return _effect.run.bind(_effect)
}
// 更新trigger
function trigger (target, key) {
const depsMap = targetMap.get(target)
const dep = depsMap.get(key)
for (const effect of dep) {
// 如果有scheduler,则执行scheduler
if (effect.scheduler) {
effect.scheduler()
} else { // 没有就执行run
effect.run()
}
}
}
// 更新ReactiveEffect
class ReactiveEffect {
private _fn: any
// 添加public关键字让它可以在实例对象上访问
constructor (fn, public scheduler?) {
this._fn = fn
}
run () {
activeEffect = this
this._fn()
}
}
3.stop
测试用例
it('stop', () => {
let dummy
const obj = reactive({ prop: 1 })
const runner = effect(() => {
dummy = obj.prop
})
obj.prop = 2
expect(dummy).toBe(2)
// 调用stop,传入runner
stop(runner)
// 更新响应式对象的值
obj.stop = 3
// dummy === 2意味着dummy不再更新了(通过触发依赖更新的,那么将收集的依赖删除就不再更新)
expect(dummy).toBe(2)
// 再次调用runner
runner()
// dummy === 3意味着dummy再次开始更新
expect(dummy).toBe(3)
})
it('onStop')
功能描述
当调用stop(传入runner)的时候,将runner对应的effect从dep中删除掉
实现stop
// 更新ReactiveEffect
class ReactiveEffect {
private fn
deps = []
constructor (fn, public scheduler?) {
this._fn = fn
}
run () {
activeEffect = this
return this._fn()
}
stop () {
this.deps.forEach(dep: any => {
// track收集依赖的时候,将所有的依赖(reactiveEffect实例)都存在dep中,通过delete将
// runner.effect.stop()调用,this指向runner.effect
dep.delete(this) // 将runner对应的effect从dep中删除掉
})
}
}
// 更新track
function track (target, key) {
let depsMap = targetMaps.get(target)
if (!depsMap) {
depsMap = new Map()
targetMaps.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
dep.add(activeEffect)
// 反向收集dep
activeEffect.deps.push(dep)
}
// 更新effect
function effect (fn, options: any = {}) {
const _effect = new ReactiveEffect(fn, options.scheduler)
_effect.run()
const runner: any = _effect.run.bind(this)
// 保存effect到runner中
runner.effect = _effect
return runner
}
// 新增stop
function stop (runner) {
runner.effect.stop()
}
优化
重构
stop () {
cleanupEffect(this)
}
// 将删除effect的逻辑抽离出来
function cleanupEffect (effect) {
effect.deps.forEach((dep: any) => {
dep.delete(effect)
})
}
性能
执行stop后,已经将effect清除过了,再次执行的时候不需要再做其他操作
给一个状态标识是否stop
class ReactiveEffect {
...
active: boolean = true
...
stop () {
// 多次调用stop的时候也只是清空一次
if (this.active) {
cleanupEffect(this)
this.active = false
}
}
}
4.onStop
测试用例
it('onStop', () => {
const obj = reactive({ foo: 1 })
const onStop = jest.fn()
let dummy
const runner = effect(
() => {
dummy = obj.foo
},
{ onStop } // 给第二个参数
)
// 当执行stop之后
stop(runner)
// 传入的onStop会被执行,就是stop的回调函数
expect(onStop).toBeCalledTimes(1)
})
功能描述
当执行stop后,传入的onStop会被执行,就是onStop作为stop的回调函数。
实现onStop
// 更新effect
function effect (fn ,options: any = {}) {
const _effect = new ReactiveEffect(fn, options.scheduler)
_effect.onStop = options.onStop
_effect.run()
const runner = _effect.run.bind(this)
runner.effect = _effect
return runner
}
// 更新ReactiveEffect
class ReactiveEffect {
...
onStop?: () => void
stop () {
if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
...
}
优化
重构
// 工具方法
const extend = Object.assign
// 优化
function effect (fn ,options: any = {}) {
const _effect = new ReactiveEffect(fn, options.scheduler)
// _effect.onStop = options.onStop
// 可能有多个options选项
extend(_effect, options)
_effect.run()
const runner = _effect.run.bind(this)
runner.effect = _effect
return runner
}
// 优化
function track (target, key) {
let depsMap = targetMaps.get(target)
if (!depsMap) {
depsMap = new Map()
targetMaps.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
dep.add(activeEffect)
// activeEffect是在effect调用时会被赋值,如果是一个单纯的get操作的时候,activeEffect是undefined
if (!activeEffect) return // 判空
activeEffect.deps.push(dep) // 反向收集dep
}
effect完整代码
// 全局变量
const targetMap = new Map()
let activeEffect
const extend = Object.assign
class ReactiveEffect () {
private _fn
active: boolean = true
deps = []
onStop?: () => void
constructor (fn, public scheduler?) {
this._fn = fn
this.scheduler = scheduler
}
run () {
activeEffect = this
return this.run()
}
stop () {
if (this.active) {
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
this.active = false
}
}
}
function effect (fn, options) {
const _effect = new ReactiveEffect(fn, options.scheduler)
extend(_effect, options)
_effect.run()
const runner = effect.run.bind(this)
runner.effect = _effect
return runner
}
function track (target, key) {
const depsMap = targetMap.get(target)
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
const dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
dep.add(activeEffect)
if (!activeEffect) return
activeEffect.deps.push(dep)
}
function trigger (target, key) {
const depsMap = targetMap.get(target)
const dep = depsMap.get(key)
for (const effect of dep) {
// 如果有scheduler,则执行scheduler
if (effect.scheduler) {
effect.scheduler()
} else { // 没有就执行run
effect.run()
}
}
}
function cleanupEffect (effect) {
effect.deps.forEach((dep: any) => {
dep.delete(effect)
})
}
关于依赖删除
自己回头再看的时候,发现stop删除依赖的时候看到effect.deps.push(dep),突然很懵忘记为啥这么些了(应该是当时学的时候还没彻底理解)。于是画了张图终于搞明白了。
举个例子说明一下:
传入effect的fn方法有四个依赖项——name、age、wife、hobby,那么这个fn会对应四个依赖集合(dep),因为对应四个key,一个key对应一个依赖集合。
这四个依赖集合会将fn收集进去,且这个fn会反向收集这四个依赖集合。当其中任意一个key变化的时候(例如name),会找到name对应的依赖集合, 并执行这个集合中收集的effectFn以更新页面中的数据(这里会执行 fn 和 fn2 )。
当stop的时候(以fn举例:即调用stop(runner)),执行后当name、age、wife、hobby任何一项发生变化的时候,fn都不会重新调用——message不会更新, 所以我们需要将这四个依赖集合中收集的fn删除掉(于是就有了effect.deps.forEach(dep => dep.delete(effect)),这样当其中任意一个依赖的key 发生变化时,都不会调用这个fn。拿name来说,stop(runner)执行后,会将name对应的依赖集合中的fn删除,但不影响fn1,当name发生变化时,遍历 name的依赖集合并执行,遍历的时候fn已经被删除了,所以message不会更新,但document.body.innerText会更新。
const onePiece = reactice({ name: '路飞', age: '19', wife: '汉库克', hobby: '吃肉' })
let message
const fn = () => {
message = `${onePiece.name}今年${onePiece.age}岁,他的老婆是${onePiece.wife},他喜欢${onePiece.hobby} `
}
const fn2 = () => {
document.body.innerText = onePiece.name
}
const runner = effect(fn)
effect(fn2)
// ... 省略其他effect