上一篇:Vue3.0源码系列(一):响应式原理 (effect,reactive,依赖收集get,触发依赖set)
背景:接着上次文章一juejin.cn/post/708303… 往下看,这一节我们实现runner的功能,其主要就是当我们调用effect方法之后,代码会返回一个function函数,当调用runner函数之后,会再次执行传给effect方法的fn方法,进而拿到fn函数的返回值。
崔大大:mini-vue项目地址:github.com/cuixiaorui/…
个人学习github:github.com/zzq921/my-m…
1.书写runner功能的单元测试
it('当调用effect时候,会返回一个runner方法',()=>{
let foo = 10
const runner:any = effect(()=>{
foo++
return 'foo'
})
expect(foo).toBe(11)
const r = runner()
//单元测试(1)
expect(foo).toBe(12)
//单元测试(2)
expect(r).toBe('foo')
})
2.在effect中实现runner功能
export function effect(fn) {
// 创建 ReactiveEffect 类,生成effect工厂,每次生成唯一的effect,每次调用effect的时候,执行fn方法。
const _effect = new ReactiveEffect(fn)
//处理_effect指针,当前的实例的run方法
const runner:any = _effect.run.bind(_effect)
//返回一个runner函数 ,完成 单元测试(1)
return runner
}
export class ReactiveEffect{
constructor(fn) {
//接受fn方法
this._fn = fn
}
//每次effect被调用时候,执行run方法,进而执行fn()方法
run() {
//this代表当前effect 赋值给activeEffect
activeEffect = this
const result = this._fn();
//return 出返回值 完成 单元测试(2)
return result
}
}
3.实现scheduler,scheduler为我们调用effect时候传入的第二个参数,一般会执行一些我们编码当中的一些逻辑,当我们响应式对象放生变化时,也就是说set的时候,会执行scheduler函数,而不会再去执行effect当中的第一个参数fn方法。
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
}
)
//单元测试(1)
// 执行effect的第一个参数fn,第二个参数scheduler方法没有被执行
expect(scheduler).not.toHaveBeenCalled()
expect(dummy).toBe(1)
obj.foo++
//单元测试(2)
//当响应式数据变化 obj.foo++,也就是set时候,执行scheduler方法,但是不会执行fn。
expect(scheduler).toHaveBeenCalledTimes(1)
expect(dummy).toBe(1)
run()
//单元测试(3)
//当我们调用run也就是runner方法时候,会再次执行fn
expect(dummy).toBe(2)
})
4.实现远源码中的scheduler方法
export function effect(fn,options:any = {}) {
// 创建 ReactiveEffect 类,生成effect工厂,每次生成唯一的effect,每次调用effect的时候,执行fn方法。
const _effect = new ReactiveEffect(fn,options.scheduler)
//处理_effect指针,当前的实例的run方法
const runner:any = _effect.run.bind(_effect)
//返回一个runner函数
return runner
}
export class ReactiveEffect{
private _fn:any //内部的_fn()方法
//public scheduler 外部能够获取到scheduler方法
constructor(fn,public scheduler?) {
//接受fn方法
this._fn = fn
}
//每次effect被调用时候,执行run方法,进而执行fn()方法
run() {
const result = this._fn();
//return 出返回值
return result
}
}
5.在trigger中也就是响应式数据发生变化,也就是set时候进行判断,如果scheduler存在,则执行scheduler()方法。否则执行fn()方法
export function trigger(target,key) {
//通过目标值target,依赖的key值,获取所有与此依赖有关的集合dep
let depsMap = targetMap.get(target)
let dep = depsMap.get(key)
//触发依赖的方法,把dep当作参数传入,封装成一个triggerEffects方法,方便后续进行复用
triggerEffects(dep)
}
export function triggerEffects(dep) {
//循环set结构的dep数组
for(let effect of dep) {
//取出每一个effect,判断effect是否有第二个参数传入
if(effect.scheduler) {
//如果存在,调用scheduler方法,也就是用户传过来的第二个回调函数fn2。
即effect(function fn1(){},function fn2(){})
effect.scheduler()
}else{
//如果没有第二个参数,直接进行依赖触发,执行run方法,run方法里会调用fn
effect.run()
}
}
}
这节主要分析了vue3源码中的runner和scheduler的实现方法,日常开发中scheduler还是很有用处的,当我们应用时,要懂得内部的原理实现。其实内部实现超级简单就是对参数进行了判断,来执行传入内部的方法,好了,今天的源码分析就到这里,期待下篇文章见。