欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue3響應(yīng)式方案及ref?reactive的區(qū)別詳解

 更新時間:2022年12月19日 16:28:13   作者:Ali2333  
眾所周知ref和reactive都是用來作響應(yīng)式數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于Vue3響應(yīng)式方案及ref?reactive區(qū)別的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下

一、前言

距離 Vue3 出來了已經(jīng)有一段時間了, 最近呢重溫了一下Vue3的響應(yīng)式方案,以及ref reactive的區(qū)別,相信你看完本文,也能對 Vue3 響應(yīng)式的了解有所提高

在看源碼的過程中,時常會感到框架的設(shè)計之美,看起來也會感到賞心悅目, 這也是能堅持把源碼看下去的動力

二、新的方案

1. 緣由

  • 已知在 Vue2 中, 響應(yīng)式原理一直是采用 Object.defineProperty 來進(jìn)行,那這樣做有什么權(quán)限呢? 下面一一道來

    • 這個API, 只能攔截 get / set 的屬性
    • 如對象 新增 或者 刪除 了屬性,則無法監(jiān)聽到改變
    • 對于數(shù)組,若使用數(shù)組的原生方法改變數(shù)組元素的時候 也無法監(jiān)聽到改變
  • 所以呢在Vue3中采用了 ProxyReflect搭配來代理數(shù)據(jù)

2. Proxy 和 Reflect

1) Proxy

既然Vue3中響應(yīng)式數(shù)據(jù)是基于 Proxy 實(shí)現(xiàn)的,那么什么是Proxy呢?

使用Proxy可以創(chuàng)建一個代理對象,它可以實(shí)現(xiàn)對 對象數(shù)據(jù) 的 代理, 所以它 無法對非對象值進(jìn)行代理,也就是為什么Vue3中對于非對象值要使用 ref 來進(jìn)行響應(yīng)式的原因 (后面講解ref的時候再細(xì)說)

  • 代理是指 允許我們攔截并重新定義對一個對象的基本操作。 例如: 攔截讀取、 修改等操作.
const obj = {}


const newP = new Proxy(obj, {
     // 攔截讀取
  get(){/*...*/ },

  // 攔截設(shè)置屬性操作
  set(){/*...*/ }
})

2) Reflect

說完了Proxy, 接下來我們來說說 Reflect

通過觀察 MDN 官網(wǎng)可以發(fā)現(xiàn), Reflect的方法Proxy的攔截器方法 名字基本一致

那就出現(xiàn)了一個問題,我們為什么要用 Reflect 呢?

主要還是它的第三個參數(shù),你可以理解為函數(shù)調(diào)用過程中的this,我們來看看它配合 Proxy 具體的用途吧

const obj = {
  foo: 1,
  
  // obj 中有一個 getter屬性 通過this獲取foo的值
  get getFoo() { return this.foo; }
};


const newP = new Proxy(obj,
  {
    // 攔截讀取
    get(target, key) {
      console.log('讀取', key); // 注意這里目前沒有使用 Reflect
      return target[key];
    },

    // 攔截設(shè)置屬性操作
    set(target, key, newVal) {
      console.log('修改', key);
      target[key] = newVal
    }
  })

obj.foo++
console.log(newP.getFoo);  

執(zhí)行上面代碼你會發(fā)現(xiàn), 在 Proxy 中 get 攔截的中,只會觸發(fā)對 getFoo 屬性進(jìn)行讀取的攔截, 而無法觸發(fā)在 getFoo 里面對 this.foo 進(jìn)行讀取的攔截!

問題就出現(xiàn)在 getFoo 這個getter里, 這里面的 this 在我們 未使用 Reflect 的時候指向它的原始對象,所以我們才無法通過 Proxy 攔截到屬性讀取

只需修改一下上面代碼中 Proxy 里面的 get 攔截方法

    // 攔截讀取
    get(target, key, receiver) {
      console.log('讀取', key);
      return Reflect.get(target, key, receiver); // 使用 Reflect返回讀取的屬性值
    },

這下再執(zhí)行上面的例子,就會發(fā)現(xiàn)能正常對 getFoo 里面的 foo 屬性進(jìn)行讀取的攔截。 因為這個時候的 this 已經(jīng)指向了代理對象 newP

以上呢,就是對 Proxy 和 Reflect 的簡易講解,接下來我們講講 Vue3 中的 reactive

3. reactive

看源碼會發(fā)現(xiàn),我們平時使用 reactive 的時候,會調(diào)用一個 createReactiveObject 的方法

這個地方在: packages\reactivity\src\reactive

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
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,  // 普通對象的 handlers
    mutableCollectionHandlers, // Set Map 等類型的 handlers
    reactiveMap
  )
}

1) createReactiveObject() 函數(shù)

其中主要是做一些前置判斷,然后建立響應(yīng)式地圖

WeakMap -> Map -> Set

function createReactiveObject(
  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>
) {

  //  若目標(biāo)數(shù)據(jù)是不是對象則直接返回
  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
  // raw 代表原始數(shù)據(jù)
  // 或者是非響應(yīng)式數(shù)據(jù)就直接返回 原數(shù)據(jù)
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  
  // 如已被代理則直接返回代理的這個對象
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  
  // only a whitelist of value types can be observed.
  // 只有在白名單中的類型才可以被代理
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  
  // 建立代理 Proxy
  const proxy = new Proxy(
    target,
    // 使用不同的 hanlders
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  
  // 存儲到響應(yīng)式地圖中
  proxyMap.set(target, proxy)
  return proxy
}

2) mutableHandlers() 函數(shù) -> 對象類型的 handlers

這個地方在: packages\reactivity\src\baseHandlers

主要講講getset

export const mutableHandlers: ProxyHandler<object> = {
  get: createGetter(), // 讀取屬性
  set: createSetter(), // 設(shè)置屬性
  deleteProperty,      // 刪除屬性
  has,                 // 判斷是否存在對應(yīng)屬性
  ownKeys              // 獲取自身的屬性值
}

get

function createGetter(isReadonly = false, shallow = false) {
  return function get(target: Target, key: string | symbol, receiver: object) {
    // 判斷返回一些特定的值 例如 是 readonly 的就返回 readonlyMap,是 reactive 的就返回 reactiveMap 等等
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (key === ReactiveFlags.IS_SHALLOW) {
      return shallow
    } else if (
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }

    // 如果是數(shù)組要進(jìn)行一些特殊處理
    const targetIsArray = isArray(target)

    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      //  重寫數(shù)組的方法
      // 'includes', 'indexOf', 'lastIndexOf', 'push', 'pop', 'shift', 'unshift', 'splice'
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    // 獲取屬性值
    const res = Reflect.get(target, key, receiver)

    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    // 如果非只讀屬性 才進(jìn)行依賴收集
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    // 淺層響應(yīng)則直接返回對應(yīng)的值
    if (shallow) {
      return res
    }


    // 如果是ref 則自動進(jìn)行 脫ref
    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    // 返回值是對象
    // 如果是只讀就用 readonly 包裹返回數(shù)據(jù)
    // 否則則進(jìn)行遞歸深層包裹 reactive 返回 Proxy 代理對象
    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)
    }

    // 如都不是上面的判斷 則返回這個數(shù)據(jù)
    return res
  }
}

set

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    // 緩存舊值
    let oldValue = (target as any)[key]

    if (!shallow && !isReadonly(value)) {
      if (!isShallow(value)) {
        value = toRaw(value)
        oldValue = toRaw(oldValue)
      }

      // 若是 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
    }

    // 是否有對于的key
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)

    // 修改對應(yīng)的值
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original

    // 若目標(biāo)是原型鏈上的內(nèi)容就不觸發(fā)依賴
    if (target === toRaw(receiver)) {

      // 這里主要是判斷是 新增屬性 還是修改屬性的操作
      if (!hadKey) {
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }

    // 最終返回結(jié)果
    return result
  }
}

3) mutableInstrumentations() 函數(shù) -> Map Set等類型的 handlers

這個地方在: packages\reactivity\src\collectionHandlers

其主要是為了解決 代理對象 無法訪問集合類型的屬性和方法

function createInstrumentations() {
   // 主要就是代理了 Map Set等類型的方法 具體實(shí)現(xiàn)各位可以去上面地址中的文件里查看
  const mutableInstrumentations: Record<string, Function> = {
    get(this: MapTypes, key: unknown) {
      return get(this, key)
    },
    get size() {
      return size(this as unknown as IterableCollections)
    },
    has,
    add,
    set,
    delete: deleteEntry,
    clear,
    forEach: createForEach(false, false)
  }

  const iteratorMethods = ['keys', 'values', 'entries', Symbol.iterator]
  iteratorMethods.forEach(method => {
    mutableInstrumentations[method as string] = createIterableMethod(
      method,
      false,
      false
    )
  })

  return [
    mutableInstrumentations
  ]
}

4. ref

之前說過 Proxy 代理的必須是對象數(shù)據(jù)類型,而非對象數(shù)據(jù)類型 例如: string number 等等 則不能用其進(jìn)行代理, 所以有了 ref 的概念

聯(lián)想到上面說的 reactive 和我們?nèi)粘J褂玫?.value 的形式, 是不是就認(rèn)為 ref 直接把原始數(shù)據(jù)包裹成對象 然后通過 Proxy 進(jìn)行代理的呢?

最開始我也以為是這樣,但是查看了源碼中發(fā)現(xiàn)其實(shí)并不是, 其實(shí)是創(chuàng)建 ref 的時候, 實(shí)例化了一個 class -> new RefImpl(rawValue, shallow) ,然后通過自定義的 get set來進(jìn)行依賴收集和依賴更新

源碼地址: packages\reactivity\src\ref

1) createRef()

export function ref(value?: unknown) {
   // 調(diào)用創(chuàng)建方法
  return createRef(value, false)
}

function createRef(rawValue: unknown, shallow: boolean) {
  // 如果已經(jīng)是一個ref 則直接返回
  if (isRef(rawValue)) {
    return rawValue
  }
  
  // 實(shí)例化 class
  return new RefImpl(rawValue, shallow)
}

class RefImpl<T> {
  private _value: T
  private _rawValue: T

  public dep?: Dep = undefined
  
  // 用于區(qū)分 ref 的不可枚舉屬性 例如 isRef 方法就是直接判斷這個屬性
  public readonly __v_isRef = true

  // 構(gòu)造函數(shù)
  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) {
     // 拿到原始值
    newVal = this.__v_isShallow ? newVal : toRaw(newVal)
    
    // 判斷是否有變化 如有才進(jìn)行更新
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = this.__v_isShallow ? newVal : toReactive(newVal)
      // 依賴更新
      triggerRefValue(this, newVal)
    }
  }
}

2) toReactive()

我們?nèi)粘J褂玫臅r候會發(fā)現(xiàn), ref 傳入一個對象 也能正常使用,其玄機(jī)就在 創(chuàng)建class 的時候,構(gòu)造函數(shù)中調(diào)用了 toReactive 這個函數(shù)

export const toReactive = &lt;T extends unknown&gt;(value: T): T =&gt;
  // 如果是一個對象則利用 reactive 代理成 Proxy 返回
  isObject(value) ? reactive(value) : value
復(fù)制代碼

3)proxyRefs() 自動脫 ref

我們在使用 ref 的時候會發(fā)現(xiàn),從 setup 返回的 ref, 在頁面中使用并不需要 .value ,這都?xì)w功 proxyRefs 這個函數(shù),減少了我們在模板中需要判斷 ref 的心智負(fù)擔(dān)

<template>
   // 這里并不需要 .value 
   // 并且如果我 直接在模板的點(diǎn)擊事件中 使用 count++ 響應(yīng)式也不會丟失
  <div @click="count++"> {{ count }} </div>
</template>

const myComponent = {
   setup() {
    const count = ref(0)
    
    return { count }
   }
}

下面我們就來看看 proxyRefs 的實(shí)現(xiàn)

export function proxyRefs<T extends object>(
  objectWithRefs: T
): ShallowUnwrapRef<T> {
   // 如果是 reactive 則不處理
  return isReactive(objectWithRefs)
    ? objectWithRefs
    // 如果是 ref 則直接通過 Proxy 代理一下
    : new Proxy(objectWithRefs, shallowUnwrapHandlers)
}

export function unref<T>(ref: T | Ref<T>): T {
   // 如果是 ref 直接返回 .value 的值
  return isRef(ref) ? (ref.value as any) : ref
}

const shallowUnwrapHandlers: ProxyHandler<any> = {
  // get 的時候直接脫 ref
  get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
  
 
  set: (target, key, value, receiver) => {
    const oldValue = target[key]
    
    // 如果舊值是 ref 而新值不是 ref 直接把 新值 替換 舊值 的.value屬性
    if (isRef(oldValue) && !isRef(value)) {
      oldValue.value = value
      return true
    } else {
      return Reflect.set(target, key, value, receiver)
    }
  }
}

然后我們會發(fā)現(xiàn)在模板調(diào)用中,會自動把setup的返回值通過 proxyRefs 調(diào)用一遍

通過上面的源碼來個總結(jié):

  • 我們在編寫 Vue 組件的時候, 組件中 setup 的函數(shù)所返回的數(shù)據(jù)會自動傳給 proxyRefs 函數(shù)處理一遍,所以我們在頁面中使用 無需 .value
  • ref 最后在 模板中 還是被 Proxy 代理 了一遍

三、 結(jié)語

以上呢就是對 Vue3 的響應(yīng)式的方案解析了, 以及關(guān)于 reactive ref的區(qū)別相信你如果仔細(xì)看完了,也會心知肚明了

到此這篇關(guān)于Vue3響應(yīng)式方案及ref reactive的區(qū)別詳解的文章就介紹到這了,更多相關(guān)Vue3響應(yīng)式及ref reactive區(qū)別內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論