vue3中reactive和ref的區(qū)別是什么

這篇文章主要講解了“vue3中reactive和ref的區(qū)別是什么”,文中的講解內(nèi)容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“vue3中reactive和ref的區(qū)別是什么”吧!

成都創(chuàng)新互聯(lián)公司成立于2013年,先為渾源等服務(wù)建站,渾源等地企業(yè),進行企業(yè)商務(wù)咨詢服務(wù)。為渾源企業(yè)網(wǎng)站制作PC+手機+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

reactive

源碼地址:packages/reactivity/reactive.ts

首先我們看一下vue3中用來標記目標對象target類型的ReactiveFlags

// 標記目標對象 target 類型的 ReactiveFlags
export const enum ReactiveFlags {
  SKIP = '__v_skip',
  IS_REACTIVE = '__v_isReactive',
  IS_READONLY = '__v_isReadonly',
  RAW = '__v_raw'
}

export interface Target {
  [ReactiveFlags.SKIP]?: boolean          // 不做響應(yīng)式處理的數(shù)據(jù)
  [ReactiveFlags.IS_REACTIVE]?: boolean   // target 是否是響應(yīng)式
  [ReactiveFlags.IS_READONLY]?: boolean   // target 是否是只讀
  [ReactiveFlags.RAW]?: any               // 表示proxy 對應(yīng)的源數(shù)據(jù), target 已經(jīng)是 proxy 對象時會有該屬性
}

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.
  // 如果目標對象是一個只讀的響應(yīng)數(shù)據(jù),則直接返回目標對象
  if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
    return target
  }
  // 創(chuàng)建 observe
  return createReactiveObject(
    target,
    false,
    mutableHandlers,
    mutableCollectionHandlers,
    reactiveMap
  )
}

reactive函數(shù)接收一個target對象,如果target對象只讀則直接返回該對象

若非只讀則直接通過createReactiveObject創(chuàng)建observe對象

createReactiveObject

看著長不要怕,先貼createReactiveObject完整代碼,我們分段閱讀

/**
 * 
 * @param target 目標對象
 * @param isReadonly 是否只讀
 * @param baseHandlers 基本類型的 handlers
 * @param collectionHandlers 主要針對(set、map、weakSet、weakMap)的 handlers
 * @param proxyMap  WeakMap數(shù)據(jù)結(jié)構(gòu)
 * @returns 
 */

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

  // typeof 不是 object 類型的,在開發(fā)模式拋出警告,生產(chǎn)環(huán)境直接返回目標對象
  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
  // 已經(jīng)是響應(yīng)式的就直接返回(取ReactiveFlags.RAW 屬性會返回true,因為進行reactive的過程中會用weakMap進行保存,
  // 通過target能判斷出是否有ReactiveFlags.RAW屬性)
  // 例外:對reactive對象進行readonly()
  if (
    target[ReactiveFlags.RAW] &&
    !(isReadonly && target[ReactiveFlags.IS_REACTIVE])
  ) {
    return target
  }
  // target already has corresponding Proxy
  // 對已經(jīng)Proxy的,則直接從WeakMap數(shù)據(jù)結(jié)構(gòu)中取出這個Proxy對象
  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }
  // only a whitelist of value types can be observed.
  // 只對targetTypeMap類型白名單中的類型進行響應(yīng)式處理
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }
  // proxy 代理 target
  // (set、map、weakSet、weakMap) collectionHandlers
  // (Object、Array) baseHandlers
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

首先我們看到createReactiveObject接收了五個參數(shù)

  target: Target,
  isReadonly: boolean,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>,
  proxyMap: WeakMap<Target, any>

target 目標對象

isReadonly 是否只讀

baseHandlers 基本類型的 handlers處理數(shù)組,對象

collectionHandlers處理 set、map、weakSet、weakMap

proxyMapWeakMap數(shù)據(jù)結(jié)構(gòu)存儲副作用函數(shù)


這里主要是通過ReactiveFlags.RAWReactiveFlags.IS_REACTIVE判斷是否是響應(yīng)式數(shù)據(jù),若是則直接返回該對象

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

對于已經(jīng)是Proxy的,則直接從WeakMap數(shù)據(jù)結(jié)構(gòu)中取出這個Proxy對象并返回

  const existingProxy = proxyMap.get(target)
  if (existingProxy) {
    return existingProxy
  }

這里則是校驗了一下當前target的類型是不是ObjectArray、Map、Set、WeakMapWeakSet,如果都不是則直接返回該對象,不做響應(yīng)式處理

 // 只對targetTypeMap類型白名單中的類型進行響應(yīng)式處理
  const targetType = getTargetType(target)
  if (targetType === TargetType.INVALID) {
    return target
  }

校驗類型的邏輯

function getTargetType(value: Target) {
  return value[ReactiveFlags.SKIP] || !Object.isExtensible(value)
    ? TargetType.INVALID
    : targetTypeMap(toRawType(value))
}

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
  }
}

所有的前置校驗完后,就可以使用proxy 代理target對象了

這里使用了一個三目運算符通過TargetType.COLLECTION來執(zhí)行不同的處理邏輯

  • (set、map、weakSet、weakMap) 使用 collectionHandlers

  • (Object、Array) 使用 baseHandlers

// proxy 代理 target
  // (set、map、weakSet、weakMap) collectionHandlers
  // (Object、Array) baseHandlers
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy

現(xiàn)在對createReactiveObject的執(zhí)行邏輯是不是就很清晰了

到這里還沒有結(jié)束,createReactiveObject中最后proxy是如何去代理target的呢?這里我們用baseHandlers舉例,深入baseHandlers的內(nèi)部去看看

baseHandlers

源碼地址:packages/reactivity/baseHandlers.ts

reactive.ts中我們可以看到一共引入了四種 handler

import {
  mutableHandlers,
  readonlyHandlers,
  shallowReactiveHandlers,
  shallowReadonlyHandlers
} from './baseHandlers'
  • mutableHandlers 可變處理

  • readonlyHandlers 只讀處理

  • shallowReactiveHandlers 淺觀察處理(只觀察目標對象的第一層屬性)

  • shallowReadonlyHandlers 淺觀察 && 只讀

我們以mutableHandlers為例

// 可變處理
// const get = /*#__PURE__*/ createGetter()
// const set = /*#__PURE__*/ createSetter()
// get、has、ownKeys 會觸發(fā)依賴收集 track()
// set、deleteProperty 會觸發(fā)更新 trigger()
export const mutableHandlers: ProxyHandler<object> = {
  get,                  // 用于攔截對象的讀取屬性操作
  set,                  // 用于攔截對象的設(shè)置屬性操作
  deleteProperty,       // 用于攔截對象的刪除屬性操作
  has,                  // 檢查一個對象是否擁有某個屬性
  ownKeys               // 針對 getOwnPropertyNames,  getOwnPropertySymbols, keys 的代理方法
}

這里的getset分別對應(yīng)著createGetter()、createSetter()

  • createGetter()

先上完整版代碼

/**
 * 用于攔截對象的讀取屬性操作
 * @param isReadonly 是否只讀
 * @param shallow 是否淺觀察
 * @returns 
 */
function createGetter(isReadonly = false, shallow = false) {
  /**
   * @param target 目標對象
   * @param key 需要獲取的值的鍵值
   * @param receiver 如果遇到 setter,receiver 則為setter調(diào)用時的this值
   */
  return function get(target: Target, key: string | symbol, receiver: object) {
    // ReactiveFlags 是在reactive中聲明的枚舉值,如果key是枚舉值則直接返回對應(yīng)的布爾值
    if (key === ReactiveFlags.IS_REACTIVE) {
      return !isReadonly
    } else if (key === ReactiveFlags.IS_READONLY) {
      return isReadonly
    } else if (
      // 如果key是raw  receiver 指向調(diào)用者,則直接返回目標對象。
      // 這里判斷是為了保證觸發(fā)攔截 handle 的是 proxy 本身而不是 proxy 的繼承者
      // 觸發(fā)攔的兩種方式:一是訪問 proxy 對象本身的屬性,二是訪問對象原型鏈上有 proxy 對象的對象的屬性,因為查詢會沿著原型鏈向下找
      key === ReactiveFlags.RAW &&
      receiver ===
        (isReadonly
          ? shallow
            ? shallowReadonlyMap
            : readonlyMap
          : shallow
          ? shallowReactiveMap
          : reactiveMap
        ).get(target)
    ) {
      return target
    }

    const targetIsArray = isArray(target)
    // 如果目標對象 不為只讀、是數(shù)組、key屬于arrayInstrumentations:['includes', 'indexOf', 'lastIndexOf']方法之一,即觸發(fā)了這三個方法之一
    if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
      // 通過 proxy 調(diào)用,arrayInstrumentations[key]的this一定指向 proxy
      return Reflect.get(arrayInstrumentations, key, receiver)
    }

    const res = Reflect.get(target, key, receiver)

    // 如果 key 是 symbol 內(nèi)置方法,或者訪問的是原型對象__proto__,直接返回結(jié)果,不收集依賴
    if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
      return res
    }

    // 不是只讀類型的 target 就收集依賴。因為只讀類型不會變化,無法觸發(fā) setter,也就會觸發(fā)更新
    if (!isReadonly) {
      track(target, TrackOpTypes.GET, key)
    }

    // 如果是淺觀察,不做遞歸轉(zhuǎn)化,就是說對象有屬性值還是對象的話不遞歸調(diào)用 reactive()
    if (shallow) {
      return res
    }

    // 如果get的結(jié)果是ref
    if (isRef(res)) {
      // ref unwrapping - does not apply for Array + integer key.
      // 返回 ref.value,數(shù)組除外
      const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
      return shouldUnwrap ? res.value : res
    }

    // 由于 proxy 只能代理一層,如果子元素是對象,需要遞歸繼續(xù)代理
    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
  }
}

看著長,最終就是track()依賴收集

  • createSetter()

/**
 * 攔截對象的設(shè)置屬性操作
 * @param shallow 是否是淺觀察
 * @returns 
 */
function createSetter(shallow = false) {
  /**
   * @param target 目標對象
   * @param key 設(shè)置的屬性名稱
   * @param value 要改變的屬性值
   * @param receiver 如果遇到setter,receiver則為setter調(diào)用時的this值
   */
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key]
    // 如果模式不是淺觀察模式
    if (!shallow) {
      // 拿新值和老值的原始值,因為新傳入的值可能是響應(yīng)式數(shù)據(jù),如果直接和 target 上原始值比較是沒有意義的
      value = toRaw(value)
      oldValue = toRaw(oldValue)
      // 目標對象不是數(shù)組,舊值是ref,新值不是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
    }
    // 檢查對象是否有這個屬性
    const hadKey =
      isArray(target) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key)
    // 賦值    
    const result = Reflect.set(target, key, value, receiver)
    // don't trigger if target is something up in the prototype chain of original
    // reactive是proxy實例才觸發(fā)更新,防止通過原型鏈觸發(fā)攔截器觸發(fā)更新
    if (target === toRaw(receiver)) {
      if (!hadKey) {
        // 如果不存在則trigger ADD
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        // 如果新舊值不相等則trigger SET
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

trigger()觸發(fā)更新

ref

源碼地址:packages/reactivity/src/ref.ts

接收一個可選unknown,接著直接調(diào)用createRef()

export function ref(value?: unknown) {
  return createRef(value, false)
}

vue3中reactive和ref的區(qū)別是什么

ref的區(qū)別就是在調(diào)用createRef()時第二個值傳的是true

export function shallowRef(value?: unknown) {
  return createRef(value, true)
}

看一下官方文檔上對shallowRef的解釋

vue3中reactive和ref的區(qū)別是什么

createRef

通過isRef()判斷是否是ref數(shù)據(jù),是則直接返回該數(shù)據(jù),不是則通過new RefImpl創(chuàng)建ref數(shù)據(jù)

在創(chuàng)建時會傳兩個值一個是rawValue(原始值),一個是shallow(是否是淺觀察),具體使用場景可看上面refshallowRef的介紹

function createRef(rawValue: unknown, shallow: boolean) {
  // 是否是 ref 數(shù)據(jù)
  if (isRef(rawValue)) {
    return rawValue
  }
  return new RefImpl(rawValue, shallow)
}
  • isRef()

通過__v_isRef只讀屬性判斷是否是ref數(shù)據(jù),此屬性會在RefImpl創(chuàng)建ref數(shù)據(jù)時添加

export function isRef(r: any): r is Ref {
  return Boolean(r && r.__v_isRef === true)
}

RefImpl

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

  public dep?: Dep = undefined
  // 只讀屬性 __v_isRef 判斷是否是ref數(shù)據(jù)的靜態(tài)標識
  public readonly __v_isRef = true

  constructor(value: T, public readonly _shallow: boolean) {
    this._rawValue = _shallow ? value : toRaw(value)  // 非淺觀察用toRaw()包裹原始值
    this._value = _shallow ? value : toReactive(value) // 非淺觀察用toReactive()處理數(shù)據(jù)
  }

  get value() {
  // 依賴收集
    trackRefValue(this)
    return this._value
  }

  set value(newVal) {
    newVal = this._shallow ? newVal : toRaw(newVal) // 非淺觀察用toRaw()包裹值
    // 兩個值不相等
    if (hasChanged(newVal, this._rawValue)) {
      this._rawValue = newVal
      this._value = this._shallow ? newVal : toReactive(newVal)
      triggerRefValue(this, newVal) // 觸發(fā)依賴,派發(fā)更新
    }
  }
}

根據(jù)RefImpl我們可以看到ref的底層邏輯,如果是對象確實會使用reactive進行處理,并且ref的創(chuàng)建使用的也是RefImpl class實例,value只是RefImpl的屬性

在我們訪問設(shè)置 ref的value值時,也分別是通過getset攔截進行依賴收集派發(fā)更新

  • toReactive

我們來看一下toReactive()這個方法,在RefImpl中創(chuàng)建ref數(shù)據(jù)時會調(diào)用toReactive()方法,這里會先判斷傳進來的值是不是對象,如果是就用reactive()包裹,否則就返回其本身

export const toReactive = <T extends unknown>(value: T): T =>
  isObject(value) ? reactive(value) : value
  • trackRefValue

ref的依賴收集方法

export function trackRefValue(ref: RefBase<any>) {
  if (isTracking()) {
    ref = toRaw(ref)
    if (!ref.dep) {
      ref.dep = createDep()
    }
    if (__DEV__) {
      trackEffects(ref.dep, {
        target: ref,
        type: TrackOpTypes.GET,
        key: 'value'
      })
    } else {
      trackEffects(ref.dep)
    }
  }
}
  • triggerRefValue

ref的派發(fā)更新方法

export function triggerRefValue(ref: RefBase<any>, newVal?: any) {
  ref = toRaw(ref)
  if (ref.dep) {
    if (__DEV__) {
      triggerEffects(ref.dep, {
        target: ref,
        type: TriggerOpTypes.SET,
        key: 'value',
        newValue: newVal
      })
    } else {
      triggerEffects(ref.dep)
    }
  }
}

總結(jié)

看完reactiveref源碼,相信對本文一開始的幾個問題也都有了答案,這里也總結(jié)了幾個問題:

  • 問:ref的底層邏輯是什么,具體是如何實現(xiàn)的

答:ref底層會通過 new RefImpl()來創(chuàng)造ref數(shù)據(jù),在new RefImpl()會首先給數(shù)據(jù)添加__v_isRef只讀屬性用來標識ref數(shù)據(jù)。而后判斷傳入的值是否是對象,如果是對象則使用toReactive()處理成reactive,并將值賦給RefImpl()value屬性上。在訪問設(shè)置ref數(shù)據(jù)的value時會分別觸發(fā)依賴收集派發(fā)更新流程。


  • 問:ref底層是否會使用reactive處理數(shù)據(jù)

答:RefImpl中非淺觀察會調(diào)用toReactive()方法處理數(shù)據(jù),toReactive()中會先判斷傳入的值是不是一個對象,如果是對象則使用reactive進行處理,不是則直接返回值本身。


  • 問:為什么已經(jīng)有了reactive還需要在設(shè)計一個ref呢?

答: 因為vue3響應(yīng)式方案使用的是proxy,而proxy的代理目標必須是非原始值,沒有任何方式能去攔截對原始值的操作,所以就需要一層對象作為包裹,間接實現(xiàn)原始值的響應(yīng)式方案。


  • 問:為什么ref數(shù)據(jù)必須要有個value屬性,訪問ref數(shù)據(jù)必須要通過.value的方式呢?

答:這是因為要解決響應(yīng)式丟失的問題,舉個例子:

// obj是響應(yīng)式數(shù)據(jù)
const obj = reactive({ foo: 1, bar: 2 })

// newObj 對象下具有與 obj對象同名的屬性,并且每個屬性值都是一個對象
// 該對象具有一個訪問器屬性 value,當讀取 value的值時,其實讀取的是 obj 對象下相應(yīng)的屬性值 
const newObj = {
    foo: {
        get value() {
            return obj.foo
        }
    },
    bar: {
        get value() {
            return obj.bar
        }
    }
}

effect(() => {
    // 在副作用函數(shù)內(nèi)通過新對象 newObj 讀取 foo 的屬性值
    console.log(newObj.foo)
})
// 正常觸發(fā)響應(yīng)
obj.foo = 100

可以看到,在現(xiàn)在的newObj對象下,具有與obj對象同名的屬性,而且每個屬性的值都是一個對象,例如foo 屬性的值是:

{
    get value() {
        return obj.foo
    }
}

該對象有一個訪問器屬性value,當讀取value的值時,最終讀取的是響應(yīng)式數(shù)據(jù)obj下的同名屬性值。也就是說,當在副作用函數(shù)內(nèi)讀取newObj.foo時,等價于間接讀取了obj.foo的值。這樣響應(yīng)式數(shù)據(jù)就能夠與副作用函數(shù)建立響應(yīng)聯(lián)系

感謝各位的閱讀,以上就是“vue3中reactive和ref的區(qū)別是什么”的內(nèi)容了,經(jīng)過本文的學習后,相信大家對vue3中reactive和ref的區(qū)別是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識點的文章,歡迎關(guān)注!

文章標題:vue3中reactive和ref的區(qū)別是什么
文章起源:http://bm7419.com/article22/jdigcc.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供動態(tài)網(wǎng)站、品牌網(wǎng)站制作、外貿(mào)網(wǎng)站建設(shè)移動網(wǎng)站建設(shè)、手機網(wǎng)站建設(shè)、標簽優(yōu)化

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站建設(shè)網(wǎng)站維護公司