核心逻辑
收集依赖
创建一个响应式对象user,响应式对象里面有一个容器,容器需要收集所有的依赖,通过effect去收集依赖,effect函数接受一个回调函数fn作为形参,effect函数执行时先会调用fn一次,调用fn时会执行user.age,会触发get操作,当触发get操作时,响应式对象里的容器会将当前的fn收集到容器中。
触发依赖
当修改响应式对象中的fn时,会触发set操作,会把之前收集的所有fn,即所有依赖,将它们进行调用,即触发依赖。
使用jest进行单元测试
import {reactive} from "../reactive";
import {effect} from "../effect"
describe('effect',()=>{
it('happy path',()=>{
//创建一个响应式对象user
//reactive函数用于收集依赖(get)
const user = reactive({
age:10
})
let nextAge;
//effect函数用于触发依赖(set)
//用effect函数进行包裹,当响应式对象里面的值发生更改了,effect函数会自动更新
effect(()=>{
nextAge=user.age+1;
console.log(nextAge);
});
//通过断言的方式去验证
expect(nextAge).toBe(11);
//update
user.age++;
//当触发依赖时判断
expect(nextAge).toBe(12);
});
reactive.ts模块
import { track, trigger } from "./effect";
export function reactive(raw){
//reactive本质是通过proxy做的代理,然后去拦截
//接收没有处理的raw对象,
return new Proxy(raw,{
//target指向当前对象raw,key是指获取到的用户访问的key
get(target,key){
const res=Reflect.get(target,key);
//TODO 依赖收集
track(target,key);
return res;
},
set(target,key,value){
//TODO 触发依赖
trigger(target,key);
const res=Reflect.set(target,key,value);
return res;
}
})
}
reactivity.spec.ts单元测试
import {reactive} from "../reactive";
//任务拆分思想 针对reactive做单独的单元测试
describe('reactive',()=>{
it("happy path",()=>{
const original={foo:1};
const observed=reactive(original);
expect(observed).not.toBe(original);
expect(observed.foo).toBe(1);
})
})
effect.ts模块
//抽离出一个类,面向对象的思想
class ReactiveEffect{
private _fn:any;
//当实例化对象传入fn,保存fn并运行fn,
constructor(fn){
this._fn=fn;
}
run(){
activeEffect=this;//保存当前的effect 获取当前实例对象
//当实例化对象执行run方法时,触发fn
return this._fn();//实现effect返回runner
}
}
const targetMap=new Map();
export function track(target,key){
//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);
}
dep.add(activeEffect)
}
//触发依赖 遍历之前收集到的所有fn 并调用它
export function trigger(target,key){
let depsMap=targetMap.get(target);
let dep=depsMap.get(key);
for(const effect of dep){
effect.run();
}
}
let activeEffect;
export function effect(fn){
//fn
//当调用effect函数时,需要先执行一下fn,传入fn,并run
const _effect=new ReactiveEffect(fn);
_effect.run();
return _effect.run.bind(_effect);//实现effect返回runner
}
effect.spec.ts单元测试
import { effect } from "../effect"
import { reactive } from "../reactive";
describe('effect',()=>{
it('happy path',()=>{
//创建一个响应式对象user
//reactive函数用于收集依赖(get)
const user = reactive({
age:10
})
let nextAge;
//effect函数用于触发依赖(set)
//用effect函数进行包裹,当响应式对象里面的值发生更改了,effect函数会自动更新
effect(()=>{
nextAge = user.age + 1 ;
console.log( nextAge );
});
//通过断言的方式去验证
expect( nextAge ).toBe(11);
//update
user.age++;
//当触发依赖时判断
expect( nextAge ).toBe(12);
});
完善effect功能的一个测试:实现effect返回runner
it("shoule return runner when call effect",()=>{
//1.effect(fn) -> function(runner) ->fn ->return 结果
let foo=10;
const runner=effect(()=>{
foo++;
return "foo";
});
expect(foo).toBe(11);
const r=runner();
expect(foo).toBe(12);
expect(r).toBe("foo");
})