浅谈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处理方式类似
相关推荐
匹马夕阳13 分钟前
Vue 3中导航守卫(Navigation Guard)结合Axios实现token认证机制
前端·javascript·vue.js
你熬夜了吗?14 分钟前
日历热力图,月度数据可视化图表(日活跃图、格子图)vue组件
前端·vue.js·信息可视化
桂月二二6 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
hunter2062068 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb8 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角8 小时前
CSS 颜色
前端·css
九酒8 小时前
从UI稿到代码优化,看Trae AI 编辑器如何帮助开发者提效
前端·trae
浪浪山小白兔9 小时前
HTML5 新表单属性详解
前端·html·html5
lee5769 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579659 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter