Vue学习记录之进阶篇——vue3响应式原理

45 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第7天,点击查看活动详情

我们在前面已经大致的了解了 vue2 和 vue3 的一些特性, 这里我们针对 vue3 的响应式原理 ,进行一下深度的剖析

  • vue2 和 vue3 的响应式区别 和 api 使用的区别就不在这里复述了, 我们直接来看 vue3 的响应式的实现 ,下面是我自己写的一个 demo, 包含了 代码和 注释, 大家可以配合食用
//! 响应式原理 reactive 的实现 P106
const obj = {};
const proto = {bar:1};
const child = reactive(obj);
const parent = reactive(proto);
// 使用 parent 作为 child 的原型
Object.setPrototypeOf(child,parent);
effect(()=>{
  console.log(child.bar); //1
})
// 修改 child.bar 的值
child.bar = 2 // 会执行两次副作用,原因是 set 的时候 child.bar parent.bar 都与副作用函数建立了联系,在赋值的时候, 调用 [[get]] 时,实际上是调用了两次get  
function reactive(obj){
  return new Proxy(obj,{
    set(target,key,newVal,receiver){
      const oldVal = target[key];
      // 判断设置的属性是否是原有的,不是自身拥有的就是新增
      const type = Object.prototype.hasOwnProperty.call(target,key) ? 'SET' : 'ADD' ;
      const res = Reflect.set(target,key,newVal,receiver);
      // target === receiver.raw 说明 receiver 就是target 的代理对象, 如果 target 是沿着原型链找到的原型对象 如上面例子中的 parent, 则不等,这里 reveiver 一直都是代理对象 如 child
      if(target === receiver.raw){
        // && 后面用于判断 NaN 的情况,因为 NaN === NaN  为false 
        if(oldVal !== newVal && (oldVal === oldVal || newVal === newVal)){
          tirgger(target,key,type)
        }
      }
      return res
    },
    get(target,key,receiver){
      // 代理对象可以通过raw属性 访问原始数据
      if(key === 'raw'){
        return target
      }

      track(target,key)
      return Reflect.get(target,key,receiver)
    }
  })
}

// ref 源码 value 可以是 原始值,Object,Array,Map,Set等 实际上就是返回一个实例对象, 实例对象就是 RefImpl 类
function ref(value){
  return createRef(value)
}
// shallow 表示是否是浅响应
function createRef(rawValue,shallow = false){
  // 判断是否已是ref 对象
  if(ifRef(rawValue)){
    return rawValue
  }
  return new RefImpl(rawValue,shallow)
}
// 三点核心: value属性的getter,value属性的setter,shallow与convert
// convert 实现如下: 如果值是对象类型就会调用reactive实现完全代理 ,传入给ref的值是对象,实际上内部会调用reactive来实现完全代理,即value属性的值也会被代理,可以配置第二个参数shallow为 true,则不进行完全代理,只浅响应
const convert = (val) => isObject(val) ? reactive(val) : val; 
class RefImpl{
  constructor(_rawValue,_shallow){
    this._rawValue = _rawValue; // 原始数据
    this._shallow = _shallow; // 是否是浅层代理
    this.__v_isRef = true; // 标记ref对象
    this._value = _shallow ? _rawValue : convert(_rawValue); // 返回的 ref 对象
  }
  // 依赖追踪(收集依赖)对target进行toRaw操作,即始终使用目标代理的原始对象,而非代理对象。
  get value(){
    track(toRaw(this),'get' /* GET */, 'value')
    return this._value
  }
  // 调用trigger函数来触发视图的更新(触发副作用),其次会替换内部属性_value的值为最新的值。
  set value(newVal){ 
    if(hasChanged(toRaw(newVal),this._rawValue)){
      this._rawValue = newVal;
      this._value = this._shallow ? newVal : convert(newVal);
      tirgger(toRaw(this),'set' /* SET */, 'value', newVal)
    }
  }
}
// 收集依赖
function track()
// 派发更新



// toRef:可以用来为源响应式对象上的 property 创建一个 ref。然后可以将 ref 传递出去,从而保持对其源 property 的响应式连接 语法是:toRef(Object, key) 实际上toRef只能用来对指定的对象属性创建一个ref对象。
// 如果对象属性的值本身就是ref对象就不会再次处理,否则会调用ObjectRefImpl类创建对应实例。ObjectRefImpl中不会对属性做任何操作,仅仅是定义value属性,而value属性的getter、setter只是简单的获取和赋值操作,toRef只是定义了value属性而已,之所以实现保持对 对象源属性的响应式连接,完全是属性所在对象自身必须是响应式
class ObjectRefImpl{
  constructor(_object,_key){
    this._object = _object;
    this._key = _key;
    this.__v_isRef = true;
  }
  get value(){
    return this._object[this._key]
  }
  set value(newVal){
    this._object[this._key] = newVal
  }
}
function toRef(object,key){
  return isRef(object[key]) ? object[key] : new ObjectRefImpl(object,key)
}
// toRefs: 就是批量的执行 toRef, 支持数组类型,只是浅层处理,只处理对象自身属性,不处理嵌套属性
function toRefs(object){
  //object 必须是代理对象
  if(!isProxy(object)){
    console.warn(`toRefs() expects a reactive object but received a plain one.`)
  }
  const ret = isArray(object) ? new Array(object.length) : { };
  for(const key in object) {
    ret[key] = toRef(object,key)
  }
  return ret
}

//! 自动脱ref , 如果读取的属性是一个ref,则直接将ref对应的value属性值返回,这样就完成了在模板中直接使用变量读取,不需要.value 来读取 ref, reactive 函数自身带有自动脱ref 的能力
function proxyRefs(target){
  return new Proxy(target,{
    get(target,key,receiver){
      const value = Reflect.get(target,key,receiver);
      // 自动脱 ref 实现: 如果读取的是 ref, 则返回他的 value 属性值,上面的 ref 实现中,每个ref都有一个ref属性标记 __v_isRef
      return value.__v_isRef ? value.value : value
    },
    set(target,key,newVal,receiver){
      // 通过 target 读取真实值
      const value = target[key];
      // 如果值是 Ref, 则设置其对应的value属性值
      if(value.__v_isRef){
        value.value = newVal
        return true
      }
      return Reflect.set(target,key,newVal,receiver)
    }
  })
}