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

Vue3源碼解讀computed和watch

 更新時間:2023年03月28日 14:59:12   作者:PHM  
這篇文章主要為大家介紹了Vue3中的computed和watch源碼解讀分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

computed

computed和watch在面試中經(jīng)常被問到他們的區(qū)別,那么我們就從源碼的實現(xiàn)來看看他們的具體實現(xiàn)

// 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內(nèi)部只是先處理getter和setter,然后new一個ComputedRefImpl返回,如果你知道ref API的實現(xiàn),可以發(fā)現(xiàn)他們的實現(xiàn)有很多相同之處

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
  ) {
    // 創(chuàng)建effect
    this.effect = new ReactiveEffect(getter, () => {
      // 調(diào)度器執(zhí)行 重新賦值_dirty為true
      if (!this._dirty) {
        this._dirty = true
        // 觸發(fā)effect
        triggerRefValue(this)
      }
    })
    // 用于區(qū)分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)
    // 如果是臟的,重新執(zhí)行effect.run(),并且將_dirty設置為false
    if (self._dirty || !self._cacheable) {
      self._dirty = false
      // run()方法會執(zhí)行getter方法 值會被緩存到self._value
      self._value = self.effect.run()!
    }
    return self._value
  }
  set value(newValue: T) {
    this._setter(newValue)
  }
}

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

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

小結(jié):

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

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

// 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會先執(zhí)行
  // 防止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與響應式數(shù)據(jù)之間的關系,在響應式數(shù)據(jù)變化時調(diào)用用戶傳過來的回調(diào)并將新舊值傳入即可

// 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 具體實現(xiàn)
  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
  // 根據(jù)不同source 創(chuàng)建不同的getter函數(shù)
  // getter 函數(shù)與computed的getter函數(shù)作用類似
  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
    }
  }
  // 深度監(jiān)聽
  if (cb && deep) {
    const baseGetter = getter
    // traverse會遞歸遍歷對象的所有屬性 以達到深度監(jiān)聽的目的
    getter = () => traverse(baseGetter())
  }
  let cleanup: () => void
  // watch回調(diào)的第三個參數(shù) 可以用此注冊一個cleanup函數(shù) 會在下一次watch cb調(diào)用前執(zhí)行
  // 常用于競態(tài)問題的處理
  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監(jiān)聽則初始化為數(shù)組
  let oldValue: any = isMultiSource
    ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
    : INITIAL_WATCHER_VALUE
  // 調(diào)度器調(diào)用時執(zhí)行
  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) {
          // 執(zhí)行onCleanup傳過來的函數(shù)
          cleanup()
        }
        // 調(diào)用cb 參數(shù)為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') {
    // 同步更新 即每次響應式數(shù)據(jù)改變都會回調(diào)一次cb 通常不使用
    scheduler = job as any // the scheduler function gets called directly
  } else if (flush === 'post') {
    // job放入pendingPostFlushCbs隊列中
    // pendingPostFlushCbs隊列會在queue隊列執(zhí)行完畢后執(zhí)行 函數(shù)更新effect通常會放在queue隊列中
    // 所以pendingPostFlushCbs隊列執(zhí)行時組件已經(jīng)更新完畢
    scheduler = () => queuePostRenderEffect(job, instance && instance.suspense)
  } else {
    // default: 'pre'
    job.pre = true
    if (instance) job.id = instance.uid
    // 默認異步更新 關于異步更新會和nextTick放在一起詳細講解
    scheduler = () => queueJob(job)
  }
  // 創(chuàng)建effect effect.run的時候建立effect與getter內(nèi)響應式數(shù)據(jù)的關系
  const effect = new ReactiveEffect(getter, scheduler)
  if (__DEV__) {
    effect.onTrack = onTrack
    effect.onTrigger = onTrigger
  }
  // initial run
  if (cb) {
    if (immediate) {
      // 立馬執(zhí)行一次job
      job()
    } else {
      // 否則執(zhí)行effect.run() 會執(zhí)行getter 獲取oldValue
      oldValue = effect.run()
    }
  } else if (flush === 'post') {
    queuePostRenderEffect(
      effect.run.bind(effect),
      instance && instance.suspense
    )
  } else {
    effect.run()
  }
  // 返回一個取消監(jiān)聽的函數(shù)
  const unwatch = () => {
    effect.stop()
    if (instance && instance.scope) {
      remove(instance.scope.effects!, effect)
    }
  }
  if (__SSR__ && ssrCleanup) ssrCleanup.push(unwatch)
  return unwatch
}

以上就是Vue3源碼解讀computed和watch的詳細內(nèi)容,更多關于Vue3 computed watch的資料請關注腳本之家其它相關文章!

相關文章

  • 一文徹底搞懂Vue的MVVM響應式原理

    一文徹底搞懂Vue的MVVM響應式原理

    這篇文章主要介紹了一文徹底搞懂Vue的MVVM響應式原理,vue則是采用發(fā)布者-訂閱者模式,通過Object.defineProperty()來劫持各個屬性的getter和setter,在數(shù)據(jù)變動時發(fā)布消息給訂閱者,觸發(fā)相應的監(jiān)聽回調(diào)
    2022-06-06
  • vue實現(xiàn)滑動超出指定距離回頂部功能

    vue實現(xiàn)滑動超出指定距離回頂部功能

    這篇文章主要為大家詳細介紹了vue實現(xiàn)滑動超出指定距離回頂部功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • Vue命令行工具Vue-CLI圖文詳解(推薦!)

    Vue命令行工具Vue-CLI圖文詳解(推薦!)

    vue-cli 是 Vue.js 開發(fā)的標準工具,它簡化了程序員基于 webpack 創(chuàng)建工程化的 Vue 項目的過程,下面這篇文章主要給大家介紹了關于Vue命令行工具Vue-CLI的相關資料,需要的朋友可以參考下
    2022-07-07
  • Vue之請求如何傳遞參數(shù)

    Vue之請求如何傳遞參數(shù)

    這篇文章主要介紹了Vue之請求如何傳遞參數(shù)問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Vue + Elementui實現(xiàn)多標簽頁共存的方法

    Vue + Elementui實現(xiàn)多標簽頁共存的方法

    這篇文章主要介紹了Vue + Elementui實現(xiàn)多標簽頁共存的方法,本文通過實例代碼給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下
    2019-06-06
  • 利用Nuxt.js做Vuex數(shù)據(jù)持久化

    利用Nuxt.js做Vuex數(shù)據(jù)持久化

    這篇文章主要介紹了利用Nuxt.js做Vuex數(shù)據(jù)持久化問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • elementPlus表格二次封裝過程

    elementPlus表格二次封裝過程

    我們正常在開發(fā)項目中,表格的風格是一致的,但是表格或多或少會有些不同,有些是需要分頁,有些是按鈕功能不同,有些又需要加Tag,或者對時間進行格式化等,這篇文章主要介紹了elementPlus表格二次封裝過程,需要的朋友可以參考下
    2024-07-07
  • vue實現(xiàn)拖拽交換位置

    vue實現(xiàn)拖拽交換位置

    這篇文章主要為大家詳細介紹了vue實現(xiàn)拖拽交換位置,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • vue+node+webpack環(huán)境搭建教程

    vue+node+webpack環(huán)境搭建教程

    這篇文章主要為大家詳細介紹了vue+node+webpack環(huán)境搭建教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • Vue組件生命周期運行原理解析

    Vue組件生命周期運行原理解析

    這篇文章主要介紹了Vue組件生命周期運行原理解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-11-11

最新評論

正安县| 句容市| 紫金县| 左权县| 察哈| 青州市| 色达县| 南开区| 洮南市| 新乡市| 腾冲县| 策勒县| 鞍山市| 济阳县| 六盘水市| 南汇区| 莱西市| 宜宾市| 南岸区| 信宜市| 高青县| 长沙县| 遂溪县| 上饶市| 乐平市| 靖宇县| 尖扎县| 延安市| 渝北区| 锡林浩特市| 尼木县| 成都市| 临江市| 专栏| 安顺市| 津市市| 崇明县| 乌鲁木齐县| 通榆县| 宁陵县| 常州市|