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

溫馨提示×

溫馨提示×

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

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

Vue3計算屬性是怎么實現的

發布時間:2022-04-14 09:03:23 來源:億速云 閱讀:293 作者:iii 欄目:編程語言

今天小編給大家分享一下Vue3計算屬性是怎么實現的的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

Vue3計算屬性是怎么實現的

計算屬性是 Vue.js 開發中一個非常實用的 API ,它允許用戶定義一個計算方法,然后根據一些依賴的響應式數據計算出新值并返回。當依賴發生變化時,計算屬性可以自動重新計算獲取新值,所以使用起來非常方便。

在 Vue.js 2.x 中,相信你對計算屬性的應用已經如數家珍了,我們可以在組件對象中定義 computed 屬性。到了 Vue.js 3.0 ,雖然也可以在組件中沿用 Vue.js 2.x 的使用方式,但是我們也可以單獨使用計算屬性 API。

計算屬性本質上還是對依賴的計算,那么為什么我們不直接用函數呢?在 Vue.js 3.0 中計算屬性的 API 又是如何實現呢?本文就來分析下計算屬性的實現原理。

計算屬性API:computed

Vue.js 3.0 提供了一個 computed 函數作為計算屬性 API,我們先來看看它是如何使用的。

我們舉個簡單的例子:

const count = ref(1) 
const plusOne = computed(() => count.value + 1) 
console.log(plusOne.value) // 2 
plusOne.value++ // error 
count.value++ 
console.log(plusOne.value) // 3

從代碼中可以看到,我們先使用 ref API 創建了一個響應式對象 count,然后使用 computed API 創建了另一個響應式對象 plusOne,它的值是 count.value + 1,當我們修改 count.value 的時候, plusOne.value 就會自動發生變化。

注意,這里我們直接修改 plusOne.value 會報一個錯誤,這是因為如果我們傳遞給 computed 的是一個函數,那么這就是一個 getter 函數,我們只能獲取它的值,而不能直接修改它。

在 getter 函數中,我們會根據響應式對象重新計算出新的值,這也就是它被叫做計算屬性的原因,而這個響應式對象,就是計算屬性的依賴。

當然,有時候我們也希望能夠直接修改 computed 的返回值,那么我們可以給 computed 傳入一個對象:

const count = ref(1) 
const plusOne = computed({ 
  get: () => count.value + 1, 
  set: val => { 
    count.value = val - 1 
  } 
}) 
plusOne.value = 1 
console.log(count.value) // 0

在這個例子中,結合上述代碼可以看到,我們給 computed 函數傳入了一個擁有 getter 函數和 setter 函數的對象,getter 函數和之前一樣,還是返回 count.value + 1;而 setter 函數,請注意,這里我們修改 plusOne.value 的值就會觸發 setter 函數,其實 setter 函數內部實際上會根據傳入的參數修改計算屬性的依賴值 count.value,因為一旦依賴的值被修改了,我們再去獲取計算屬性就會重新執行一遍 getter,所以這樣獲取的值也就發生了變化。

好了,我們現在已經知道了 computed API 的兩種使用方式了,接下來就看看它是怎樣實現的:

function computed(getterOrOptions) { 
  // getter 函數 
  let getter 
  // setter 函數 
  let setter 
  // 標準化參數 
  if (isFunction(getterOrOptions)) { 
    // 表面傳入的是 getter 函數,不能修改計算屬性的值 
    getter = getterOrOptions 
    setter = (process.env.NODE_ENV !== 'production') 
      ? () => { 
        console.warn('Write operation failed: computed value is readonly') 
      } 
      : NOOP 
  } 
  else { 
    getter = getterOrOptions.get 
    setter = getterOrOptions.set 
  } 
  // 數據是否臟的 
  let dirty = true 
  // 計算結果 
  let value 
  let computed 
  // 創建副作用函數 
  const runner = effect(getter, { 
    // 延時執行 
    lazy: true, 
    // 標記這是一個 computed effect 用于在 trigger 階段的優先級排序 
    computed: true, 
    // 調度執行的實現 
    scheduler: () => { 
      if (!dirty) { 
        dirty = true 
        // 派發通知,通知運行訪問該計算屬性的 activeEffect 
        trigger(computed, "set" /* SET */, 'value') 
      } 
    } 
    }) 
  // 創建 computed 對象 
  computed = { 
    __v_isRef: true, 
    // 暴露 effect 對象以便計算屬性可以停止計算 
    effect: runner, 
    get value() { 
      // 計算屬性的 getter 
      if (dirty) { 
        // 只有數據為臟的時候才會重新計算 
        value = runner() 
        dirty = false 
      } 
      // 依賴收集,收集運行訪問該計算屬性的 activeEffect 
      track(computed, "get" /* GET */, 'value') 
      return value 
    }, 
    set value(newValue) { 
      // 計算屬性的 setter 
      setter(newValue) 
    } 
  } 
  return computed 
}

從代碼中可以看到,computed 函數的流程主要做了三件事情:標準化參數創建副作用函數創建 computed 對象。 我們來詳細分析一下這幾個步驟。

首先是標準化參數。computed 函數接受兩種類型的參數,一個是 getter 函數,一個是擁有 getter 和 setter 函數的對象,通過判斷參數的類型,我們初始化了函數內部定義的 getter 和 setter 函數。

接著是創建副作用函數 runner。computed 內部通過 effect 創建了一個副作用函數,它是對 getter 函數做的一層封裝,另外我們這里要注意第二個參數,也就是 effect 函數的配置對象。其中 lazy 為 true 表示 effect 函數返回的 runner 并不會立即執行;computed 為 true 用于表示這是一個 computed effect,用于 trigger 階段的優先級排序,我們稍后會分析;scheduler 表示它的調度運行的方式,我們也稍后分析。

最后是創建 computed 對象并返回,這個對象也擁有 getter 和 setter 函數。當 computed 對象被訪問的時候會觸發 getter,然后會判斷是否 dirty,如果是就執行 runner,然后做依賴收集;當我們直接設置 computed 對象時會觸發 setter,即執行 computed 函數內部定義的 setter 函數。

計算屬性的運行機制

computed 函數的邏輯會有一點繞,不過不要緊,我們可以結合一個應用 computed 計算屬性的例子,來理解整個計算屬性的運行機制。分析之前我們需要記住 computed 內部兩個重要的變量,第一個 dirty 表示一個計算屬性的值是否是“臟的”,用來判斷需不需要重新計算,第二個 value 表示計算屬性每次計算后的結果。

現在,我們來看這個示例:

<template> 
  <div> 
    {{ plusOne }} 
  </div> 
  <button @click="plus">plus</button> 
</template> 
<script> 
  import { ref, computed } from 'vue' 
  export default { 
    setup() { 
      const count = ref(0) 
      const plusOne = computed(() => { 
        return count.value + 1 
      }) 

      function plus() { 
        count.value++ 
      } 
      return { 
        plusOne, 
        plus 
      } 
    } 
  } 
</script>

可以看到,在這個例子中我們利用 computed API 創建了計算屬性對象 plusOne,它傳入的是一個 getter 函數,為了和后面計算屬性對象的 getter 函數區分,我們把它稱作 computed getter。另外,組件模板中引用了 plusOne 變量和 plus 函數。

組件渲染階段會訪問 plusOne,也就觸發了 plusOne 對象的 getter 函數:

get value() { 
  // 計算屬性的 getter 
  if (dirty) { 
    // 只有數據為臟的時候才會重新計算 
    value = runner() 
    dirty = false 
  } 
  // 依賴收集,收集運行訪問該計算屬性的 activeEffect 
  track(computed, "get" /* GET */, 'value') 
  return value 
}

由于默認 dirty 是 true,所以這個時候會執行 runner 函數,并進一步執行 computed getter,也就是 count.value + 1,因為訪問了 count 的值,并且由于 count 也是一個響應式對象,所以就會觸發 count 對象的依賴收集過程。

請注意,由于是在 runner 執行的時候訪問 count,所以這個時候的 activeEffect 是 runner 函數。runner 函數執行完畢,會把 dirty 設置為 false,并進一步執行 track(computed,"get",'value') 函數做依賴收集,這個時候 runner 已經執行完了,所以 activeEffect 是組件副作用渲染函數。

所以你要特別注意這是兩個依賴收集過程:對于 plusOne 來說,它收集的依賴是組件副作用渲染函數;對于 count 來說,它收集的依賴是 plusOne 內部的 runner 函數。

然后當我們點擊按鈕的時候,會執行 plus 函數,函數內部通過 count.value++ 修改 count 的值,并派發通知。請注意,這里不是直接調用 runner 函數,而是把 runner 作為參數去執行 scheduler 函數。我們來回顧一下 trigger 函數內部對于 effect 函數的執行方式:

const run = (effect) => { 
  // 調度執行 
  if (effect.options.scheduler) { 
    effect.options.scheduler(effect) 
  } 
  else { 
    // 直接運行 
    effect() 
  } 
}

computed API 內部創建副作用函數時,已經配置了 scheduler 函數,如下:

scheduler: () => { 
  if (!dirty) { 
    dirty = true 
    // 派發通知,通知運行訪問該計算屬性的 activeEffect 
    trigger(computed, "set" /* SET */, 'value') 
  } 
}

它并沒有對計算屬性求新值,而僅僅是把 dirty 設置為 true,再執行 trigger(computed, "set" , 'value'),去通知執行 plusOne 依賴的組件渲染副作用函數,即觸發組件的重新渲染。

在組件重新渲染的時候,會再次訪問 plusOne,我們發現這個時候 dirty 為 true,然后會再次執行 computed getter,此時才會執行 count.value + 1 求得新值。這就是雖然組件沒有直接訪問 count,但是當我們修改 count 的值的時候,組件仍然會重新渲染的原因。

通過下圖可以直觀的展現上述過程:

Vue3計算屬性是怎么實現的

通過以上分析,我們可以看出 computed 計算屬性有兩個特點:

  • 延時計算,只有當我們訪問計算屬性的時候,它才會真正運行 computed getter 函數計算;

  • 緩存,它的內部會緩存上次的計算結果 value,而且只有 dirty 為 true 時才會重新計算。如果訪問計算屬性時 dirty 為 false,那么直接返回這個 value。

現在,我們就可以回答開頭提的問題了。和單純使用普通函數相比,計算屬性的優勢是:只要依賴不變化,就可以使用緩存的 value 而不用每次在渲染組件的時候都執行函數去計算,這是典型的空間換時間的優化思想。

嵌套計算屬性

計算屬性也支持嵌套,我們可以針對上述例子做個小修改,即不在渲染函數中訪問 plusOne,而在另一個計算屬性中訪問:

const count = ref(0) 
const plusOne = computed(() => { 
  return count.value + 1 
}) 
const plusTwo = computed(() => { 
  return plusOne.value + 1 
}) 
console.log(plusTwo.value)

從代碼中可以看到,當我們訪問 plusTwo 的時候,過程和前面都差不多,同樣也是兩個依賴收集的過程。對于 plusOne 來說,它收集的依賴是 plusTwo 內部的 runner 函數;對于 count 來說,它收集的依賴是 plusOne 內部的 runner 函數。

接著當我們修改 count 的值時,它會派發通知,先運行 plusOne 內部的 scheduler 函數,把 plusOne 內部的 dirty 變為 true,然后執行 trigger 函數再次派發通知,接著運行 plusTwo 內部的 scheduler 函數,把 plusTwo 內部的 dirty 設置為 true。

然后當我們再次訪問 plusTwo 的值時,發現 dirty 為 true,就會執行 plusTwo 的 computed getter 函數去執行 plusOne.value + 1,進而執行 plusOne 的 computed gette 即 count.value + 1 + 1,求得最終新值 2。

得益于 computed 這種巧妙的設計,無論嵌套多少層計算屬性都可以正常工作。

計算屬性的執行順序

我們曾提到計算屬性內部創建副作用函數的時候會配置 computed 為 true,標識這是一個 computed effect,用于在 trigger 階段的優先級排序。我們來回顧一下 trigger 函數執行 effects 的過程:

const add = (effectsToAdd) => { 
  if (effectsToAdd) { 
    effectsToAdd.forEach(effect => { 
      if (effect !== activeEffect || !shouldTrack) { 
        if (effect.options.computed) { 
          computedRunners.add(effect) 
        } 
        else { 
          effects.add(effect) 
        } 
      } 
    }) 
  } 
} 
const run = (effect) => { 
  if (effect.options.scheduler) { 
    effect.options.scheduler(effect) 
  } 
  else { 
    effect() 
  } 
} 
computedRunners.forEach(run) 
effects.forEach(run)

在添加待運行的 effects 的時候,我們會判斷每一個 effect 是不是一個 computed effect,如果是的話會添加到 computedRunners 中,在后面運行的時候會優先執行 computedRunners,然后再執行普通的 effects。

那么為什么要這么設計呢?其實是考慮到了一些特殊場景,我們通過一個示例來說明:

import { ref, computed } from 'vue' 
import { effect } from '@vue/reactivity' 
const count = ref(0) 
const plusOne = computed(() => { 
  return count.value + 1 
}) 
effect(() => { 
  console.log(plusOne.value + count.value) 
}) 
function plus() { 
  count.value++ 
} 
plus()

這個示例運行后的結果輸出:

1 
3 
3

在執行 effect 函數時運行 console.log(plusOne.value + count.value),所以第一次輸出 1,此時 count.value 是 0,plusOne.value 是 1。

后面連續輸出兩次 3 是因為, plusOne 和 count 的依賴都是這個 effect 函數,所以當我們執行 plus 函數修改 count 的值時,會觸發并執行這個 effect 函數,因為 plusOne 的 runner 也是 count 的依賴,count 值修改也會執行 plusOne 的 runner,也就會再次執行 plusOne 的依賴即 effect 函數,因此會輸出兩次。

那么為什么兩次都輸出 3 呢?這就跟先執行 computed runner 有關。首先,由于 plusOne 的 runner 和 effect 都是 count 的依賴,當我們修改 count 值的時候, plusOne 的 runner 和 effect 都會執行,那么此時執行順序就很重要了。

這里先執行 plusOne 的 runner,把 plusOne 的 dirty 設置為 true,然后通知它的依賴 effect 執行 plusOne.value + count.value。這個時候,由于 dirty 為 true,就會再次執行 plusOne 的 getter 計算新值,拿到了新值 2, 再加上 1 就得到 3。執行完 plusOne 的 runner 以及依賴更新之后,再去執行 count 的普通effect 依賴,從而去執行 plusOne.value + count.value,這個時候 plusOne dirty 為 false, 直接返回上次的計算結果 2,然后再加 1 就又得到 3。

如果我們把 computed runner 和 effect 的執行順序換一下會怎樣呢?我來告訴你,會輸出如下結果:

1 
2 
3

第一次輸出 1 很好理解,因為流程是一樣的。第二次為什么會輸出 2 呢?我們來分析一下,當我們執行 plus 函數修改 count 的值時,會觸發 plusOne 的 runner 和 effect 的執行,這一次我們先讓 effect 執行 plusOne.value + count.value,那么就會訪問 plusOne.value,但由于 plusOne 的 runner 還沒執行,所以此時 dirty 為 false,得到的值還是上一次的計算結果 1,然后再加 1 得到 2。

接著再執行 plusOne 的 runner,把 plusOne 的 dirty 設置為 true,然后通知它的依賴 effect 執行 plusOne.value + count.value,這個時候由于 dirty 為 true,就會再次執行 plusOne 的 getter 計算新值,拿到了 2,然后再加上 1 就得到 3。

知道原因后,我們再回過頭看例子。因為 effect 函數依賴了 plusOne 和 count,所以 plusOne 先計算會更合理,這就是為什么我們需要讓 computed runner 的執行優先于普通的 effect 函數。

以上就是“Vue3計算屬性是怎么實現的”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

向AI問一下細節

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

AI

阿克苏市| 海晏县| 北宁市| 隆回县| 军事| 迭部县| 蓝山县| 长垣县| 郓城县| 太康县| 香河县| 黄陵县| 贵州省| 无极县| 丹江口市| 韩城市| 芜湖市| 罗江县| 罗山县| 泽普县| 泗洪县| 中山市| 岫岩| 司法| 郯城县| 青神县| 安西县| 嫩江县| 四平市| 寻乌县| 怀宁县| 浮梁县| 安图县| 西青区| 米泉市| 墨玉县| 兴义市| 虹口区| 太原市| 锦屏县| 连云港市|