最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

詳解Vue3中響應(yīng)式的特殊處理

 更新時(shí)間:2023年04月11日 10:41:21   作者:Gill  
這篇文章主要為大家詳細(xì)介紹了Vue3中響應(yīng)式的一些特殊處理,文中的示例代碼講解詳細(xì),對(duì)我們深入了解Vue3有一定的幫助,需要的可以參考一下

vue2 vs vue3

兩個(gè)響應(yīng)式更新的核心區(qū)別在于Object.definePropertyProxy 兩個(gè)api 問(wèn)題,經(jīng)過(guò)這兩個(gè) api 能解決主要的響應(yīng)式問(wèn)題。對(duì)于一些情況需要特殊處理

vue2 中不能實(shí)現(xiàn)的響應(yīng)式

  • arr.length
  • arr[0] = newVal
  • obj[newKey] = value
  • delete obj.key

對(duì)于這些情況 vue2 中通過(guò)增加Vue.$set和重寫(xiě)數(shù)組方法來(lái)實(shí)現(xiàn)。然而對(duì)于 vue3 中,因?yàn)?proxy 是代理整個(gè)對(duì)象,所以它天生支持一個(gè)Object.defineProperty 不能支持的特性,比如他能偵聽(tīng)到添加新屬性,而 Object.defineProperty因?yàn)榇淼氖敲恳粋€(gè) key 所以它對(duì)于新增的屬性并不能知道。諸如此類(lèi),下面列出一些vue3 中不同的響應(yīng)式處理。

新增屬性的更新

proxy 雖然能夠監(jiān)聽(tīng)到新屬性的添加,但新增的屬性并沒(méi)有經(jīng)過(guò)像已有屬性那樣的 getter 收集依賴(lài),也就是并不能觸發(fā)更新。所以我們的目的變成如何收集響應(yīng)

首先,我們先看下 vue3 中是如何處理 for...in.. 循環(huán)的,可以知道的是循環(huán)的內(nèi)部使用了Reflect.ownKeys(obj) 來(lái)獲取只屬于對(duì)象自身?yè)碛械逆I。所以對(duì)于 for..in 循環(huán)的攔截就可以清楚了

const obj = {foo: 1}
const ITERATE_KEY = symbol()
const p = new Proxy(obj, {
   track(target, ITERATE_KEY)
   return Reflect.ownKeys(target)
})

這里,我們用了一個(gè)symbol 的數(shù)據(jù)作為 收集依賴(lài)的key 因?yàn)檫@個(gè)是我們?cè)诒闅v中的攔截操作沒(méi)有與具體的 key 關(guān)聯(lián),而是一個(gè)整體性的攔截。在觸發(fā)響應(yīng)時(shí),只要觸發(fā)這個(gè) symbol 收集的 effect 就可以

trigger(target, isArray(target) ? 'length' : ITERATE_KEY) // 數(shù)組的情況追蹤 length

這里會(huì)發(fā)生影響遍歷對(duì)象長(zhǎng)度時(shí),會(huì)引ITERATE_KEY 相關(guān)的副作用函數(shù)執(zhí)行

effect(() => {
    for(let i in obj) {
        console.log(i)
    }
})

副作用函數(shù)執(zhí)行后,類(lèi)比我們執(zhí)行了渲染函數(shù)。 然后回到我們的新增屬性,

p.newKey = 1

因?yàn)樾略鰧傩?,?huì)對(duì)for.. in .. 循環(huán)產(chǎn)生影響,所以我們需要把與 ITERATE_KEY 相關(guān)的副作用函數(shù)拿出來(lái)重新執(zhí)行,看看源碼中這塊的處理

首先這里是 setter 的處理

function createSetter(shallow = false) {
  return function set(
    target: object,
    key: string | symbol,
    value: unknown,
    receiver: object
  ): boolean {
    let oldValue = (target as any)[key]
    ...
    // 這里是表明是否有 key 也就是判斷是否是新增元素
    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
    if (target === toRaw(receiver)) {
      if (!hadKey) {
         // 這里表明是新增元素時(shí),走的 trigger 邏輯
        trigger(target, TriggerOpTypes.ADD, key, value)
      } else if (hasChanged(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue)
      }
    }
    return result
  }
}

然后是 具體的 trigger,拿到對(duì)應(yīng)的標(biāo)識(shí)去更新effect

export function trigger(
  target: object,
  type: TriggerOpTypes,
  key?: unknown,
  newValue?: unknown,
  oldValue?: unknown,
  oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
  const depsMap = targetMap.get(target)
  if (!depsMap) {
    // never been tracked
    return
  }
    ...

    // also run for iteration key on ADD | DELETE | Map.SET
    switch (type) {
    // 這種情況就是我們剛剛確定的 trigger 的執(zhí)行
      case TriggerOpTypes.ADD:
        if (!isArray(target)) {
        // 拿到收集的 ITERATE_KEY 的依賴(lài)
          deps.push(depsMap.get(ITERATE_KEY))
          if (isMap(target)) {
            deps.push(depsMap.get(MAP_KEY_ITERATE_KEY))
          }
        } else if (isIntegerKey(key)) {
          // new index added to array -> length changes
          deps.push(depsMap.get('length'))
        }
        break
         ...
  }
    for (const dep of deps) {
      if (dep) {
        effects.push(...dep)
      }
    }
    ...
    triggerEffects(createDep(effects))
    ...
  }
}

function triggerEffect(
  effect: ReactiveEffect,
  debuggerEventExtraInfo?: DebuggerEventExtraInfo
) {
  if (effect !== activeEffect || effect.allowRecurse) {
    if (__DEV__ && effect.onTrigger) {
      effect.onTrigger(extend({ effect }, debuggerEventExtraInfo))
    }
    if (effect.scheduler) {
      effect.scheduler()
    } else {
     // 執(zhí)行所有的 effect 函數(shù)
      effect.run()
    }
  }
}

總結(jié):

在遍歷對(duì)象或數(shù)組時(shí),用一個(gè)唯一標(biāo)識(shí)symbol 收集依賴(lài)

  • 其實(shí)如果在模板中直接使用 obj 會(huì)伴隨著一個(gè) JSON.stringify 的過(guò)程,也會(huì)伴隨著收集依賴(lài)。
  • 我們?cè)?js 代碼里如果沒(méi)有用到遍歷對(duì)象,單獨(dú)對(duì)一個(gè)對(duì)象新增是不會(huì)觸發(fā)更新的,因?yàn)闆](méi)有收集的過(guò)程。

在設(shè)置新值時(shí),獲取收集的symbol對(duì)應(yīng)的副作用函數(shù)更新

遍歷數(shù)組方法的處理

在使用數(shù)組時(shí),會(huì)伴隨著 this 的問(wèn)題導(dǎo)致代理對(duì)象拿不到屬性的問(wèn)題,比如

const obj = {}
const arr = reactive([obj])
console.log(arr.includes(obj) // false

之所以會(huì)出現(xiàn)這樣的問(wèn)題,是因?yàn)?includes 內(nèi)部的this 指向的是代理對(duì)象 arr, 并且在因?yàn)楸容^去獲取元素時(shí)拿到的也是代理對(duì)象,所以拿原始對(duì)象去找肯定找不到。所以,我們需要去修改inlcudes的行為,

new Proxy(obj, {
    get() {
        if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
          return Reflect.get(arrayInstrumentations, key, receiver)
        }
    }
})
   
const arrayInstrumentations = /*#__PURE__*/ createArrayInstrumentations()

// 處理集中數(shù)組遍歷方法中的問(wèn)題。
function createArrayInstrumentations() {
  const instrumentations: Record<string, Function> = {}
  // instrument identity-sensitive Array methods to account for possible reactive
  // values 
  ;(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      const arr = toRaw(this) as any
      for (let i = 0, l = this.length; i < l; i++) {
        track(arr, TrackOpTypes.GET, i + '')
      }
      // we run the method using the original args first (which may be reactive)
      // 先使用原始參數(shù),可能是原始對(duì)象,也可能是代理對(duì)象。
      const res = arr[key](...args)
      if (res === -1 || res === false) {
        // if that didn't work, run it again using raw values.
        // 如果沒(méi)有找到,就拿到原始參數(shù)去比較,脫完響應(yīng)式的數(shù)據(jù)。
        return arr[key](...args.map(toRaw)) 
      } else {
        return res
      }
    }
  })

數(shù)組的變更方法

對(duì)于可能會(huì)更改原數(shù)組長(zhǎng)度的數(shù)組方法,push, pop, shift, unshift, splice 也需要進(jìn)行處理,否則,就會(huì)陷入無(wú)限遞歸當(dāng)中,考慮下面的場(chǎng)景

cosnt arr = reactive([])
effect(() => {
    arr.push(1)
})
effect(() => {
    arr.push(1)
})

這兩個(gè)的執(zhí)行過(guò)程如下:

  • 首先,第一個(gè)副作用函數(shù)執(zhí)行,然后數(shù)組中添加1, 并且這個(gè)過(guò)程會(huì)給影響數(shù)組的 length, 所以會(huì)與 length 會(huì)被 track , 建立響應(yīng)式聯(lián)系。
  • 然后第二個(gè)副作用函數(shù)執(zhí)行,執(zhí)行 push 這時(shí)因?yàn)橛绊懥?length,先track 建立響應(yīng)式聯(lián)系,然后會(huì)試圖拿出length的副作用函數(shù)也就是第一個(gè)副作用函數(shù)執(zhí)行,然而這時(shí)第二個(gè)副作用函數(shù)還未執(zhí)行完成,就又開(kāi)始執(zhí)行第一個(gè)副作用函數(shù)了,
  • 第一個(gè)副作用函數(shù)再次執(zhí)行,同樣會(huì)讀取length 并且設(shè)置 length,重復(fù)上面收集和更新的過(guò)程,又要把第二個(gè)副作用中收集的 length 執(zhí)行
  • 如此循環(huán)往復(fù)。最終會(huì)棧溢出。

所以問(wèn)題的關(guān)鍵就在于 length 的不斷讀取和設(shè)置。所以我們需要在讀取到 length,避免它與副作用函數(shù)之間建立聯(lián)系

;(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
    instrumentations[key] = function (this: unknown[], ...args: unknown[]) {
      // 禁止追蹤變化, 
      pauseTracking()
      const res = (toRaw(this) as any)[key].apply(this, args)
      // 等函數(shù)執(zhí)行完畢時(shí)再回復(fù)追蹤。
      resetTracking()
      return res
    }
  })

總結(jié)

vue2vue3 對(duì)于新增的屬性都是需要先獲取之前收集到的依賴(lài),才能夠派發(fā)更新。vue2 中使用 Vue.$set借助的是新增屬性的對(duì)象已有的依賴(lài)派發(fā)更新。vue3 是由于 ownKeys()攔截之前收集到的 symbol 依賴(lài),在添加屬性時(shí)觸發(fā)這個(gè)symbol 收集到的依賴(lài)更新。

對(duì)于數(shù)組,vue2 是攔截修改數(shù)組的方法,然后把當(dāng)前數(shù)組所收集的依賴(lài)做出更新執(zhí)行ob.dep.notify()vue3因?yàn)楸旧砭陀幸欢ǖ哪芰?shí)現(xiàn)數(shù)組元素的更新,只是由于是因?yàn)?code>length 導(dǎo)致的棧溢出,所以采用禁止跟蹤解決,同時(shí),訪問(wèn)方法也需要更新是因?yàn)榇韺?duì)象和原始對(duì)象不一致問(wèn)題,在查找時(shí)讓對(duì)比兩個(gè)解決。

以上就是詳解Vue3中響應(yīng)式的特殊處理的詳細(xì)內(nèi)容,更多關(guān)于Vue3響應(yīng)式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Vue3源碼解析watch函數(shù)實(shí)例

    Vue3源碼解析watch函數(shù)實(shí)例

    這篇文章主要為大家介紹了Vue3源碼解析watch函數(shù)實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • vue實(shí)現(xiàn)頂部左右滑動(dòng)導(dǎo)航

    vue實(shí)現(xiàn)頂部左右滑動(dòng)導(dǎo)航

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)頂部左右滑動(dòng)導(dǎo)航,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-06-06
  • vue移動(dòng)端使用canvas簽名的實(shí)現(xiàn)

    vue移動(dòng)端使用canvas簽名的實(shí)現(xiàn)

    這篇文章主要介紹了vue移動(dòng)端使用canvas簽名的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-01-01
  • Vue3系列教程之插槽slot詳解

    Vue3系列教程之插槽slot詳解

    插槽(slot)可以說(shuō)在一個(gè)?Vue?項(xiàng)目里面處處都有它的身影,比如我們使用一些UI?組件庫(kù)的時(shí)候,我們通??梢允褂貌宀蹃?lái)自定義我們的內(nèi)容,今天通過(guò)本文給大家介紹vue3插槽slot的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2022-08-08
  • Xx-vue自定義指令實(shí)現(xiàn)點(diǎn)擊水波紋漣漪效果

    Xx-vue自定義指令實(shí)現(xiàn)點(diǎn)擊水波紋漣漪效果

    這篇文章主要為大家介紹了Xx-vue自定義指令實(shí)現(xiàn)點(diǎn)擊水波紋漣漪效果,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • 解析vue、angular深度作用選擇器

    解析vue、angular深度作用選擇器

    在 Vue 的開(kāi)發(fā)中,我們經(jīng)常會(huì)用到外部組件庫(kù),這篇文章主要介紹了vue、angular深度作用選擇器,需要的朋友可以參考下
    2019-09-09
  • vue組件傳值(高級(jí))、屬性傳值、反向傳值、跨級(jí)傳值實(shí)例詳解

    vue組件傳值(高級(jí))、屬性傳值、反向傳值、跨級(jí)傳值實(shí)例詳解

    父組件通過(guò)屬性傳值給子組件,父組件修改數(shù)據(jù)后會(huì)刷新頁(yè)面并重新傳值給子組件,子組件可以修改父組件傳的值并刷新自己的頁(yè)面?但是并不會(huì)修改父組件中的值,這篇文章主要介紹了vue組件傳值(高級(jí))、屬性傳值、反向傳值、跨級(jí)傳值,需要的朋友可以參考下
    2022-09-09
  • uni-app開(kāi)發(fā)小程序的完整流程記錄

    uni-app開(kāi)發(fā)小程序的完整流程記錄

    本文詳細(xì)介紹了使用uni-app進(jìn)行項(xiàng)目開(kāi)發(fā)的全過(guò)程,包括uni-app框架的介紹、開(kāi)發(fā)工具HBuilderX的下載安裝與配置、項(xiàng)目開(kāi)發(fā)中的網(wǎng)絡(luò)請(qǐng)求配置、小程序分包加載、自定義組件使用、組件使用技巧等,還提及了解決開(kāi)發(fā)中遇到的一些問(wèn)題,需要的朋友可以參考下
    2024-11-11
  • vue3如何解決slot深層透?jìng)鲉?wèn)題

    vue3如何解決slot深層透?jìng)鲉?wèn)題

    這篇文章主要介紹了vue3如何解決slot深層透?jìng)鲉?wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • vue3中利用Export2Excel將數(shù)據(jù)導(dǎo)出為excel表格

    vue3中利用Export2Excel將數(shù)據(jù)導(dǎo)出為excel表格

    這篇文章主要給大家介紹了關(guān)于vue3中利用Export2Excel將數(shù)據(jù)導(dǎo)出為excel表格的相關(guān)資料,最近項(xiàng)目需要前端來(lái)導(dǎo)出Excel操作,所以給大家總結(jié)下,需要的朋友可以參考下
    2023-09-09

最新評(píng)論

新营市| 克拉玛依市| 金堂县| 孝感市| 大庆市| 北安市| 朝阳区| 淮安市| 册亨县| 乌鲁木齐市| 白山市| 家居| 桐庐县| 吉林市| 乐亭县| 灵武市| 九台市| 玛多县| 德令哈市| 华蓥市| 潜山县| 汽车| 额济纳旗| 滦南县| 都匀市| 葫芦岛市| 呈贡县| 福清市| 江达县| 合肥市| 兴城市| 弥渡县| 苍南县| 大同市| 四川省| 曲阳县| 武定县| 乐安县| 额敏县| 措美县| 大庆市|