exportfunctioncomputed(getter: ComputedGetter<T>):ComputedRefexportfunctioncomputed( options: WritableComputedOptions<T>):WritableComputedRefexportfunctioncomputed( getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>){letgetter:ComputedGetterletsetter:ComputedSetterif(isFunction(getterOrOptions)){getter=getterOrOptionssetter=NOOP}else{ getter = getterOrOptions.get setter = getterOrOptions.set }returnnewComputedRefImpl( getter, setter, isFunction(getterOrOptions) || !getterOrOptions.set )asany}
在最開始使用函數(shù)重載的方式允許computed函數(shù)接受兩種類型的參數(shù):第一種是一個getter函數(shù), 第二種是一個帶get和set的對象潘鲫。
接下就是在函數(shù)內(nèi)部根據(jù)傳入的不同類型的參數(shù)初始化函數(shù)內(nèi)部的getter和setter函數(shù),如果傳入的是一個函數(shù)類型的參數(shù),那么getter就是這個函數(shù)毅待,setter就是一個空的操作,如果傳入的參數(shù)是一個對象,則getter就等于這個對象的get函數(shù),setter就等于這個對象的set函數(shù)别凹。
在函數(shù)的結(jié)尾返回了一個new ComputedRefImpl,并將前面我們標(biāo)準(zhǔn)化后的參數(shù)傳遞給了這個構(gòu)造函數(shù)。
下面我們就來分析一下ComputedRefImpl這個構(gòu)造函數(shù)。
ComputedRefImpl
classComputedRefImpl{// 緩存結(jié)果private _value!: T// 重新計算開關(guān)private _dirty =truepublic readonly effect: ReactiveEffect? public readonly __v_isRef =true;? public readonly [ReactiveFlags.IS_READONLY]: booleanconstructor(? ? getter: ComputedGetter<T>,
? ? private readonly _setter: ComputedSetter<T>,
? ? isReadonly: boolean
? ){// 對傳入的getter函數(shù)進行包裝this.effect = effect(getter, {lazy:true,// 調(diào)度執(zhí)行scheduler:() =>{if(!this._dirty) {this._dirty =true// 派發(fā)通知trigger(toRaw(this), TriggerOpTypes.SET,'value')? ? ? ? }? ? ? }? ? })? }// 訪問計算屬性的時候 默認(rèn)調(diào)用此時的get函數(shù)getvalue() {// 是否需要重新計算if(this._dirty) {this._value =this.effect()this._dirty =false}// 訪問的時候進行依賴收集 此時收集的是訪問這個計算屬性的副作用函數(shù)track(toRaw(this), TrackOpTypes.GET,'value')returnthis._value? }setvalue(newValue: T) {this._setter(newValue)? }}
ComputedRefImpl類在內(nèi)部維護了_value和_dirty這兩個非常重要的私有屬性,其中_value使用用來緩存我們計算的結(jié)果官份,_dirty是用來控制是否需要重現(xiàn)計算。接下來我們來看一下這個函數(shù)的內(nèi)部運行機制。
首先構(gòu)造函數(shù)在初始化的時候使用了effect函數(shù)對傳入getter進行了一層包裝(上一篇文章中我們分析過effect函數(shù)的作用就是將傳入的函數(shù)變成可響應(yīng)式的副作用函數(shù)),但是這里我們在effect中傳入了一些配置參數(shù)肥惭,還記得前面我們分析trigger函數(shù)的時候有這一段代碼:
construn =(effect: ReactiveEffect) =>{if(effect.options.scheduler) {? ? ? effect.options.scheduler(effect)? ? }else{? ? ? effect()? ? }? }effects.forEach(run)
當(dāng)屬性值發(fā)生改變之后,會觸發(fā)trigger函數(shù)進行派發(fā)更新紊搪,將所有依賴這個屬性的effect函數(shù)循環(huán)遍歷,使用run函數(shù)執(zhí)行effect,如果effect的參數(shù)中配置了scheduler蜜葱,則就執(zhí)行scheduler函數(shù),而不是執(zhí)行依賴的副作用函數(shù)嗦明。當(dāng)計算屬性依賴的屬性發(fā)生變化的時候笼沥,回執(zhí)行包裝getter函數(shù)的effect, 但是因為配置了scheduler函數(shù),所以真正執(zhí)行的是scheduler函數(shù)娶牌,在scheduler函數(shù)中并沒有執(zhí)行計算屬性的getter函數(shù)求取新值,而是將_dirty設(shè)置為false,然后通知依賴計算屬性的副作用函數(shù)進行更新, 當(dāng)依賴計算屬性的副作用函數(shù)收到通知的時候就會訪問計算屬性的get函數(shù)奔浅,此時會根據(jù)_dirty值來確定是否需要重新計算。
回到我們的這個構(gòu)造函數(shù)中诗良,只需要記得我們在構(gòu)造函數(shù)初始化三個重要的點:第一:對傳入的getter函數(shù)使用effect函數(shù)進行包裝汹桦。第二:在使用effect包裝的過程中,我們會執(zhí)行g(shù)etter函數(shù)鉴裹,此時getter函數(shù)執(zhí)行過程中對于訪問到的屬性會將當(dāng)前的這個計算屬性收集到對應(yīng)的依賴集合中,?第三:傳入了配置參數(shù)lazy和scheduler舞骆,這些配置參數(shù)在當(dāng)前的這個計算屬性所訂閱的屬性發(fā)生改變的時候,用來控制計算屬性的調(diào)度時機径荔。
接著我們繼續(xù)分析get value,當(dāng)我們訪問計算屬性的值時候?qū)嶋H上訪問的就是這個函數(shù)的返回值, 它會根據(jù)_dirty的值來判斷是否需要重新計算getter函數(shù)督禽,_dirty為true需要重新執(zhí)行effect函數(shù),并將effect的值置為false总处,否則就返回之前緩存的_value值狈惫。在訪問計算屬性值的階段會調(diào)用track函數(shù)進行依賴收集,此時收集的是訪問計算屬性值的副作用函數(shù), key始終是vlaue鹦马。
最后就是當(dāng)設(shè)置計算屬性的值的時候會執(zhí)行set函數(shù),然后調(diào)用我們傳入的_setter函數(shù)胧谈。
示例流程
至此計算屬性的執(zhí)行流程就分析完畢了,我們來結(jié)合一個示例來完整的過一遍整個流程:
龍華大道1號 http://www.kinghill.cn/Dynamics/2106.html
計算屬性:{{computedData}}