vue3-05 Reactivity 收集依赖

137 阅读8分钟

effect

这个匹配vue2 watch

Vue3的源代码并没有把effect暴露出去。

我们可以执行npm run build 就可以把所有的包都导出。

然后去dist目录创建一个example 文件件 index.html

然后引入 ./reactivity.global.js

介绍使用方法
let {effect, reactive} = VueReactivity; // npm run dev 构建vue是拿不到这个的 VueReactivity 源代码打包暴露出来的

// 这个方法有啥用? 这个是响应式的一个核心

// demo01

let state = reactive({a:1, other:1});

// 默认上来会执行一次。
effect(() => {
    // 默认上来会执行一次。
    // 取值会调用代理get方法。 这个时候可以把effect函数存放起来。 采用 WeakMap(target=>Map(key=>Set([effect])))
});

    // 更新 state.a 这个的时候会让effect这个函数在跑起来
    // state.a. 就触发了state的getter 出发了effect 的收集工作。
    console.log(state.a);
    setTimeout(() => {
        // 上面那个effect函数就重新执行了, 读取的时候已经存储了effect 所以更新的时候可以直接执行它。所以数据一变就重新执行effect了 等价于vue2的watch react 是每次render才走对应钩子 这个是纯靠响应式。
        state.a = 2; 
    });


    // 这时候有一个问题我更改 other 的时候 effect需要重新执行吗? 应为effect内部other并没有使用。
    
    // 这里只有 a 和 effect关联起来了
    
    // [update 生命周期任何内容变了都会执行。// effect内部的属性和effect有关联]
    
    // 疑问?自定义一下effect 看看这个玩意怎么和proxy关联的?!

基于上节课动了几个文件

// basehander.ts 需要改了getter 和 setter
// effect.ts effect 响应收集的核心
basehander.ts

主要修改了

const Getter = (isReadOnly:boolean=false, isShallow:boolean=false) => {
    // 如果不是只读的那么就需要收集依赖关系
    // 收集依赖 如果不是只读的需要收集起来一会更新试图,如果是只读的就不牵扯到视图的更新。
    // 执行effect的时候里面取值会走到这里的。因为动用到了get
    // 如果进来取值的时候需要收集effect
    if(!isReadOnly){
        // 问题是怎么拿到effect?
        // 这个问题很简单把track的effect的收集器扔到effect.ts里面 这样在effect执行的时候可以放到全局里面后面回详细说明
        // track调用的是effect的track
        // 这个东西设计的的确蛮精妙的
        /**
        * 梳理一下昂
        * 你看看一开始
        * effect 开始执行了对吧
        * 里面有get set 方法对吧
        * 注意这个时候effect没有执行完囊昂 在执行栈中
        * 执行到了get又调用了effect.ts的track方法,这个时候是不是就拿到了什么对象的什么方法在effect执行了
        * 可以轻松加愉快的拿到当前正在执行的effect
        * 好回到了effect包里面如果设置个全局变量或者其他的来存一下当前对象和属性
        * track继续执行的时候是不是就知道谁调用的effect了? 因为已经通知track收集全局的了。
        * 这是猜想继续看后面
        * 这里继续昂回到正经实际的实现
        * 创建一个描述操作符号的 opterators.ts
        * export const enum TrackOpter {GET」 
        * package/reactivity/src/core/opterators.ts
        * track(target,TrackOpter.GET,key) // 对target做取值的时候用key属性。
        * track(
        *    target,
        *    TrackOpter.GET,
        *    key
        *);
        * // 回到effect.ts
        */
    }
}

const Setter = (isShallow:boolean = false) => {
    // 当数据更新的时候就需要用到刚才上面的 getTargetMap[effect.ts中的] 通知下去更新effect

}
effect.ts
// 当前的effect当前正在执行的effect,每次执行的时候都保存当前正在执行的effect 当track调用的时候需要用到它
let activeEffect:any = null;
// 辅助当前的effect为了避免下面描述的那个大bug因为存在state和effect可能穿插着用会造成一些列的问题,下面说明一下effect嵌套是个栈所以用栈来保存当前执行的effect比较合适
let activeEffectStrack:any = [];
// 收集effect+key关联的map 收集对象key引用的effect
const getTargetMap = new WeakMap();


/**
 * 收集effect
 * 让某个对象中的属性手机他对应的effect函数
 */

function track(
    target:any,
    opter:any,
    key:string
){
    console.log("收集effect");
    // 丁陆超猜想
    // 如何让
    // target key找到是哪个effect 就是我在basehander猜想的那样
    // 来看看实际的实现是啥样子的?
    // 第二种猜想把,effect至顶部在这里做捆绑.
    // vue2就用的我说这种第二种方式
    // 怎么找到 effect ?
    // 来吧暴露全局变量吧
    // activeEffect
    
    console.log("最终的结果", activeEffect, target, opter, key);
    console.log("最终的流程图",
        '第一步定义返回的effect函数',
        '第二步执行fn',
        '第三步fn执行了get',
        '第四步走到了收集effect,这个调用的effect库',
        '第五步track可以直接拿到暴露出来的当前的effect',
        '第六步直接做关联'
    )
    
    // 按照表面上看用 activeEffect 定义当前指向了哪一个effect足矣
    // 其实是存在问题的
    /**
    * 先来看一个大坑
    * effect(() => { // effect1 入栈 activeEffectStrack[effect1]
    *    state.name
    *    effect(()=>{ // effect2 入栈 activeEffectStrack[effect1, effect2]
    *        state.sex
    *        effect(()=>{ effect3 入栈 activeEffectStrack[effect1, effect2, effect3]
    *          state.ak47 
    *        }) // 出栈 activeEffectStrack[effect1, effect2]
    *    }) // 出栈 activeEffectStrack[effect1]
    *    state.name1 在没有 activeEffectStrack的时候 这里就凉了全局用的是 // effect 3 
    *}) // 出栈 activeEffectStrack[effect1]
    *
    * 解决方案用一个栈记录他们的关系
    * 永远来找数组的最后一个
    * 
    * 设计的很巧妙应为调用时栈所以用栈来控制最后选择谁。
    *
    */
    
    /**
    * 第二个大bug
    * effect(()=>{
    *    state.flg++; // 死循环 // 如果 state.flg 一变就让effect执行就回进入到死循环中去。
    * });
    *
    * 解决方案如果当前栈里面有当前正在运行的effect就别往里放了防止同样的effect一直进入死循环
    * 
    */
    
    
    // 开始进行第六步做关联
    // 处理了上面的大BUG就可以处理这里的内容了。
    // Vue3对编译做了非常多的优化。
    console.log(
        "当前对象",
        target,
        key,
        activeEffect
    );
    
    // key 要和 effect关联
    // 数据结构
    /**
     * 伪代码
     * 
     * let struct = new WeakMap(target => new WeakMap(name=>new Set([effect])))
     */
     
     // // 如果 activeEffect 为null说明当前 target key 没有在effect中使用也就不需要去收集了,如果在effect中是使用了一定会有effect
     if(activeEffect === null) return; // 当前的effect都结束了 或者effect没有用到响应数据。
     
     // 维护 getTargetMap
     // 不管里面有没有先拿出来
     // 第一步先拿target对应的map 
     // // 如果没有target的key就创建他并且把deepMap 指向新的数据空间
     let deepMap = getTargetMap.get(target);
     if(!deepMap) {
         // 如果没有那就创建它一个
         getTargetMap.set(target, (deepMap = new Map()))
     }
     
     // 第二步拿到key对应的set
     let deepKeyMap = getTargetMap.get(key);
     // 如果没有就创建它
     if(!deepKeyMap) {
         deepMap.set(key, (deepKeyMap=new Set()))
     }
     
     // 第三步就拿到了set
     // 防止出现 state.a state.a 这种无限的往里面加有问题
     if(!deepKeyMap.has(activeEffect)) {
         deepKeyMap.add(activeEffect);
     }
     
    // 为什么要用set?
    /**
     * effect(() => {
     *  state.name;state.name;state.name 就会把相同的effect能了三次
     * })
     * 
     *  要区分
     * effect(()=>{
     *  state.name;
     * })
     * 
     * effect(()=>{
     * state.name;
     * });
     * 这个确实是要收集两边的而且用到set[]去重复了还能多次执行
     */
     
     // 这里的 deepMap 就到了map空间了
     
     // 收集后的鬼东西
     
     console.log("收集后的鬼东西::", getTargetMap);
     
     // 这里完事儿了就需要走到basehander的setter了
     
     // 当数据执行的时候通知所有的监控点就是我们这里的钩子
}

createProxyEffect

/**
 * 
 * @param fn 
 * @returns 
 */

// 每一个effect的唯一ID这个后面在说搞啥用的
let uid = 0;
function createProxyEffect(fn:() => {}, options:EffectOptions) {
    // 每创建一个effect都需要有个唯一的身份-uid
    // 源代码里面需要用到这个做排序先创建的先来执行一下。
    // 用于组建的更新。
    effect.id = uid++;
    
    // effect 还需要有一个标识自己身份的状态,标识我是一个effect函数标识我是我 标识我是响应式的effect _表示私有的外界是不能获取的。
    effect.isEffiect = true;
    
    // 记录一下effect原本的函数是谁其实就是fn啦
    effect.raw = fn;
    
    // 另外把effect的配置也保存起来
    effect.options = options;
    
    
    /**
     * 返沪的effect 需要有一个唯一的标识
     */
     function effect() {
         // 避免出现死循环 主义看上面的第二个大bug
         // 第二个大bug 保证一轮只执行一次
         // 这里区分的是函数地址
         if(activeEffectStrack.includes(effect)) return;
         
         try{
             // 把effect捆绑到全局变量上来吧
             activeEffect = effect;
             
             // effect 的指针入栈操作 
             activeEffectStrack.push(effect); 
             
             // 测试第一次调用被执行了
            console.log("测试第一次调用被执行了");
            
            // 执行一次fn
            // 用户返回的方法就是effect的返回方法。
            return fn();
            
            
            // 最难的我们依赖的数据就是fn中的数据变化可这个鬼东西怎么执行?

            // fn函数执行的时候需要干点什么?这个注意在外边定义的不能协迫调用者干什么

            // fn中的响应式数据在取值的时候会走get方法

            /**
             *  effect(()=>{
                console.log("自定义的fn被执行了...", state.a)
            });
             * 这是外边的调用者 state.a会去取值
             *
             * 问题是这个state.a 这个属性怎么和当前的effect进行关联?
             * 
             * 这个取值在哪里?basehander.ts 的 getter这个核心函数去改改呗
             * 注意这里要跑到basehandler函数去做操作。 23 行
             * 跑到这里
             * // 收集依赖 如果不是只读的需要收集起来一会更新试图,如果是只读的就不牵扯到视图的更新。
             * 28-39行
             * 
             * // 从basehander返回
             * 创建收集effect track
             */
         }finally{
             // 上面函数有返回值所以这里用了finally 保证了无论如何都可以执行的到, 
            // 这个地方为什么要加这么个玩意try catch 因为如果fn包错了还能正常的把当前指针给扔出去。
            // 当fn执行完了就执行出栈
            activeEffectStrack.pop();
            activeEffect = activeEffectStrack[activeEffectStrack.length-1];
         }
         
     }
    return effect;

}
function effect(fn:()=>{}, options:EffectOptions) {
    // 这里是做配置

    // 这里是做响应式的
    // 如何把effect变成响应式的,可以做到数据发生了改变重新执行它。
    const ResProxyEffect = createProxyEffect(fn, options);

    // 因为effect在执行的时候回先执行一次。
    // 所以先跑一次
    if(!options || !options.lazy){ // 如果不是懒加载就执行一下
        ResProxyEffect();
    }

    return ResProxyEffect;
}


export {
    effect,
    track,
}