手写miniVue之reactivity核心流程

502 阅读3分钟

核心逻辑

收集依赖

创建一个响应式对象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");
    })