Vue3源碼解析(computed-計算屬性)

做者:秦志英javascript

前言

上一篇文章中咱們分析了Vue3響應式的整個流程,本篇文章咱們將分析Vue3中的computed計算屬性是如何實現的。html

在Vue2中咱們已經對計算屬性瞭解的很清楚了,在Vue3中提供了一個computed的函數做爲計算屬性的API,下面咱們來經過源碼的角度去分析計算屬性的運行流程。前端

computed

export function computed<T>(getter: ComputedGetter<T>): ComputedRef<T>
export function computed<T>(
  options: WritableComputedOptions<T>
): WritableComputedRef<T>
export function computed<T>(
  getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>
) {
  let getter: ComputedGetter<T>
  let setter: ComputedSetter<T>
  if (isFunction(getterOrOptions)) {
    getter = getterOrOptions
    setter = NOOP
  } else {
    getter = getterOrOptions.get
    setter = getterOrOptions.set
  }
  return new ComputedRefImpl(
    getter,
    setter,
    isFunction(getterOrOptions) || !getterOrOptions.set
  ) as any
}
  • 在最開始使用函數重載的方式容許computed函數接受兩種類型的參數:第一種是一個getter函數, 第二種是一個帶getset的對象。
  • 接下就是在函數內部根據傳入的不一樣類型的參數初始化函數內部的gettersetter函數,若是傳入的是一個函數類型的參數,那麼getter就是這個函數,setter就是一個空的操做,若是傳入的參數是一個對象,則getter就等於這個對象的get函數,setter就等於這個對象的set函數。
  • 在函數的結尾返回了一個new ComputedRefImpl,並將前面咱們標準化後的參數傳遞給了這個構造函數。
    下面咱們就來分析一下ComputedRefImpl這個構造函數。

ComputedRefImpl

class ComputedRefImpl<T> {
  // 緩存結果
  private _value!: T
  // 從新計算開關
  private _dirty = true
  public readonly effect: ReactiveEffect<T>
  public readonly __v_isRef = true;
  public readonly [ReactiveFlags.IS_READONLY]: boolean
  constructor(
    getter: ComputedGetter<T>,
    private readonly _setter: ComputedSetter<T>,
    isReadonly: boolean
  ) {
    // 對傳入的getter函數進行包裝
    this.effect = effect(getter, {
      lazy: true,
      // 調度執行
      scheduler: () => {
        if (!this._dirty) {
          this._dirty = true
          // 派發通知
          trigger(toRaw(this), TriggerOpTypes.SET, 'value')
        }
      }
    })
  }
  // 訪問計算屬性的時候 默認調用此時的get函數
  get value() {
    // 是否須要從新計算
    if (this._dirty) {
      this._value = this.effect()
      this._dirty = false
    }
    // 訪問的時候進行依賴收集 此時收集的是訪問這個計算屬性的反作用函數
    track(toRaw(this), TrackOpTypes.GET, 'value')
    return this._value
  }

  set value(newValue: T) {
    this._setter(newValue)
  }
}

ComputedRefImpl類在內部維護了_value_dirty這兩個很是重要的私有屬性,其中_value使用用來緩存咱們計算的結果,_dirty是用來控制是否須要重現計算。接下來咱們來看一下這個函數的內部運行機制。vue

  • 首先構造函數在初始化的時候使用了effect函數對傳入getter進行了一層包裝(上一篇文章中咱們分析過effect函數的做用就是將傳入的函數變成可響應式的反作用函數),可是這裏咱們在effect中傳入了一些配置參數,還記得前面咱們分析trigger函數的時候有這一段代碼:
const run = (effect: ReactiveEffect) => {
    if (effect.options.scheduler) {
      effect.options.scheduler(effect)
    } else {
      effect()
    }
  }
effects.forEach(run)

當屬性值發生改變以後,會觸發trigger函數進行派發更新,將全部依賴這個屬性的effect函數循環遍歷,使用run函數執行effect,若是effect的參數中配置了scheduler,則就執行scheduler函數,而不是執行依賴的反作用函數。當計算屬性依賴的屬性發生變化的時候,回執行包裝getter函數的effect, 可是由於配置了scheduler函數,因此真正執行的是scheduler函數,在scheduler函數中並無執行計算屬性的getter函數求取新值,而是將_dirty設置爲false,而後通知依賴計算屬性的反作用函數進行更新, 當依賴計算屬性的反作用函數收到通知的時候就會訪問計算屬性的get函數,此時會根據_dirty值來肯定是否須要從新計算。java

回到咱們的這個構造函數中,只須要記得咱們在構造函數初始化三個重要的點:第一:對傳入的getter函數使用effect函數進行包裝。第二:在使用effect包裝的過程當中,咱們會執行getter函數,此時getter函數執行過程當中對於訪問到的屬性會將當前的這個計算屬性收集到對應的依賴集合中, 第三:傳入了配置參數lazyscheduler,這些配置參數在當前的這個計算屬性所訂閱的屬性發生改變的時候,用來控制計算屬性的調度時機。react

  • 接着咱們繼續分析get value,當咱們訪問計算屬性的值時候實際上訪問的就是這個函數的返回值, 它會根據_dirty的值來判斷是否須要從新計算getter函數,_dirty爲true須要從新執行effect函數,並將effect的值置爲false,不然就返回以前緩存的_value值。在訪問計算屬性值的階段會調用track函數進行依賴收集,此時收集的是訪問計算屬性值的反作用函數, key始終是vlaue。
  • 最後就是當設置計算屬性的值的時候會執行set函數,而後調用咱們傳入的_setter函數。

示例流程

至此計算屬性的執行流程就分析完畢了,咱們來結合一個示例來完整的過一遍整個流程:git

<template>
    <div>
        <button @click="addNum">add</button>
        <p>計算屬性:{{computedData}}</p>
    </div>
</template>

<script>
import { ref, watch,reactive, computed } from 'vue' 
import { effect } from '@vue/reactivity'
export default {
  name: 'App',
  setup(){
    const testData = ref(1)
    const computedData = computed(() => {
      return testData.value++
    })
    function addNum(){
      testData.value += 10
    }
    return {
      addNum,
      computedData
    }
  },
}

</script>

下面是一張流程圖,當點擊頁面中的按鈕改變testData的value值時,發生的變化流程就是下面的紅線部分。
github

  • 首先初始化頁面的時候,testData通過ref()以後變成響應式數據,會對訪問testData.value的值進行依賴收集,當testData.value的值發生變化的話,會對依賴這個值的依賴集合進行派發更新
  • computed中傳入了一個getter函數,getter函數內部有對testData.value的訪問,此時當前的這個計算屬性的反作用函數就訂閱了testData.value的值,computed返回了一個值,而頁面中的組件有對computed返回值的訪問,頁面的渲染反作用函數就訂閱了computed的返回值,因此這個頁面中有兩個依賴集合。
  • 當咱們點擊頁面中的按鈕,會改變testData.value的值,此時會通知訂閱計算屬性的反作用函數進行更新操做,因爲咱們在生成計算屬性反作用的時候配置了scheduler,因此執行的是scheduler函數,scheduler函數並無當即執行getter函數進行從新計算,而是將ComputedRefImpl類內部的私有變量_dirty設置爲true,而後通知訂閱當前計算屬性的反作用函數進行更新操做。
  • 組件中的渲染反作用函數執行更新操做的時候會訪問到get value函數,函數內部會根據_dirty值來判斷是否須要從新計算,因爲前面的scheduler函數將_dirty設置爲true因此此時會調用getter函數的反作用函數effect,這個時候纔會從新計算並將結果返回,頁面數據更新。

總結

計算屬性兩個最大的特色就是typescript

  • 延時計算 計算屬性所依賴的值發生改變的時候並不會當即執行getter函數去從新計算新的結果,而是打開從新計算的開關並通知訂閱計算屬性的反作用函數進行更新。若是當前的計算屬性沒有依賴集合就不執行從新計算邏輯,若是有依賴觸發計算屬性的get,這個時候纔會調用this.effect()進行從新計算。
  • 緩存結果 當依賴的屬性沒有發生改變的,訪問計算屬性會返回以前緩存在_value中的值。

對 Electron 感興趣?請關注咱們的開源項目 Electron Playground,帶你極速上手 Electron。segmentfault

咱們每週五會精選一些有意思的文章和消息和你們分享,來掘金關注咱們的 曉前端週刊


咱們是好將來 · 曉黑板前端技術團隊。
咱們會常常與你們分享最新最酷的行業技術知識。
歡迎來 知乎掘金SegmentfaultCSDN簡書開源中國博客園 關注咱們。

相關文章
相關標籤/搜索