源码分析Vue3响应式核心之reactive

目录
  • 一、Reactive源码
    • 1、reactive
    • 2、接着看工厂方法createReactiveObject
  • 二、baseHandlers
    • 1、baseHandlers

vue3响应式核心文章汇总:

vue3响应式核心之reactive源码详解

vue3响应式核心之effect源码详解

vue3响应式核心分两篇文章讲解,本篇讲解reactive源码和实现原理,下一篇vue3响应式核心之effect源码详解讲解effect依赖收集与触发。

一、Reactive源码

1、reactive

源码路径:packages/reactivity/src/reactive.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
  )
}

当我们执行reactive({})的时候,会执行createReactiveObject这个工厂方法,返回一个响应式对象。

2、接着看工厂方法createReactiveObject

源码路径:packages/reactivity/src/reactive.ts

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {
  // 仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。
  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
}

仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。

if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
}

如果 target 已经是一个代理对象了,那么直接返回 target

if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
}

如果 target 已经有对应的代理对象了,那么直接返回代理对象

const existingProxy = proxyMap.get(target) // 存储响应式对象
if (existingProxy) {
    return existingProxy
}

对于不能被观察的类型,直接返回 target

const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
    return target
}
// getTargetType源码
function getTargetType(value: Target) {
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value) // 不可扩展
    ? TargetType.INVALID
    : targetTypeMap(toRawType(value))
}

// ReactiveFlags枚举
export const enum ReactiveFlags {
  // 用于标识一个对象是否不可被转为代理对象,对应的值是 __v_skip
  SKIP = '__v_skip',
  // 用于标识一个对象是否是响应式的代理,对应的值是 __v_isReactive
  IS_REACTIVE = '__v_isReactive',
  // 用于标识一个对象是否是只读的代理,对应的值是 __v_isReadonly
  IS_READONLY = '__v_isReadonly',
  // 用于标识一个对象是否是浅层代理,对应的值是 __v_isShallow
  IS_SHALLOW = '__v_isShallow',
  // 用于保存原始对象的 key,对应的值是 __v_raw
  RAW = '__v_raw'
}

// targetTypeMap
function targetTypeMap(rawType: string) {
  switch (rawType) {
    case 'Object':
    case 'Array':
      return TargetType.COMMON
    case 'Map':
    case 'Set':
    case 'WeakMap':
    case 'WeakSet':
      return TargetType.COLLECTION
    default:
      return TargetType.INVALID
  }
}

// toRawType
export const toRawType = (value: unknown): string => {
  // extract "RawType" from strings like "[object RawType]"
  return toTypeString(value).slice(8, -1)
}

创建响应式对象(核心代码)

const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)

接下来将重点讲解baseHandlers这个回调函数。

二、baseHandlers

1、baseHandlers

baseHandlersmutableHandlers, 来自于 baseHandlers文件。

mutableHandlers的源码如下,分别对get、set、deleteProperty、has、ownKeys做了代理。

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}

接下来看看这些个拦截器的具体实现。

(1)、get的代理

const get = /*#__PURE__*/ createGetter()

function createGetter(isReadonly = false, shallow = false) {
  // 闭包返回 get 拦截器方法
  return function get(target: Target, key: string | symbol, receiver: object) {
       // 如果访问的是 __v_isReactive 属性,那么返回 isReadonly 的取反值
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
      // 如果访问的是 __v_isReadonly 属性,那么返回 isReadonly 的值
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
      // 如果访问的是 __v_isShallow 属性,那么返回 shallow 的值
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
      // 如果访问的是 __v_raw 属性,那么返回 target
    } else if (
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }

    // target是否是数组
    const targetIsArray = isArray(target)

    if (!isReadonly) { // 可读
      // 如果是数组,并且访问的是数组的一些方法,那么返回对应的方法

    /**
     * Vue3中使用 arrayInstrumentations对数组的部分方法做了处理,为什么要这么做呢?
     * 对于 push、pop、 shift、 unshift、 splice 这些方法,
     * 写入和删除时底层会获取当前数组的length属性,如果我们在effect中使用的话,
     * 会收集length属性的依赖,当使用这些api是也会更改length,就会造成死循环:
     * */
      if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
        // 返回重写的push、pop、 shift、 unshift、 splice 'includes', 'indexOf', 'lastIndexOf'
        return Reflect.get(arrayInstrumentations, key, receiver)
      }
      // 如果访问的是 hasOwnProperty 方法,那么返回 hasOwnProperty 方法
      if (key === 'hasOwnProperty') {
        return hasOwnProperty
      }
    }

    // 获取 target 的 key 属性值
    const res = Reflect.get(target, key, receiver)

    // 如果是内置的 Symbol,或者是不可追踪的 key,那么直接返回 res
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    // 如果不是只读的,那么进行依赖收集
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    // 如果是浅的,那么直接返回 res
    if (shallow) {
      return res
    }
    // 如果 res 是 ref,对返回的值进行解包
    if (isRef(res)) {
      // ref unwrapping - skip unwrap for Array + integer key.
      return targetIsArray && isIntegerKey(key) ? res : res.value
    }
    // 如果 res 是对象,递归代理
    if (isObject(res)) {
      // Convert returned value into a proxy as well. we do the isObject check
      // here to avoid invalid value warning. Also need to lazy access readonly
      // and reactive here to avoid circular dependency.
      return isReadonly ? readonly(res) : reactive(res)
    }

    return res
  }
}

当target是数组的时候,'push', 'pop', 'shift', 'unshift', 'splice'这些方法会改变数组长度,会导致无限递归,因此要先暂停收集依赖, 所以对数组的以上方法进行了拦截和重写

  if (targetIsArray && hasOwn(arrayInstrumentations, key)) {
    // 返回重写的push、pop、 shift、 unshift、 splice 'includes', 'indexOf', 'lastIndexOf'
    return Reflect.get(arrayInstrumentations, key, receiver)
  }

重写的代码:

const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()

function createArrayInstrumentations() {
  const instrumentations: Record<string, Function> = {}
  // instrument length-altering mutation methods to avoid length being tracked
  // which leads to infinite loops in some cases (#2137)
  ;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      // 由于上面的方法会改变数组长度,因此暂停收集依赖,不然会导致无限递归
      console.log('----自定义push等入口:this, args, key');
      pauseTracking()
      console.log('----自定义push等暂停收集依赖&执行开始')
      // 调用原始方法
      const res = (toRaw(this) as any)[key].apply(this, args)
      console.log('----自定义push等暂停收集依赖&执行结束')
      //复原依赖收集
      resetTracking()
      return res
    }
  })
  return instrumentations
}

下图是执行结果:

可以用以下代码来理解:

let arr = [1,2,3]
let obj = {
    'push': function(...args) {
        // 暂停收集依赖逻辑
        return Array.prototype.push.apply(this, [...args])
        // 启动收集依赖逻辑
    }
}
let proxy = new Proxy(arr, {
   get: function (target, key, receiver) {
       console.log('get的key为 ===>' + key);
       let res = '';
       if(key === 'push') { //重写push
        res = Reflect.get(obj, key, receiver)
       } else {
        res = Reflect.get(target, key, receiver)
       }
       return res
   },
   set(target, key, value, receiver){
       console.log('set的key为 ===>' + key, value);
       return Reflect.set(target, key, value, receiver);
   }
})

proxy.push('99')

特殊属性的不进行依赖收集

// 如果是内置的 Symbol,或者是不可追踪的 key,那么直接返回 res
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
        return res;
    }

这一步是为了过滤一些特殊的属性,例如原生的Symbol类型的属性,如:Symbol.iterator、Symbol.toStringTag等等,这些属性不需要进行依赖收集,因为它们是内置的,不会改变;

还有一些不可追踪的属性,如:proto、__v_isRef、__isVue这些属性也不需要进行依赖收集;

依赖收集

// 如果不是只读的,那么进行依赖收集
    if (!isReadonly) {
        track(target, "get" /* TrackOpTypes.GET */, key);
    }

浅的不进行递归代理

if (shallow) {
  return res;
}

对返回值进行解包

 // 如果 res 是 ref,对返回的值进行解包
    if (isRef(res)) {
        // 对于数组和整数类型的 key,不进行解包
        return targetIsArray && isIntegerKey(key) ? res : res.value;
    }

这一步是为了处理ref的情况,如果res是ref,那么就对res进行解包,这里有一个判断,如果是数组,并且key是整数类型,那么就不进行解包;因为reactive是深层响应式的,所以要把属性为ref的进行解包

对象的递归代理

 // 如果 res 是对象,那么对返回的值进行递归代理
    if (isObject(res)) {
        return isReadonly ? readonly(res) : reactive(res);
    }

(2)、set的代理

const set = /*#__PURE__*/ createSetter()

function createSetter(shallow = false) {
  // 返回一个set方法
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key] // 获取旧值
    //  如果旧值是只读的,并且是 ref,并且新值不是 ref
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
      return false
    }
    if (!shallow) { // 非shallow
      // 新值非shallow && 非只读
      if (!isShallow(value) && !isReadonly(value)) {
        // 获取新旧值的原始值
        oldValue = toRaw(oldValue)
        value = toRaw(value)
      }
      // 代理对象非数组 & 旧值是ref & 新值非ref
      if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
        oldValue.value = value
        return true
      }
    } else {
      // in shallow mode, objects are set as-is regardless of reactive or not
    }
    console.log('----set', target, key, value)

    // 是数组 & key是整型数字 ?
    // 如果 key 小于数组的长度,那么就是有这个 key :
    // 如果不是数组,那么就是普通对象,直接判断是否有这个 key
    // 数组会触发两次set: index和新增的值 和 'length'和新增之后的数组长度
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    // 设置key-value
    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) {// 如果没有这个 key,那么就是新增了一个属性,触发 add 事件
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) { // // 如果有这个 key,那么就是修改了一个属性,触发 set 事件
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    // 返回结果,这个结果为 boolean 类型,代表是否设置成功
    return result
  }
}

主要逻辑:

获取旧值

let oldValue = target[key];

判断旧值是否是只读的

// 如果旧值是只读的,并且是 ref,并且新值不是 ref,那么直接返回 false,代表设置失败
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
        return false;
    }

以上就是源码分析Vue3响应式核心之reactive的详细内容,更多关于Vue3 reactive的资料请关注我们其它相关文章!

(0)

相关推荐

  • vue3使用reactive赋值后页面不改变

    目录 场景原因 一.例子 二.解决方法 1.使用ref存储响应式数据 2.在reactive中使用对象包裹要改变的数据 3.for of循环push到reactive数据中,不破坏数据结构 总结 场景原因 我们需要在vue3中使用服务器的数据渲染到页面上,我使用的是reactive生成的响应式数据属性,但是在挂载后请求的数据并没有渲染显示到页面上. 技术:vue3 . element-ui-plus 一.例子 home.vue <template> <div class="co

  • 源码分析Vue3响应式核心之effect

    目录 一.effect用法 1.基本用法 2.lazy属性为true 3.options中包含onTrack 二.源码分析 1.effect方法的实现 2.ReactiveEffect函数源码 三.依赖收集相关 1.如何触发依赖收集 2.track源码 3.trackEffects(dep, eventInfo)源码解读 四.触发依赖 1.trigger依赖更新 2.triggerEffects(deps[0], eventInfo) 3.triggerEffect(effect, debugg

  • Vue源码学习之响应式是如何实现的

    目录 前言 一.一个响应式系统的关键要素 1.如何监听数据变化 2.如何进行依赖收集--实现 Dep 类 3.数据变化时如何更新--实现 Watcher 类 二.虚拟 DOM 和 diff 1.虚拟 DOM 是什么? 2.diff 算法--新旧节点对比 三.nextTick 四.总结 前言 作为前端开发,我们的日常工作就是将数据渲染到页面➕处理用户交互.在 Vue 中,数据变化时页面会重新渲染,比如我们在页面上显示一个数字,旁边有一个点击按钮,每次点击一下按钮,页面上所显示的数字会加一,这要怎么

  • Vue3响应式方案及ref reactive的区别详解

    目录 一.前言 二.新的方案 1. 缘由 2. Proxy 和 Reflect 1) Proxy 2) Reflect 3. reactive 1) createReactiveObject() 函数 2) mutableHandlers() 函数 -> 对象类型的 handlers 3) mutableInstrumentations() 函数 -> Map Set等类型的 handlers 4. ref 1) createRef() 2) toReactive() 3)proxyRefs(

  • Vue3源码分析reactivity实现方法示例

    目录 深入分析对于map.set.weakMap.weakSet的响应式拦截 (1).mutableInstrumentations (2).shallowInstrumentations (3).readonlyInstrumentations (4).shallowReadonlyInstrumentations ref.computed等方法的实现 (1).ref与shallowRef源码解析 (2).toRefs (4).computed (5)其他api源码 最后总结: 深入分析对于m

  • Vue3源码分析侦听器watch的实现原理

    目录 watch 的本质 watch 的函数签名 侦听多个源 侦听单一源 watch 的实现 watch 函数 source 参数 cb 参数 options 参数 doWatch 函数 doWatch 函数签名 初始化变量 递归读取响应式数据 定义清除副作用函数 封装 scheduler 调度函数 设置 job 的 allowRecurse 属性 flush 选项指定回调函数的执行时机 创建副作用函数 执行副作用函数 返回匿名函数,停止侦听 总结 watch 的本质 所谓的watch,其本质就

  • Vue3源码分析组件挂载初始化props与slots

    目录 前情提要 初始化组件 (1).setupComponent (2).initProps (3).initSlots 额外内容 总结 前情提要 上文我们分析了挂载组件主要调用了三个函数: createComponentInstance(创建组件实例).setupComponent(初始化组件).setupRenderEffect(更新副作用).并且上一节中我们已经详细讲解了组件实例上的所有属性,还包括emit.provide等的实现.本文我们将继续介绍组件挂载流程中的初始化组件. 本文主要内

  • Vue3 源码分析reactive readonly实例

    目录 引言 一.reactive 和 readonly 1. reactive相关类型 2. 相关全局变量与方法 3. reactive函数 4. 造物主createReactiveObject 5. shallowReactive.readonly和shallowReadonly 二.对应的 Handlers 1. baseHandlers 1.1 reactive 1.2 readonly 1.3 shallowReactive 1.4 shallowReadonly 2. cellecti

  • jQuery 1.9.1源码分析系列(十五)动画处理之缓动动画核心Tween

    在jQuery内部函数Animation中调用到了createTweens()来创建缓动动画组,创建完成后的结果为: 可以看到上面的缓动动画组有四个原子动画组成.每一个原子动画的信息都包含在里面了. 仔细查看createTweens函数,实际上就是遍历调用了tweeners ["*"]的数组中的函数(实际上就只有一个元素). function createTweens( animation, props ) { jQuery.each( props, function( prop, v

  • Android透明化和沉浸式状态栏实践及源码分析

    本文所提到的透明状态栏其实指的是将顶部的导航栏延伸到状态栏,使之浑然一体(Google官方建议状态栏颜色比导航栏的颜色略深一点),并不代表一定不设置背景色,比如导航栏是白色,则可设置状态栏为白色,视情况而定. 相比于iOS系统,Android系统对于状态栏的设置就显得稍微复杂了一点.Android系统提供了API 19以上对状态栏的设置接口,而直到API 23以上才提供对于icon颜色的设置,还有就是各家厂商(如魅族,小米等)对于状态栏的有自己的定制,对于需要使用浅色背景状态栏的应用,没处理好的

  • java编程Reference核心原理示例源码分析

    带着问题,看源码针对性会更强一点.印象会更深刻.并且效果也会更好.所以我先卖个关子,提两个问题(没准下次跳槽时就被问到). 我们可以用ByteBuffer的allocateDirect方法,申请一块堆外内存创建一个DirectByteBuffer对象,然后利用它去操作堆外内存.这些申请完的堆外内存,我们可以回收吗?可以的话是通过什么样的机制回收的? 大家应该都知道WeakHashMap可以用来实现内存相对敏感的本地缓存,为什么WeakHashMap合适这种业务场景,其内部实现会做什么特殊处理呢?

随机推荐