vue3-06 Reactivity 执行

91 阅读5分钟

basehander.ts

const Setter = (isShallow:boolean = false) => {
    return function(target:any, key:any, value:any, reactiver:any) {
        // 通过proxy设置值的时候会触发set
        // 当数据更新的时候就需要用到刚才上面的 getTargetMap 通知下去更新effect
        // 区分是新增的还是修改的
        // vue2无法监控更改的[索引],无法监控数组的[长度]变化 [100] length
        // vue3解决这些内容 但是有些特殊的场景需要用到hack
        // 对象如何看新增还是修改?
        // 去对象看一下如果没有就是新增,有就是修改
        // 获取老值
        // 这个是用来判断对象
        // 三种情况 新增、修改、老值和新值一样没有改
        
        // 获取到老的值
        const oldVal = target[key];
        
        //  if(oldVal) { // 如果有就修改 为什么不能用这个判断是新增的还是老的因为存在对象和数组两种情况所以需要hasKey
        
        // }
        
        // 如何来判断数组
        // 如果是数组并且索引是数字以索引的形式访问数组
        //  if(isArray(target) && isIntegerkey(key)) {
        
        // }
        
        // 解读 isArray(target) && isIntegerkey(key)如果是数组并且key还是数字
        // key 小于数组长度是存在的
        // key 在对象上是存在的
        let hasKey = isArray(target) && isIntegerkey(key) 
        ? 
        Number(key) < target.length 
        : 
        hasOwn(target, key);
        
        // 新值
        let newVal = Reflect.set(target, key, value, reactiver); // target[key] = value;
        // hasKey 通过这个来判断属性是新增的还是修改的
        if(hasKey){
            // 修改 情况一
            // hasChange 对象是否不想等。
            // 对象比较的是地址空间。
            // share.ts 中添加的
            // const hasChange = (oldval:any, newval:any) => {return oldval != newval;}
            if(hasChange(oldVal, newVal)){
                // 如果新值和老值不相同就更新
                // 情况三 oldVal == newVal
                // 如果老值和新值不一样就改一改
                // array 如果调用了 push
                // isArray 能过 key 就是新增那一项
                // Number(key) < target.length 为false 所以走的是新增
                // 对应数据来讲不管是push pop 等等都会去改索引
                // 所以push pop 这些方法都是不需要重写的。
                // 在enum中新增两种类型一个是新增一个是修改。
                // opterators.ts
                // export const enum TriggerOrTypes {ADD,SET} 
                // 修改 oldVal 标识我要修改这个属性
                // trigger 就是让对应的effect 执行
                // 既然让对应的effect 执行当然需要给effect 添加trigger了
                trigger(target, TriggerOrTypes.SET, key, value, oldVal);
            }
        } else {
            // 新增 情况2
            // 触发新增
            trigger(target, TriggerOrTypes.ADD, key, value);
        }
        
        return newVal;
    }
}

package/reactivity/src/core/effect.ts

// 定义触发器 trigger
/**
 * 触发器
 * @param target 目标对象
 * @param opt    操作 TriggerOrTypes
 * @param key    操作对象的属性 
 * @param value  操作属性的值
 * @param oldVal 老值更新的时候可以用到
 * @description  找属性对应的effect,让其执行 目前只考虑了数组和对象
 */
 function trigger(target:any, opt:any, key?:any, value?:any, oldVal?:any) {
     console.log("triggerstart", 1, target, opt, key, value, oldVal);
     
     // 在example里面测试
     // let r = reactive({a:[1,2,3]});
     // r.AAA = 123; // 就会走到了add
     // 给数组调用push 方法
     
     /**
     * r.a.push(1); // add
     * 
     * push就走到了 数组和对象的add key就是3,push走的是index key 
     * 
     * r.a[1] = 100 // 走的是update
     * r.a.length = xx // 也是走的update
     * r.a[3] = 1 // 走的是add
     * 
     * 为什么push会进来?
     * 因为push 会给数组新增一个数据!!! 知道 没存结构就知道这个 element property 本质上都是控制属性所以都能走到set方法上来。最终都是往数组上增加那一项。
     */
     
     // 这时候就可以编写trigger了
     
     /**
     * 需求分析
     * 1.这个属性没有被收集过effect
     * 2.没有那就不需要做任何操作,你都没收集过做毛线的更新
     * 
     * 来到收集器找一下 getTargetMap
     * 
     */
     
     // 看一下这个对象是不是被收录过
     // 拿到target对象对应的的map
     const DeepMap = getTargetMap.get(target);
     // 如果DeepMap 为空说明这个对象没有被记录过直接退出行了没有执行的意义了
     if(!DeepMap) return;
     
     // effects需要执行的队列-收集当次执行的所有effect
     // 为什么用Set主要过滤掉当次执行重复的内容。
     // 为什么搞一个set防止多个相同的effect重复执行
     const effects = new Set();
     
     // 把需要执行的effect都收集到一起并且一起执行
     // 把需要更新执行的effect都存起来。全部存储到一个新的集合里面去。 最后一起执行。
     const add = (addToEffects:any) => {
         // 如果空就不执行了 用在新增上新增事没有effect的所以也就不执行了
        if(!addToEffects) return;

        addToEffects.forEach((item:any) => {
            effects.add(item);
        })
    }
    
    // 需要把effect都拿出来。
    // 看一下该key是不是数组的长度
    // 这种情况比较特殊需要hack单独处理
    // 1. 查看修改的是不是数组的长度,因为改长度影响比较大。
    // 为什么改个长度影响大 r.a.length = 100 开辟了很多空间, r.a.length = 1 删除了很多内容 一个触发了添加操作,一个触发了更新操作。
    // effect(()=>{  document.body.innserHTML = r.a[2] })  setTimeout(()=>{r.a.length=1},1000) 问 effect需不需要更新
    // 造成了数组的结构发生了改变是需要的。
    // 所以需要做这么个处理。
    if('length' === key && isArray(target)) {
        // 说明是数组的length被修改了
        // 1.如有对应的长度有依赖收集就执行effect
        // 假如说effect(()=>{r.a.length}) 这种的
        
        // 2.如果修改的长度小于数组的
        // 比如 effect(()=>{r.a[3];}) setTime(()=>{r.a.length=1}) // 这种的坑
        // weakMap 是不能遍历的 Map是可以被迭代。
        DeepMap.forEach((deep:any, key:any) => {
            console.log(DeepMap,deep, key);
            // 如果外边对长度修改
            // effect 有长度的需要修改a[1] 带索引的也需要修改。
            // 如果 key 是 length 或者 索引大于了 length的长度这些内容是需要出发他们对应的effect的
            // 这个地方设计的也是灰常的巧妙
            // 首先length 到没什么说的仅仅就是拿出length对应的set
            // 关键是key 这个玩意利用了forEach的便利每一个的key 如果是数字索引并且比length所设置的值大那就需要他们对应的effect从新来一下重新计算。
            // WeakMap 是不能被forEach所遍历的
            // Map 是可以被遍历的 xx.length = 1 value就是1 1 和 本身改的那个索引去做对比 因为索引小于了length设置的所以就需要都更新一下
            // effect(()=>{ app.innerHTML = r.a[3]; console.log(r.a.length) }) 一共出发了几次? 2次
            // 后面说怎么更新把它变成一次。
            // 特殊性!!! 因为你可能改的是长度但是对应的索引也需要更新
            if('length' === key || key > value) {
                // 把key对应的set 收集起来
                add(effects);
            }
        }
        
    } else {
        // 其他情况下就直接 DeepMap从这里面拿出来set进行收集
        // 这个意思改的不在是数组
        // 只要有key触发就好了
        // 只有收集过才能执行更新所以不存在的不要紧因为也不需要更新
        // state.xxx他也没有收集过依赖。 
        if(undefined === key) return;
        const deep = DeepMap.get(key);
        // 修改
        if(deep) {
            // 取出来了对应set直接扔到add effects合并
            add(deep);
        } else {
            // 这个是新增
            // deep 如果是个空set也不需要执行了没意义没有依赖执行个毛
        }
        
        // 还需要处理一种情况处理数组中某一个索引。
        // 什么是索引?
        // effect(()=>{ app.innerHTML = r.a // 相当于用了toString() 然后会用到.length 数组的特性 toString()就会调用length // 这个玩意会默认取长度 所以case里面可以用length 不需要显示的声明length  }) setTimeout(()=>{ r.a[1000] = 1 },1000) 前面那个要不要更新? 要
        // 问题是没有搜集100项effect所以也就没有收集
        // 判断一下是不是做了添加的操作?
        // r.a[100] 并没有走收集所以需要借助上面的length
        switch(opt){ // 如果添加了索引就触发长度的更新
            case TriggerOrTypes.ADD:
            if(isArray(target) && isIntegerkey(key)) {
                // 这说明改的是索引
                // 要触发length的更新行了
                add(DeepMap ['length']);
            }
            break;
        }
    }
    
    
    // 让存储的effect 都执行一下
    // effect(()=>{ app.innerHTML = r.a[3]; console.log(r.a.length) }) 一共出发了几次? 2次
    // 因为去除重复了所以就只有一次。
    // effects.add(item); 放的时候就去重复了。
    // 如何才能搞出来同一个effect 被执行了两次?。
    /**
     * setTimeout(() => {
     *  state.a = 1; // 这个set跑了一次
     *  state.a = 2; // 这个set同样跑了一次
     *  state.a = 1; // 这个set同样跑了一次
     *  // 这里需要做一些异步执行+合并的处理
     *  // 如果在同一个effect中多个值同时改变了。其实就是批量防抖动处理。   
     *  // 频繁更新数据就频繁的更新
     *  // 这相当于跑了多个effect 同一个effect可以通过effects进行合并
     * })
     */
    effects.forEach((cb:any)=>{
        cb();
    })
    
    
 }