中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue3 computed和watch源碼分析

發布時間:2023-03-28 15:21:19 來源:億速云 閱讀:128 作者:iii 欄目:開發技術

這篇文章主要介紹“Vue3 computed和watch源碼分析”的相關知識,小編通過實際案例向大家展示操作過程,操作方法簡單快捷,實用性強,希望這篇“Vue3 computed和watch源碼分析”文章能幫助大家解決問題。

    computed

    computed和watch在面試中經常被問到他們的區別,那么我們就從源碼的實現來看看他們的具體實現

    // packages/reactivity/src/computed.ts
    export function computed<T>(
      getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
      debugOptions?: DebuggerOptions,
      isSSR = false
    ) {
      let getter: ComputedGetter<T>
      let setter: ComputedSetter<T>
      const onlyGetter = isFunction(getterOrOptions)
      if (onlyGetter) {
        getter = getterOrOptions
        setter = __DEV__
          ? () => {
              console.warn('Write operation failed: computed value is readonly')
            }
          : NOOP
      } else {
        getter = getterOrOptions.get
        setter = getterOrOptions.set
      }
      // new ComputedRefImpl
      const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR)
      if (__DEV__ && debugOptions && !isSSR) {
        cRef.effect.onTrack = debugOptions.onTrack
        cRef.effect.onTrigger = debugOptions.onTrigger
      }
      // 返回ComputedRefImpl實例
      return cRef as any
    }

    可以看到computed內部只是先處理getter和setter,然后new一個ComputedRefImpl返回,如果你知道ref API的實現,可以發現他們的實現有很多相同之處

    ComputedRefImpl

    // packages/reactivity/src/computed.ts
    export class ComputedRefImpl<T> {
      public dep?: Dep = undefined // 存儲effect的集合
      private _value!: T
      public readonly effect: ReactiveEffect<T>
      public readonly __v_isRef = true
      public readonly [ReactiveFlags.IS_READONLY]: boolean = false
      public _dirty = true // 是否需要重新更新value
      public _cacheable: boolean
      constructor(
        getter: ComputedGetter<T>,
        private readonly _setter: ComputedSetter<T>,
        isReadonly: boolean,
        isSSR: boolean
      ) {
        // 創建effect
        this.effect = new ReactiveEffect(getter, () => {
          // 調度器執行 重新賦值_dirty為true
          if (!this._dirty) {
            this._dirty = true
            // 觸發effect
            triggerRefValue(this)
          }
        })
        // 用于區分effect是否是computed
        this.effect.computed = this
        this.effect.active = this._cacheable = !isSSR
        this[ReactiveFlags.IS_READONLY] = isReadonly
      }
      get value() {
        // the computed ref may get wrapped by other proxies e.g. readonly() #3376
        // computed ref可能被其他代理包裝,例如readonly() #3376
        // 通過toRaw()獲取原始值
        const self = toRaw(this)
        // 收集effect
        trackRefValue(self)
        // 如果是臟的,重新執行effect.run(),并且將_dirty設置為false
        if (self._dirty || !self._cacheable) {
          self._dirty = false
          // run()方法會執行getter方法 值會被緩存到self._value
          self._value = self.effect.run()!
        }
        return self._value
      }
      set value(newValue: T) {
        this._setter(newValue)
      }
    }

    可以看到ComputedRefImplget的get實現基本和ref的get相同(不熟悉ref實現的請看上一章),唯一的區別就是_dirty值的判斷,這也是我們常說的computed會緩存value,那么computed是如何知道value需要更新呢?

    可以看到在computed構造函數中,會建立一個getter與其內部響應式數據的關系,這跟我們組件更新函數跟響應式數據建立關系是一樣的,所以與getter相關的響應式數據發生修改的時候,就會觸發getter effect 對應的scheduler,這里會將_dirty設置為true并去執行收集到的effect(這里通常是執行get里收集到的函數更新的effect),然后就會去執行函數更新函數,里面會再次觸發computed的get,此時dirty已經被置為true,就會重新執行getter獲取新的值返回,并將該值緩存到_vlaue。

    小結:

    所以computed是有兩層的響應式處理的,一層是computed.value和函數的effect之間的關系(與ref的實現相似),一層是computed的getter和響應式數據的關系。

    注意:如果你足夠細心就會發現函數更新函數的effect觸發和computed getter的effect的觸發之間可能存在順序的問題。假如有一個響應式數據a不僅存在于getter中,還在函數render中早于getter被訪問,此時a對應的dep中更新函數的effect就會早于getter的effect被收集,如果此時a被改變,就會先執行更新函數的effect,那么此時render函數訪問到computed.value的時候就會發現_dirty依然是false,因為getter的effect還沒有被執行,那么此時依然會是舊值。vue3中對此的處理是執行effects的時候會優先執行computed對應的effect(此前章節也有提到):

    // packages/reactivity/src/effect.ts
    export function triggerEffects(
      dep: Dep | ReactiveEffect[],
      debuggerEventExtraInfo?: DebuggerEventExtraInfo
    ) {
      // spread into array for stabilization
      const effects = isArray(dep) ? dep : [...dep]
      // computed的effect會先執行
      // 防止render獲取computed值得時候_dirty還沒有置為true
      for (const effect of effects) {
        if (effect.computed) {
          triggerEffect(effect, debuggerEventExtraInfo)
        }
      }
      for (const effect of effects) {
        if (!effect.computed) {
          triggerEffect(effect, debuggerEventExtraInfo)
        }
      }
    }

    watch

    watch相對于computed要更簡單一些,因為他只用建立getter與響應式數據之間的關系,在響應式數據變化時調用用戶傳過來的回調并將新舊值傳入即可

    // packages/runtime-core/src/apiWatch.ts
    export function watch<T = any, Immediate extends Readonly<boolean> = false>(
      source: T | WatchSource<T>,
      cb: any,
      options?: WatchOptions<Immediate>
    ): WatchStopHandle {
      if (__DEV__ && !isFunction(cb)) {
        warn(...)
      }
      // watch 具體實現
      return doWatch(source as any, cb, options)
    }
    function doWatch(
      source: WatchSource | WatchSource[] | WatchEffect | object,
      cb: WatchCallback | null,
      { immediate, deep, flush, onTrack, onTrigger }: WatchOptions = EMPTY_OBJ
    ): WatchStopHandle {
      if (__DEV__ && !cb) {
        ...
      }
      const warnInvalidSource = (s: unknown) => {
        warn(...)
      }
      const instance =
        getCurrentScope() === currentInstance?.scope ? currentInstance : null
      // const instance = currentInstance
      let getter: () => any
      let forceTrigger = false
      let isMultiSource = false
      // 根據不同source 創建不同的getter函數
      // getter 函數與computed的getter函數作用類似
      if (isRef(source)) {
        getter = () => source.value
        forceTrigger = isShallow(source)
      } else if (isReactive(source)) {
        // source是reactive對象時 自動開啟deep=true
        getter = () => source
        deep = true
      } else if (isArray(source)) {
        isMultiSource = true
        forceTrigger = source.some(s => isReactive(s) || isShallow(s))
        getter = () =>
          source.map(s => {
            if (isRef(s)) {
              return s.value
            } else if (isReactive(s)) {
              return traverse(s)
            } else if (isFunction(s)) {
              return callWithErrorHandling(s, instance, ErrorCodes.WATCH_GETTER)
            } else {
              __DEV__ && warnInvalidSource(s)
            }
          })
      } else if (isFunction(source)) {
        if (cb) {
          // getter with cb
          getter = () =>
            callWithErrorHandling(source, instance, ErrorCodes.WATCH_GETTER)
        } else {
          // no cb -> simple effect
          getter = () => {
            if (instance && instance.isUnmounted) {
              return
            }
            if (cleanup) {
              cleanup()
            }
            return callWithAsyncErrorHandling(
              source,
              instance,
              ErrorCodes.WATCH_CALLBACK,
              [onCleanup]
            )
          }
        }
      } else {
        getter = NOOP
        __DEV__ && warnInvalidSource(source)
      }
      // 2.x array mutation watch compat
      // 兼容vue2
      if (__COMPAT__ && cb && !deep) {
        const baseGetter = getter
        getter = () => {
          const val = baseGetter()
          if (
            isArray(val) &&
            checkCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance)
          ) {
            traverse(val)
          }
          return val
        }
      }
      // 深度監聽
      if (cb && deep) {
        const baseGetter = getter
        // traverse會遞歸遍歷對象的所有屬性 以達到深度監聽的目的
        getter = () => traverse(baseGetter())
      }
      let cleanup: () => void
      // watch回調的第三個參數 可以用此注冊一個cleanup函數 會在下一次watch cb調用前執行
      // 常用于競態問題的處理
      let onCleanup: OnCleanup = (fn: () => void) => {
        cleanup = effect.onStop = () => {
          callWithErrorHandling(fn, instance, ErrorCodes.WATCH_CLEANUP)
        }
      }
      // in SSR there is no need to setup an actual effect, and it should be noop
      // unless it's eager or sync flush
      let ssrCleanup: (() => void)[] | undefined
      if (__SSR__ && isInSSRComponentSetup) {
        // ssr處理 ...
      }
      // oldValue 聲明 多個source監聽則初始化為數組
      let oldValue: any = isMultiSource
        ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
        : INITIAL_WATCHER_VALUE
      // 調度器調用時執行
      const job: SchedulerJob = () => {
        if (!effect.active) {
          return
        }
        if (cb) {
          // watch(source, cb)
          // 獲取newValue
          const newValue = effect.run()
          if (
            deep ||
            forceTrigger ||
            (isMultiSource
              ? (newValue as any[]).some((v, i) =>
                  hasChanged(v, (oldValue as any[])[i])
                )
              : hasChanged(newValue, oldValue)) ||
            (__COMPAT__ &&
              isArray(newValue) &&
              isCompatEnabled(DeprecationTypes.WATCH_ARRAY, instance))
          ) {
            // cleanup before running cb again
            if (cleanup) {
              // 執行onCleanup傳過來的函數
              cleanup()
            }
            // 調用cb 參數為newValue、oldValue、onCleanup
            callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [
              newValue,
              // pass undefined as the old value when it's changed for the first time
              oldValue === INITIAL_WATCHER_VALUE
                ? undefined
                : isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
                ? []
                : oldValue,
              onCleanup
            ])
            // 更新oldValue
            oldValue = newValue
          }
        } else {
          // watchEffect
          effect.run()
        }
      }
      // important: mark the job as a watcher callback so that scheduler knows
      // it is allowed to self-trigger (#1727)
      job.allowRecurse = !!cb
      let scheduler: EffectScheduler
      if (flush === 'sync') {
        // 同步更新 即每次響應式數據改變都會回調一次cb 通常不使用
        scheduler = job as any // the scheduler function gets called directly
      } else if (flush === 'post') {
        // job放入pendingPostFlushCbs隊列中
        // pendingPostFlushCbs隊列會在queue隊列執行完畢后執行 函數更新effect通常會放在queue隊列中
        // 所以pendingPostFlushCbs隊列執行時組件已經更新完畢
        scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
      } else {
        // default: 'pre'
        job.pre = true
        if (instance) job.id = instance.uid
        // 默認異步更新 關于異步更新會和nextTick放在一起詳細講解
        scheduler = () => queueJob(job)
      }
      // 創建effect effect.run的時候建立effect與getter內響應式數據的關系
      const effect = new ReactiveEffect(getter, scheduler)
      if (__DEV__) {
        effect.onTrack = onTrack
        effect.onTrigger = onTrigger
      }
      // initial run
      if (cb) {
        if (immediate) {
          // 立馬執行一次job
          job()
        } else {
          // 否則執行effect.run() 會執行getter 獲取oldValue
          oldValue = effect.run()
        }
      } else if (flush === 'post') {
        queuePostRenderEffect(
          effect.run.bind(effect),
          instance && instance.suspense
        )
      } else {
        effect.run()
      }
      // 返回一個取消監聽的函數
      const unwatch = () => {
        effect.stop()
        if (instance && instance.scope) {
          remove(instance.scope.effects!, effect)
        }
      }
      if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
      return unwatch
    }

    關于“Vue3 computed和watch源碼分析”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識,可以關注億速云行業資訊頻道,小編每天都會為大家更新不同的知識點。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    金山区| 乐山市| 吉安县| 得荣县| 沭阳县| 霞浦县| 崇仁县| 新巴尔虎右旗| 东源县| 嘉鱼县| 平阳县| 平陆县| 南川市| 榆社县| 沂南县| 神木县| 南京市| 大石桥市| 中方县| 赤水市| 唐海县| 衢州市| 揭西县| 延津县| 青州市| 新津县| 肥东县| 静乐县| 当雄县| 长沙市| 舒城县| 宝鸡市| 湘乡市| 太保市| 建水县| 永新县| 达孜县| 延津县| 芒康县| 凌海市| 驻马店市|