简单vue3源代码学习笔记(一)响应式绑定代码理解

98 阅读4分钟

reactive.js

import { hasChanged, isArray, isObject } from "../utils";
import {track, trigger } from "./effect";

//处理第二个特例 代理同一个对象
const proxyMap = new WeakMap()
export function reactive(target){
    // 判断是否为原始值 如果不是  就用proxy处理来进行响应式绑定
    if(!isObject(target)){
        return target;
    }
    //处理特例 判断是否已经被代理过
    if(isReactive(target)){
        return target
    }
    if(proxyMap.has(target)){
        return proxyMap.get(target)
    }
    const proxy = new Proxy(target,{
        get(target,key,receiver){

            if(key === '__isReactive'){
                return true
            }
            // Reflect.get 
            // target:需要取值的目标对象,
            //  key:需要获取的值的键值,
            //  receiver:如果target对象中指定了getter,
            //  receiver则为getter调用时的this值。
            // 在es6 Proxy中,推荐使用Reflect.get而不是target[key]的原因?
        //     Reflect.get(target, prop, receiver)中的参数receiver:如果target对象中指定了getter,receiver则为getter调用时的this值。
        // handler.get(target, prop, receiver)中的参数receiver:Proxy或者继承Proxy的对象。
      
        const res = Reflect.get(target,key,receiver);

        track(target,key)
        // 处理深层对象代理  vue2会递归所有 而vue3会做懒处理
         return isObject(res)?reactive(res):res;
        },
        set(target,key,value,receiver){
            //处理数组在更新时 length不更新问题
            let oldLength = target.length;
            const oldValue = target[key];
          const res = Reflect.set(target,key,value,receiver);
          //如果新老值发生改变时 
          if(hasChanged(oldValue,value)){
              //更新值 如果是数组并且更新length
            trigger(target,key);
            if(isArray(target)&& hasChanged(oldLength,target.length)){
                trigger(target,'length');
            }
          }
         
          return res
        }
        })
        //保存对象的响应式方法,可以WeakMap来进行储存
        proxyMap.set(target,proxy)
        return proxy
}
//处理特例 判断是否已经被代理过 增加个私有的key来处理判断
export function isReactive(target){
return !!(target && target.__isReactive);
}

effect.js

// 可能会有多个effect []
const effectStack = [];
let activeEffect;  //使两个函数有关系 用来记录当前正在执行的副作用函数
export function effect(fn,options={}){
    // 接收的为一段用户代码  可能会出错 所以使用try进行包裹来处理 
    // try-catch-finally的执行顺序是:1、不管有没有出现异常,finally块中的代码都会执行;
    // 2、当try和catch中有return时,finally仍然会执行;
    // 3、finally是在return后面的表达式运算后执行的。
    // options里的参数 lazy 判断依赖有没有更新 需不需要出发effect函数  scheduler 调度机制
    const effectFn= ()=>{
        try{
            activeEffect = effectFn;  //复制为当前执行的副作用函数
            effectStack.push(activeEffect)
            return fn();
        }finally{
       //todo  执行完后应该让它还原
       effectStack.pop();
       activeEffect = effectStack[effectStack.length-1];
        }
    }
    if(!options.lazy){
        effectFn();
    }
    effectFn.scheduler = options.scheduler;
    return effectFn;
  
}
// ##targetMap

// targetMap用于存储副作用,并建立副作用和依赖的对应关系。

// 一个副作用可能依赖多个响应式对象,也可能依赖一个响应式里的多个属性。
// 而一个属性又可能被多个副作用依赖,因此targetMap的结构设计如下。
//就是会有多个effect里面又会有多个对象包对象的响应式结构

// ```javascript
// { // 这是一个WeakMap
//   [target]: { // key是reactiveObject, value是一个Map
//     [key]: [] // key是reactiveObject的键值, value是一个Set
//   }
// }
// ```

// 使用WeakMap的原因:当reactiveObject不再使用后,不必手动去WeakMap里删除,垃圾回收系统可以自动回收。
const targetMap = new WeakMap();
export function track(target,key){
    //收集依赖的时候必需要存在activeEffect
    if(!activeEffect){
        return ;
    }
    let depsMap = targetMap.get(target)
    //第一次收集依赖可能不存在
    if(!depsMap){
        targetMap.set(target,(depsMap = new Map()))
    }
    let deps = depsMap.get(key)
    if(!deps){
        depsMap.set(key,(deps=new Set()))
    }
    //将副作用存储进去 并建立联系
    deps.add(activeEffect)
}
// 相当于track的逆运算
export function trigger(target,key){
    const depsMap = targetMap.get(target);
   if(!depsMap){
       return
   } 
   const deps = depsMap.get(key);
   if(!deps){
       return
   }
   deps.forEach((effectFn) => {
       if(effectFn.scheduler){
           effectFn.scheduler(effectFn)
       }else{
        effectFn()
       }
     
   });
}

ref.js

import { track, trigger } from "./effect";
import { reactive } from './reactive';
// ref 也是响应式数据的一种,类似于 reactive,不过通常用 ref 封装简单数据,如 Number、String、Boolean等,用 reactive 只能封装对象
export function ref(value){
  if(isRef(value)){
      return value;
  }
  return new RefImpl(value)
}
export function isRef(value){
  return !!(value &&value.__isRef)
}

// ref 中的数据实际上是储存在 this._value 中,通过 value 来进行存取是因为类上定义了 value 的 getter 和 setter,而这两者维护的都是其中的 _value 属性,是一种类似代理的关系
class RefImpl{ 
    constructor(value){
        this.__isRef = true;
        this._value =convert(value) ;
    }
    get value(){
   track(this,'value')
   return this._value;
    }
    set value(newValue){
        //如果发生改变了 才去更新_value属性
        if(hasChanged(newValue,this._value)){
            this._value = convert(newValue);
            trigger(this,'value')
        }
 
    }

}

function convert(value){
    return isObject(value)?reactive(value):value;
}

computed.js

import { effect, track, trigger } from "./effect";
export function computed(getterOrOption){
    //这是处理当computed处理当传入的为get函数 和set函数的处理
    let getter,setter;
    if(isFunction(getterOrOption)){
        getter = getterOrOption;
        setter=()=>{
            console.warn('computed is readonly')
        }
    }else{
        getter = getterOrOption.get;
        setter = getterOrOption.set;
    }
return new ComputedImpl(getter,setter)
}
class ComputedImpl {
    constructor(getter,setter){
        this._setter= setter;
this._value = undefined;
//依赖有没有更新
this._dirty = true;
this.effect= effect(getter,{
    lazy:true,
    //调度机制 为了让effect 触发更新时不是去立即执行 而是先触发scheduler
    scheduler:()=>{
        if(!this._dirty){
            this._dirty = true;  
            trigger(this,'value')
        }
    }
})
    }
    get value (){
        // 执行了一次 getter 之后,_dirty 置为 false,因此在下次执行
        //  getter 时就不会再进行计算而是直接返回,而触发调度函数之后,_dirty 置为 true,
        //  再次执行 getter 的话就会再次进行计算,然后再置为 false,如此往复
        if (this._dirty){
            //依赖更新 需要重新计算
            this._value=this.effect();
            this._dirty = false;
            track(this,'value')
        }
        return this._value;
    }
    set value (newValue){
        //todo
        this._setter(newValue)
    }
}