浅谈vue3响应式源码

vue3响应式原理- Proxy和Reflect

Proxy

就是代理的意思,为对象创建一个代理,从而实现对对象基本操作的拦截,在目标对象访问之前有一层"拦截",外界访问这个对象都要通过这层拦截,从而实现对外界的访问进行过滤或者改写

js 复制代码
const test = new Proxy(target, handler)
  • target 需要拦截的目标对象
  • handler 处理这个拦截的具体行为操作
js 复制代码
 const obj = {
     name: "Jack",
     age: 18
 }
 
 const handler = {
     get(target, key, receiver){
         console.log('receiver',receiver)
         return target[key]
     },
     set(target, key, value, receiver){
         console.log('receiver',receiver)
         target[key] = value
     },
     deleteProperty(target, key){
         return delete target[key]
     }
 }
 
 const newObj = new Proxy(obj, handler)
 
 console.log(newObj.age)  // 18
 
 newObj.age = 20 
 
 console.log(delete newObj.age)
  • target: 是目标对象
  • key: 目标属性名称
  • value: 目标属性的值
  • receiver :指向的是当前操作 正确的上下文。如果目标属性是一个 getter 访问器属性,则 receiver 就是本次读取属性所指向的 this 对象。通常,receiver这就是 newObj 对象本身,但是如果我们从 newObj 继承,则receiver指的是从该 newObj 继承的对象

Reflect

反射的意思,就是将代理的内容反射出去,与Proxy一样,也是ES6为了操作对象提供的新的API. 提供拦截JavaScript操作的方法,与Proxy的handler提供的方法一一对应,只要是proxy对象的方法,就能在reflect对象上找到,而且reflect不是一个函数对象,所有的属性和方法都是静态的。不能进行实例化。

js 复制代码
 const obj = {
     name: "Jack",
     age: 18
 }
 
 const handler = {
     get(target, key, receiver){
         console.log('recevier',receiver)
         return Reflect.get(target, key, receiver)
     },
     set(target, key, value, receiver){
         console.log('recevier',receiver)
         return Reflect.set(target,key,value,receiver)
     },
     deleteProperty(target, key){
         return Reflect.deleteProperty(target, key)
     }
 }
 
 const newObj = new Proxy(obj, handler)
 
 console.log(newObj.age)  // 18
 
 newObj.age = 20 
 
 console.log(delete newObj.age)
  • Reflect.get()代替target[key]操作
  • Reflect.set()代替target[key] = value操作
  • Reflect.deleteProperty()代替delete target[key]操作 当然除了上面的方法还有一些常用的Reflect方法:
js 复制代码
Reflect.construct(target, args)  
Reflect.has(target, name)  
Reflect.ownKeys(target)  
Reflect.getPrototypeOf(target)  
Reflect.setPrototypeOf(target, prototype)

vue3响应式源码解读- reactive,ref

vue3 的github核心源码地址

reactive源码

打开源文件,找到文件packages/reactivity/src/reactive.ts 查看源码

ts 复制代码
export function reactive(target: object) {
  // if trying to observe a readonly proxy, return the readonly version.
  if (isReadonly(target)) {
    return target
  }
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap,
  )
}
  • 对target进行响应式只读判断,如果true直接返回target,createReactiveObject()方法是reactive实现的核心
ts 复制代码
function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>,
) {
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target is already a Proxy, return it.
  // exception: calling readonly() on a reactive object
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only specific value types can be observed.
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
  )
  proxyMap.set(target, proxy)
  return proxy
}

createReactiveObject()有五个参数

  • target: 传入的原始目标对象
  • isReadonly: 是否只读
  • baseHandlers: 为普通对象创建proxy时的第二个参数handler
  • collectionHandlers: 为collection类型对象创建proxy时的第二个参数handler
  • proxyMap: WeakMap类型的map,用于存储target与他的proxy之间的对应关系
  • 源码可以看出他将对象分为COMMON、COLLECTION目的是为了根据不同的对象类型进行不同的handler

  • 首先先进行了一系列的判断

    • 先判断target是否为对象,不是直接返回
    • 判断target是否是响应式对象,是则return
    • 判断是否已经为target创建过proxy,是则返回
    • 判断是否是COMMON/COLLECTION类型的对象,不是则返回
    • 如果是以上类型对象,则为target创建proxy,并返回这个proxy
  • 在来就是根据不同类型的对象,进行不同的逻辑处理

  • 主要关注这个baseHandlers 位置: packages/reactivity/src/baseHandlers.ts

ts 复制代码
export const mutableHandlers: ProxyHandler<object> = new MutableReactiveHandler()
// 返回一个对象 {  get, set, deleteProperty, has, ownKeys  }

get和依赖收集

  • mutableHandlers里头返回的对象中,都是各种钩子函数,当对proxy对象进行访问和修改时,会调用相应的函数进行处理。看看get里如何对target进行收集 源码解读:
ts 复制代码
get(target: Target, key: string | symbol, receiver: object) {
    const isReadonly = this._isReadonly,
      shallow = this._shallow
    if (key === ReactiveFlags.IS_REACTIVE) {
    //  如果`key`值为`__v_isReactive`
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
    // key值为 `__v_isReadonly`
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
    } else if (key === ReactiveFlags.RAW) {
      if (
        receiver ===
          (isReadonly
            ? shallow
              ? shallowReadonlyMap
              : readonlyMap
            : shallow
              ? shallowReactiveMap
              : reactiveMap
          ).get(target) ||
        Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
      ) {
      
      // 如果`key==='__v_raw'`并且`WeakMap`中`key`为`target`的值不为空,则返回`target`
        return target
      }
      // early return undefined
      return
    }

    const targetIsArray = isArray(target)

    if (!isReadonly) {
    // 如果target是数组,则重写/增强数组对应的方法; 在这些方法里头调用track()进行依赖收集
   // 数组查找方法: includes,indexOf,lastIndexOf
   // 修改原数组的方法:push,pop,unshift,shift,splice
      if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
        return Reflect.get(arrayInstrumentations, key, receiver)
      }
      if (key === 'hasOwnProperty') {
        return hasOwnProperty
      }
    }

    const res = Reflect.get(target, key, receiver)

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    if (!isReadonly) {
    // 如果是普通对象且非只读,则调用track进行依赖收集
      track(target, TrackOpTypes.GET, key)
    }

    if (shallow) {
    // 如果是浅层响应式对象,直接返回
      return res
    }

    if (isRef(res)) {
    // 如果是ref对象,返回其value值
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }

    if (isObject(res)) {
      
      // 如果是对象类型且只读,则调用readonly(),否则递归调用reactive()
      return isReadonly ? readonly(res) : reactive(res)
    }
    // 如果都不满足,则返回对应的属性值
    return res
  }
}

track解读

ts 复制代码
export function track(target: object, type: TrackOpTypes, key: unknown) {  
  if (!isTracking()) {  
  // 首先进行是否正在进行依赖收集的判断处理
    return  
  }  
  
  let depsMap = targetMap.get(target)  
  
  if (!depsMap) {  
    targetMap.set(target, (depsMap = new Map()))  
  }  
  let dep = depsMap.get(key)  
  if (!dep) {  
    depsMap.set(key, (dep = createDep()))  
  }  
  
  const eventInfo = __DEV__  
    ? { effect: activeEffect, target, type, key }  
    : undefined  
  
  trackEffects(dep, eventInfo)  
}
  • const targetMap = new WeakMap<any, KeyToDepMap>()创建一个targetMap容器,用于保存和当前响应式对象相关的依赖内容,本身是一个 WeakMap类型
  • targetMap键是target,值是一个depsMap【Map实例】,存储的就是和当前响应式对象的每个key对应的具体依赖
  • depsMap键是响应式对象的key,值是一个deps

set和依赖更新

ts 复制代码
set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object,
  ): boolean {
    let oldValue = (target as any)[key]
    if (!this._shallow) {
      const isOldValueReadonly = isReadonly(oldValue)
      if (!isShallow(value) && !isReadonly(value)) {
        oldValue = toRaw(oldValue)
        value = toRaw(value)
      }
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        if (isOldValueReadonly) {
          return false
        } else {
          oldValue.value = value
          return true
        }
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }

    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
  • 旧值先保存到oldValue
  • 如果不是浅层响应,target是普通对象,并且旧值是响应式对象,则赋值oldValue.value = value
  • 判断是否存在key值
  • Reflect.set设置对应的属性值
  • 判断对象是原型链上的内容,不触发依赖更新
  • 目标对象不存在对应的key 调用trigger依赖更新

ref()源码

packages/reactivity/src/ref.ts

ts 复制代码
export function ref(value?: unknown) {
  return createRef(value, false)
}
function createRef(rawValue: unknown, shallow: boolean) {
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}

class RefImpl<T> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  public readonly __v_isRef = true

  constructor(
    value: T,
    public readonly __v_isShallow: boolean,
  ) {
    this._rawValue = __v_isShallow ? value : toRaw(value)
    this._value = __v_isShallow ? value : toReactive(value)
  }

  get value() {
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    const useDirectValue =
      this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
    newVal = useDirectValue ? newVal : toRaw(newVal)
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = useDirectValue ? newVal : toReactive(newVal)
      triggerRefValue(this, DirtyLevels.Dirty, newVal)
    }
  }
}
  • 实现ref的核心就是实例化了一个RefImpl对象。
  • RefImpl对象解读:
    • _ value:用于保存ref当前值,如果参数是对象,则会保存reactive函数转化后的值
    • _ rawValue:用于保存当前ref值对应原始值,如果参数是对象,它就保存转化前的原始值。toRaw()是将的响应式对象转为普通对象
    • dep:用来存储当前的ref值收集的依赖。Set避免重复
    • _v_isRef :被ref定义的都会标识当前数据为一个Ref
    • RefImpl类暴露给实例对象的get、set方法是value对象,所以外部访问要加上.value读取
    • 如果传入的值是对象类型,会调用convert()方法,这个方法里面会调用reactive()方法对其进行响应式处理
  • RefImpl实例关键就在于trackRefValue(this)依赖收集和triggerRefValue(this, newVal)依赖更新的两个函数的处理,原理基本与reactive处理方式类似
相关推荐
雪碧聊技术15 分钟前
01-Ajax入门与axios使用、URL知识
前端·javascript·ajax·url·axios库
adminIvan19 分钟前
Element plus使用menu时候如何在折叠时候隐藏掉组件自带的小箭头
前端·javascript·vue.js
会发光的猪。39 分钟前
【 ElementUI 组件Steps 步骤条使用新手详细教程】
前端·javascript·vue.js·elementui·前端框架
我家媳妇儿萌哒哒39 分钟前
el-table合并单元格之后,再进行隔行换色的且覆盖表格行鼠标移入的背景色的实现
前端·javascript·elementui
baiduguoyun1 小时前
react的import 导入语句中的特殊符号
前端·react.js
前端青山1 小时前
webpack指南
开发语言·前端·javascript·webpack·前端框架
NiNg_1_2341 小时前
ECharts实现数据可视化入门详解
前端·信息可视化·echarts
励志前端小黑哥2 小时前
有了Miniconda,再也不用担心nodejs、python、go的版本问题了
前端·python
喵叔哟2 小时前
重构代码之取消临时字段
java·前端·重构
还是大剑师兰特3 小时前
D3的竞品有哪些,D3的优势,D3和echarts的对比
前端·javascript·echarts