Heim > Web-Frontend > View.js > Vue3 responsive Core-reaktive Quellcode-Analyse

Vue3 responsive Core-reaktive Quellcode-Analyse

WBOY
Freigeben: 2023-05-23 14:04:06
nach vorne
1776 Leute haben es durchsucht

1. Reaktiver Quellcode

1. reaktiv

Quellcodepfad: 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
  )
}
Nach dem Login kopieren
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
}
Nach dem Login kopieren

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

2、接着看工厂方法createReactiveObject

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

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

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

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

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

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

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

const targetType = getTargetType(target)
if (targetType === TargetType.INVALID) {
    return target
}
Nach dem Login kopieren

对于不能被观察的类型,直接返回 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 = &#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)
}
Nach dem Login kopieren
const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
Nach dem Login kopieren

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

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

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

二、baseHandlers

1、baseHandlers

baseHandlersmutableHandlers, 来自于 baseHandlersWenn wir reactive({}) ausführen, wird die Factory-Methode createReactiveObject ausgeführt und ein reaktives Objekt zurückgegeben.

2. Schauen Sie sich als Nächstes die Factory-Methode createReactiveObjectVue3 responsive Core-reaktive Quellcode-Analyse

Quellcodepfad an: packages/reactivity/src/reactive.ts

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
  }
}
Nach dem Login kopieren
#🎜🎜 #Nur gültig für Objekttypen (Objekte, Arrays und Sammlungstypen wie Map und Set), aber nicht gültig für primitive Typen wie String, Number und Boolean.

  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)
  }
Nach dem Login kopieren

Wenn das Ziel bereits ein Proxy-Objekt ist, geben Sie das Ziel direkt zurück.

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
}
Nach dem Login kopieren

Wenn das Ziel bereits über ein entsprechendes Proxy-Objekt verfügt, geben Sie das Proxy-Objekt direkt zurück.#🎜 🎜#

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;)
Nach dem Login kopieren
Für Typen, die nicht beobachtet werden können, geben Sie das Ziel direkt zurück
// 如果是内置的 Symbol,或者是不可追踪的 key,那么直接返回 res
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
        return res;
    }
Nach dem Login kopieren
// 如果不是只读的,那么进行依赖收集
    if (!isReadonly) {
        track(target, "get" /* TrackOpTypes.GET */, key);
    }
Nach dem Login kopieren

Erstellen Sie reaktionsfähige Objekte (Kerncode)

if (shallow) {
  return res;
}
Nach dem Login kopieren

Das Folgende konzentriert sich auf baseHandlers Diese Rückruffunktion.

2. baseHandlers

1. baseHandlers

Vue3 responsive Core-reaktive Quellcode-AnalysebaseHandlers ist mutableHandlers, von < code>baseHandlers-Datei.

Der Quellcode von mutableHandlers lautet wie folgt: Welche Proxys erhalten, setzen, löschenProperty, has und ownKeys?

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

Werfen wir einen Blick auf die spezifische Implementierung dieser Abfangjäger.

(1), Proxy abrufen

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

Wenn das Ziel ein Array ist, 'push', 'pop', 'shift', „unshift“, „splice“, diese Methoden ändern die Länge des Arrays und verursachen eine unendliche Rekursion. Daher muss die Sammlung von Abhängigkeiten zuerst angehalten werden, damit die oben genannten Methoden des Arrays abgefangen und neu geschrieben werden.

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
  }
}
Nach dem Login kopieren

Umschreiben des Codes:

let oldValue = target[key];
Nach dem Login kopieren

Das Bild unten ist das Ausführungsergebnis:

kann mit dem verstanden werden folgender Code:

// 如果旧值是只读的,并且是 ref,并且新值不是 ref,那么直接返回 false,代表设置失败
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
        return false;
    }
Nach dem Login kopieren
Keine Abhängigkeitssammlung für spezielle Attribute

rrreee

In diesem Schritt werden einige spezielle Attribute gefiltert, z. B. native Symboltypattribute wie: Symbol.iterator, Symbol.toStringTag usw. Diese Eigenschaften erfordern keine Abhängigkeitssammlung, da sie integriert sind und sich nicht ändern #, __v_isRef, __isVue usw. Für die Sammlung ist keine Abhängigkeit von Attributen erforderlich. 🎜#rrreee

Dieser Schritt dient der Behandlung der Referenzsituation Beurteilung hier. Wenn es sich um ein Array handelt und der Schlüssel ein ganzzahliger Typ ist, wird das Entpacken nicht durchgeführt. Da Reactive sehr reaktionsfähig ist, muss das Attribut mit ref

entpackt werden des Objekts

rrreee

#🎜🎜#(2), set Agent#🎜🎜##🎜🎜#rrreee#🎜🎜#Hauptlogik: #🎜🎜##🎜🎜#Alten Wert abrufen #🎜🎜#rrreee#🎜🎜#Beurteilen Sie, ob der alte Wert schreibgeschützt ist#🎜🎜#rrreee

Das obige ist der detaillierte Inhalt vonVue3 responsive Core-reaktive Quellcode-Analyse. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Verwandte Etiketten:
Quelle:yisu.com
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage