ホームページ > ウェブフロントエンド > Vue.js > Vue3 レスポンシブ コア リアクティブ ソース コード分析

Vue3 レスポンシブ コア リアクティブ ソース コード分析

WBOY
リリース: 2023-05-23 14:04:06
転載
1777 人が閲覧しました

1. 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
  )
}
ログイン後にコピー

When 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 などのコレクション型) には有効ですが、文字列、数値、ブール値などのプリミティブ型には無効です。

if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
}
ログイン後にコピー

ターゲットがすでにプロキシ オブジェクトである場合は、ターゲットを直接返します

if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
}
ログイン後にコピー

ターゲットに対応するプロキシ オブジェクトがすでにある場合は、プロキシ オブジェクトを直接返します

const existingProxy = proxyMap.get(target) // 存储响应式对象
if (existingProxy) {
    return existingProxy
}
ログイン後にコピー

For 観察できない型の場合、ターゲットは直接返されます

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 = &#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)
}
ログイン後にコピー

レスポンシブ オブジェクト (コア コード) の作成

const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
ログイン後にコピー

次に、baseHandlers に焦点を当てます。コールバック関数。

2.baseHandlers

1、baseHandlers

baseHandlers は、baseHandlers ファイルの mutableHandlers です。 。

Vue3 レスポンシブ コア リアクティブ ソース コード分析

mutableHandlers のソース コードは次のとおりです。プロキシはそれぞれ get、set、deleteProperty、has、ownKeys を実行します。

export const mutableHandlers: ProxyHandler<object> = {
  get,
  set,
  deleteProperty,
  has,
  ownKeys
}
ログイン後にコピー

これらのインターセプターの具体的な実装を見てみましょう。

(1), get proxy

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
  }
}
ログイン後にコピー

ターゲットが配列の場合、'push'、'pop'、'shift'、'unshift'、' これらsplice のメソッドは配列の長さを変更し、無限再帰を引き起こします。したがって、依存関係のコレクションを最初に一時停止する必要があるため、配列の上記のメソッドがインターセプトされて書き換えられます。

  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)
  }
ログイン後にコピー

書き換えられたコード:

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
}
ログイン後にコピー

次の図は実行結果です。

Vue3 レスポンシブ コア リアクティブ ソース コード分析

は次のコードで理解できます。

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;)
ログイン後にコピー

特殊な属性は対象外ですdependency collection

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

この手順では、Symbol.iterator、Symbol.toStringTag などのネイティブ シンボル タイプ属性など、いくつかの特別な属性をフィルタリングします。これらの属性は、ビルドされているため依存関係の収集を必要としません。

proto、__v_isRef、__isVue など、追跡できない属性もいくつかあります。これらの属性には依存関係のコレクションは必要ありません。

依存関係のコレクション

// 如果不是只读的,那么进行依赖收集
    if (!isReadonly) {
        track(target, "get" /* TrackOpTypes.GET */, key);
    }
ログイン後にコピー

Shallow は再帰プロキシを実行しません

if (shallow) {
  return res;
}
ログイン後にコピー

戻り値をアンパックします

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

このステップは ref 状況を処理するためのものです。res が ref の場合は、res をアンパックします。以下に A の判断を示します。配列でキーが整数型の場合はアンパックされません。リアクティブは応答性が高いため、属性が ref

の再帰プロキシの場合はアンパックする必要があります。 object

 // 如果 res 是对象,那么对返回的值进行递归代理
    if (isObject(res)) {
        return isReadonly ? readonly(res) : reactive(res);
    }
ログイン後にコピー

(2)、setagent

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
  }
}
ログイン後にコピー

メインロジック:

古い値を取得

let oldValue = target[key];
ログイン後にコピー

古い値は読み取り

// 如果旧值是只读的,并且是 ref,并且新值不是 ref,那么直接返回 false,代表设置失败
    if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
        return false;
    }
ログイン後にコピー
のみです

以上がVue3 レスポンシブ コア リアクティブ ソース コード分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:yisu.com
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート