Rumah  >  Artikel  >  hujung hadapan web  >  Analisis kod sumber reaktif teras responsif Vue3

Analisis kod sumber reaktif teras responsif Vue3

WBOY
WBOYke hadapan
2023-05-23 14:04:061717semak imbas

1. Kod sumber reaktif

1. laluan kod sumber reaktif:

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
  )
}
packages/reactivity/src/reactive.tsApabila kami melaksanakan

, ini akan menjadi kaedah Kilang dilaksanakan, mengembalikan objek reaktif.

reactive({})2. Seterusnya, lihat kaedah kilang createReactiveObjectcreateReactiveObject

Laluan kod sumber:

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
}

Hanya sah untuk jenis objek (objek, tatasusunan dan jenis koleksi seperti Map and Set) , yang tidak sah untuk jenis primitif seperti rentetan, nombor dan boolean. packages/reactivity/src/reactive.ts

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

Jika sasaran sudah menjadi objek proksi, maka teruskan kembali sasaran

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

Jika sasaran sudah mempunyai objek proksi yang sepadan, maka teruskan kembali objek proksi

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

Untuk Jenis yang tidak boleh diperhatikan terus mengembalikan sasaran

const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
    return target
}
rrree

Buat objek responsif (kod teras)

// 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 = &#39;__v_skip&#39;, 
  // 用于标识一个对象是否是响应式的代理,对应的值是 __v_isReactive
  IS_REACTIVE = &#39;__v_isReactive&#39;, 
  // 用于标识一个对象是否是只读的代理,对应的值是 __v_isReadonly
  IS_READONLY = &#39;__v_isReadonly&#39;,
  // 用于标识一个对象是否是浅层代理,对应的值是 __v_isShallow
  IS_SHALLOW = &#39;__v_isShallow&#39;,
  // 用于保存原始对象的 key,对应的值是 __v_raw
  RAW = &#39;__v_raw&#39;
}

// targetTypeMap
function targetTypeMap(rawType: string) {
  switch (rawType) {
    case &#39;Object&#39;:
    case &#39;Array&#39;:
      return TargetType.COMMON
    case &#39;Map&#39;:
    case &#39;Set&#39;:
    case &#39;WeakMap&#39;:
    case &#39;WeakSet&#39;:
      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)
}

Yang berikut akan memfokuskan pada fungsi panggil balik

.

2. baseHandlersbaseHandlers

1. baseHandlers

ialah

, yang datang daripada fail

. baseHandlersmutableHandlersbaseHandlers

Kod sumber mutableHandlers adalah seperti berikut, proksi mana yang masing-masing mendapat, menetapkan, memadamProperty, mempunyai dan ownKeys. Analisis kod sumber reaktif teras responsif Vue3

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

Mari kita lihat pelaksanaan khusus pemintas ini.

(1), dapatkan proksi

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}
Apabila sasaran ialah tatasusunan, 'tolak', 'pop', 'shift', 'unshift', ' splice ' kaedah akan mengubah panjang tatasusunan dan menyebabkan rekursi tak terhingga Oleh itu, pengumpulan kebergantungan mesti dijeda dahulu, jadi kaedah tatasusunan di atas dipintas dan ditulis semula
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 &#39;includes&#39;, &#39;indexOf&#39;, &#39;lastIndexOf&#39;
        return Reflect.get(arrayInstrumentations, key, receiver)
      }
      // 如果访问的是 hasOwnProperty 方法,那么返回 hasOwnProperty 方法
      if (key === &#39;hasOwnProperty&#39;) {
        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
  }
}

Kod ditulis semula:

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

Gambar berikut ialah hasil pelaksanaan:

boleh difahami dengan kod berikut: Analisis kod sumber reaktif teras responsif Vue3

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)
  ;([&#39;push&#39;, &#39;pop&#39;, &#39;shift&#39;, &#39;unshift&#39;, &#39;splice&#39;] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      // 由于上面的方法会改变数组长度,因此暂停收集依赖,不然会导致无限递归
      console.log(&#39;----自定义push等入口:this, args, key&#39;);
      pauseTracking()
      console.log(&#39;----自定义push等暂停收集依赖&执行开始&#39;)
      // 调用原始方法
      const res = (toRaw(this) as any)[key].apply(this, args)
      console.log(&#39;----自定义push等暂停收集依赖&执行结束&#39;)
      //复原依赖收集
      resetTracking()
      return res
    }
  })
  return instrumentations
}

Atribut khas tidak mengumpul kebergantungan

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

proxy.push(&#39;99&#39;)

Langkah ini adalah untuk menapis beberapa atribut khas, seperti atribut jenis Simbol asli, seperti: Symbol.iterator, Symbol.toStringTag, dll. Atribut ini tidak memerlukan pengumpulan kebergantungan kerana ia terbina dalam dan tidak akan berubah ;

Terdapat juga beberapa atribut yang tidak dapat dikesan, seperti:

proto

, __v_isRef, __isVue ini tidak memerlukan pengumpulan pergantungan

Koleksi pergantungan

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

Shallow tidak melakukan proksi rekursif

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

Buka pek nilai pulangan

if (shallow) {
  return res;
}

Langkah ini adalah untuk mengendalikan situasi ref, kemudian bongkarkan res. jika ia adalah tatasusunan dan kuncinya ialah jenis integer, maka ia tidak akan dibongkar kerana reaktif sangat responsif, ia mesti dibongkar jika atribut adalah ref

Proksi rekursif objek

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

(2), proksi set

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

Dapatkan nilai lama

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(&#39;----set&#39;, target, key, value)

    // 是数组 & key是整型数字 ? 
    // 如果 key 小于数组的长度,那么就是有这个 key : 
    // 如果不是数组,那么就是普通对象,直接判断是否有这个 key
    // 数组会触发两次set: index和新增的值 和 &#39;length&#39;和新增之后的数组长度
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key) 
    // 设置key-value 
    const result = Reflect.set(target, key, value, receiver)
    // don&#39;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
  }
}

Nilai sama ada nilai lama itu sahaja Baca

rreeee

Atas ialah kandungan terperinci Analisis kod sumber reaktif teras responsif Vue3. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan:
Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam