做者:秦志英javascript
上一篇文章中咱們分析了Vue3響應式的整個流程,本篇文章咱們將分析Vue3中的computed計算屬性是如何實現的。html
在Vue2中咱們已經對計算屬性瞭解的很清楚了,在Vue3中提供了一個computed
的函數做爲計算屬性的API,下面咱們來經過源碼的角度去分析計算屬性的運行流程。前端
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函數, 第二種是一個帶get和set的對象。getter
和setter
函數,若是傳入的是一個函數類型的參數,那麼getter就是這個函數,setter就是一個空的操做,若是傳入的參數是一個對象,則getter就等於這個對象的get函數,setter就等於這個對象的set函數。new 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函數執行過程當中對於訪問到的屬性會將當前的這個計算屬性收集到對應的依賴集合中, 第三:傳入了配置參數lazy
和scheduler
,這些配置參數在當前的這個計算屬性所訂閱的屬性發生改變的時候,用來控制計算屬性的調度時機。react
get value
,當咱們訪問計算屬性的值時候實際上訪問的就是這個函數的返回值, 它會根據_dirty
的值來判斷是否須要從新計算getter函數,_dirty
爲true須要從新執行effect函數,並將effect
的值置爲false,不然就返回以前緩存的_value
值。在訪問計算屬性值的階段會調用track
函數進行依賴收集,此時收集的是訪問計算屬性值的反作用函數, key始終是vlaue。_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
scheduler
,因此執行的是scheduler
函數,scheduler
函數並無當即執行getter函數進行從新計算,而是將ComputedRefImpl
類內部的私有變量_dirty
設置爲true,而後通知訂閱當前計算屬性的反作用函數進行更新操做。get value
函數,函數內部會根據_dirty值來判斷是否須要從新計算,因爲前面的scheduler
函數將_dirty設置爲true因此此時會調用getter函數的反作用函數effect,這個時候纔會從新計算並將結果返回,頁面數據更新。計算屬性兩個最大的特色就是typescript
_value
中的值。對 Electron 感興趣?請關注咱們的開源項目 Electron Playground,帶你極速上手 Electron。segmentfault
咱們每週五會精選一些有意思的文章和消息和你們分享,來掘金關注咱們的 曉前端週刊。
咱們是好將來 · 曉黑板前端技術團隊。
咱們會常常與你們分享最新最酷的行業技術知識。
歡迎來 知乎、掘金、Segmentfault、CSDN、簡書、開源中國、博客園 關注咱們。