Mobx——observable 裝飾器的實(shí)現(xiàn)原理(離職拷貝版)

離職了,把 2019 年在公司寫的文檔 copy 出來。年頭有點(diǎn)久,可能寫的不太對(duì)硅蹦,也不是很想改了~
注:本文檔對(duì)應(yīng) mobx 版本為 4.15.4、mobx-vue 版本為 2.0.10

對(duì)比 Vue 的猜想

observable 的變量對(duì)應(yīng) Vue 雙向綁定的 data 數(shù)據(jù)闷煤。Vue 實(shí)現(xiàn) data 的雙向綁定是在 initState 中提针,將 data 里面的每一個(gè)變量進(jìn)行遞歸的 defineProperty。所以猜測(cè) observable 也是一個(gè)遞歸建立 get(依賴收集) & set(派發(fā)更新) 的過程

源碼解析
  1. observable 函數(shù)入口
const observable: IObservableFactory & IObservableFactories & { enhancer: IEnhancer<any> } = createObservable as any

function createObservable(v: any, arg2?: any, arg3?: any) {

    // @observable someProp;
    if (typeof arguments[1] === "string") {
        return deepDecorator.apply(null, arguments as any)
    }

    // it is an observable already, done
    if (isObservable(v)) return v

    // something that can be converted and mutated?
    const res = isPlainObject(v)
        ? observable.object(v, arg2, arg3)
        : Array.isArray(v)
        ? observable.array(v, arg2)
        : isES6Map(v)
        ? observable.map(v, arg2)
        : isES6Set(v)
        ? observable.set(v, arg2)
        : v

    // this value could be converted to a new observable data structure, return it
    if (res !== v) return res
}

2.deepDecorator 裝飾器入口
這里只看裝飾器的用法

const deepDecorator = createDecoratorForEnhancer(deepEnhancer)

export function deepEnhancer(v, _, name) {
    // it is an observable already, done
    if (isObservable(v)) return v

    // something that can be converted and mutated?
    if (Array.isArray(v)) return observable.array(v, { name })
    if (isPlainObject(v)) return observable.object(v, undefined, { name })
    if (isES6Map(v)) return observable.map(v, { name })
    if (isES6Set(v)) return observable.set(v, { name })

    return v
}

function createDecoratorForEnhancer(enhancer: IEnhancer<any>): IObservableDecorator {
    invariant(enhancer)
    const decorator = createPropDecorator(
        true,
        (
            target: any,
            propertyName: PropertyKey,
            descriptor: BabelDescriptor | undefined,
            _decoratorTarget,
            decoratorArgs: any[]
        ) => {
            const initialValue = descriptor
                ? descriptor.initializer
                    ? descriptor.initializer.call(target)
                    : descriptor.value
                : undefined
            defineObservableProperty(target, propertyName, initialValue, enhancer)
        }
    )
    const res = ... // 就是返回一個(gè) decorator曹傀,只不過 NODE_ENV 不是生產(chǎn)環(huán)境會(huì)多打一個(gè)異常的 log
    res.enhancer = enhancer
    return res
}

createDecoratorForEnhancer 中的 createPropDecorator 是核心操作辐脖,也是 Mobx 裝飾器的通用核心邏輯,他的第二個(gè)入?yún)?propertyCreator皆愉,就是將來要用來生成 get & set 邏輯的重要回調(diào)嗜价,接下來看看 createPropDecorator 的源碼

  1. createPropDecorator
function createPropDecorator(
    propertyInitiallyEnumerable: boolean,
    propertyCreator: PropertyCreator
) {
    return function decoratorFactory() {
        let decoratorArguments: any[]

        const decorator = function decorate(
            target: DecoratorTarget,
            prop: string,
            descriptor: BabelDescriptor | undefined,
            applyImmediately?: any
        ) {
            if (applyImmediately === true) {
                propertyCreator(target, prop, descriptor, target, decoratorArguments)
                return null
            }
            if (process.env.NODE_ENV !== "production" && !quacksLikeADecorator(arguments))
                fail("This function is a decorator, but it wasn't invoked like a decorator")
            if (!Object.prototype.hasOwnProperty.call(target, mobxPendingDecorators)) {
                const inheritedDecorators = target.__mobxDecorators
                addHiddenProp(target, "__mobxDecorators", { ...inheritedDecorators })
            }
            target.__mobxDecorators![prop] = {
                prop,
                propertyCreator,
                descriptor,
                decoratorTarget: target,
                decoratorArguments
            }
            return createPropertyInitializerDescriptor(prop, propertyInitiallyEnumerable)
        }

        if (quacksLikeADecorator(arguments)) {
            // @decorator
            decoratorArguments = EMPTY_ARRAY
            return decorator.apply(null, arguments as any)
        } else {
            // @decorator(args)
            decoratorArguments = Array.prototype.slice.call(arguments)
            return decorator
        }
    } as Function
}

正常來說,到這一層幕庐,createPropertyInitializerDescriptor 需要 return 裝飾器所需的 descriptor久锥, 追下 createPropertyInitializerDescriptor 的源碼,你會(huì)發(fā)現(xiàn)返回的 descriptor 并不是我們想要的那種具有依賴收集派發(fā)更新邏輯的 get 和 set异剥,這只是一個(gè)初始化的鉤子而已

  1. createPropertyInitializerDescriptor
const enumerableDescriptorCache: { [prop: string]: PropertyDescriptor } = {}
const nonEnumerableDescriptorCache: { [prop: string]: PropertyDescriptor } = {}

function createPropertyInitializerDescriptor(
    prop: string,
    enumerable: boolean
): PropertyDescriptor {
    const cache = enumerable ? enumerableDescriptorCache : nonEnumerableDescriptorCache
    return ( // 這里確實(shí)就是 createObservable 的最終返回值
        cache[prop] ||
        (cache[prop] = {
            configurable: true,
            enumerable: enumerable,
            get() {
                initializeInstance(this)
                return this[prop]
            },
            set(value) {
                initializeInstance(this)
                this[prop] = value
            }
        })
    )
}
  1. initializeInstance
function initializeInstance(target: any)
function initializeInstance(target: DecoratorTarget) {
    if (target.__mobxDidRunLazyInitializers === true) return
    const decorators = target.__mobxDecorators
    if (decorators) {
        addHiddenProp(target, "__mobxDidRunLazyInitializers", true)
        for (let key in decorators) {
            const d = decorators[key]
            d.propertyCreator(target, d.prop, d.descriptor, d.decoratorTarget, d.decoratorArguments)
        }
    }
}

這里的 decorators瑟由,也就是 target.__mobxDecorators, 是在 createPropDecorator 環(huán)節(jié)塞進(jìn)去的 propertyCreator 的相關(guān)變量冤寿,在這里進(jìn)行遍歷調(diào)用歹苦,最終會(huì)執(zhí)行每個(gè) decorators 的 defineObservableProperty(target, propertyName, initialValue, enhancer) 語句

  1. defineObservableProperty
    其實(shí)是遍歷 decorators 時(shí) propertyCreator 內(nèi)的 defineObservableProperty 操作
function defineObservableProperty(
    target: any,
    propName: string,
    newValue,
    enhancer: IEnhancer<any>
) {
    /**
    * asObservableObject 的 作用是在 類上增加了 $mobx 字段
    * 給 $mobx 字段賦值并 return 一個(gè) new ObservableObjectAdministration(target, name, defaultEnhancer)
    */
    const adm = asObservableObject(target)
    assertPropertyConfigurable(target, propName)


    if (hasInterceptors(adm)) {
        const change = interceptChange<IObjectWillChange>(adm, {
            object: target,
            name: propName,
            type: "add",
            newValue
        })
        if (!change) return
        newValue = (change as any).newValue
    }
    
    // 然后在 ObservableObjectAdministration 的 value 上掛 ObservableValue
    const observable = (adm.values[propName] = new ObservableValue(
        newValue,
        enhancer,
        `${adm.name}.${propName}`,
        false
    ))
    newValue = (observable as any).value // observableValue might have changed it

    // 核心邏輯
    Object.defineProperty(target, propName, generateObservablePropConfig(propName))
    if (adm.keys) adm.keys.push(propName)
    notifyPropertyAddition(adm, target, propName, newValue)
}
  1. generateObservablePropConfig
function generateObservablePropConfig(propName) {
    return (
        observablePropertyConfigs[propName] ||
        (observablePropertyConfigs[propName] = {
            configurable: true,
            enumerable: true,
            get() {
                return this.$mobx.read(this, propName)
            },
            set(v) {
                this.$mobx.write(this, propName, v)
            }
        })
    )
}

this.$mobx 就是上面的那個(gè) adm 即 new ObservableObjectAdministration,依賴收集發(fā)生在this.$mobx.read督怜,而派發(fā)更新則是在 this.$mobx.write殴瘦,Mobx 具體的依賴收集和派發(fā)更新的邏輯會(huì)在 derivation 的依賴收集過程 和 derivation 的派發(fā)更新過程 中講解

結(jié)論

其實(shí)繞了一圈,observable裝飾器大致就干了這么一件事情?

function observable(target:any, argument?:any): any {
    return {
        configurable: true,
        enumerable: true,
        get() {
            ...
        },
        set(v: any) {
            ... // 其他核心操作
            Object.defineProperty(this, argument, {
                configurable: true,
                enumerable: true,
                get() {
                    ...
                },
                set(v: any) {
                    ...
                }
            })
            ...
        }
    }
}

說下我個(gè)人的關(guān)注點(diǎn):

  1. enhancer 是之前說的 deepEnhancer 号杠,也就是對(duì)不同類型的各種 observe 的遞歸加工操作蚪腋,初始化的時(shí)候會(huì)觸發(fā),set 一個(gè)新 value 的時(shí)候也會(huì)觸發(fā)姨蟋,如果你 @observable 的變量是個(gè) string 或者 number屉凯,你會(huì)發(fā)現(xiàn) deepEnhancer 是覆蓋不到的,會(huì)直接 return 眼溶,observable 的函數(shù)式調(diào)用直接傳進(jìn)去一個(gè) number 也會(huì)報(bào)錯(cuò)悠砚,提示你使用 observable.box 來進(jìn)行對(duì)應(yīng)的操作。本來關(guān)注 deepEnhancer 是想看看裝飾器的代碼和函數(shù)的代碼是不是有比較高程度的復(fù)用偷仿,除了都在 deepEnhancer 以外哩簿,大流程基本是不太一樣的
  2. 最終 descriptor 里的 this.$mobx 指的是 ObservableObjectAdministration ,他的 get 是返回 value 上對(duì)應(yīng)響應(yīng)式變量的值酝静、而 set 則是改變這個(gè)值节榜,和一系列響應(yīng)。所有的變量都會(huì)在 ObservableObjectAdministration 一個(gè)名為 values 的 對(duì)象上:例如 value => ObservableValue别智;其中 ObservableValue: {key: value, value extends Atom}
  3. 通過 裝飾器 和 函數(shù) 創(chuàng)建的 observable 宗苍,雖然都是靠 this.$mobx 即 ObservableObjectAdministration 的 read 和 write 來進(jìn)行依賴收集和派發(fā)更新的,但是 @observable 的操作入口薄榛,掛在 ObservableObjectAdministration.value 上面讳窟, observable() 則是通過注入的 adm 也就是 ObservableObjectAdministration 實(shí)例, 來進(jìn)行相應(yīng)的操作。但是 @observable 如果是一個(gè)受 deepEnhancer 影響的引用類型敞恋,其成員都是走的 observable() 邏輯
  4. 因?yàn)轫?xiàng)目使用 mobx4丽啡,而 demo 使用了 mobx5,期間對(duì)比了 mobx4 和 mobx5 里 @observable 的源碼硬猫,主要的區(qū)別就是:
    a. 在 4版本 里面 $mobx 就是 $mobx 的字符串补箍, 5版本則是個(gè) Symbol
    b. this.$mobx 的 value 4 版本里是一個(gè)是對(duì)象, 5 版本是 Map
    c. 上述源碼解析的第 6 步的執(zhí)行的操作不一樣啸蜜,5 對(duì)應(yīng)的操作是 asObservableObject(target).addObservableProp(propertyName, initialValue, enhancer)坑雅,但是原理是一樣的,都是 new 一個(gè) ObservableObjectAdministration衬横,然后往他的 value 上掛 ObservableValue裹粤,然后執(zhí)行 Object.defineProperty,其中 descriptor 參數(shù)是通過 generateObservablePropConfig 返回的
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蜂林,一起剝皮案震驚了整個(gè)濱河市遥诉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌噪叙,老刑警劉巖突那,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異构眯,居然都是意外死亡愕难,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門惫霸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猫缭,“玉大人,你說我怎么就攤上這事壹店〔碌ぃ” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵硅卢,是天一觀的道長(zhǎng)射窒。 經(jīng)常有香客問我藏杖,道長(zhǎng),這世上最難降的妖魔是什么脉顿? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任蝌麸,我火速辦了婚禮,結(jié)果婚禮上艾疟,老公的妹妹穿的比我還像新娘来吩。我一直安慰自己,他們只是感情好蔽莱,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布弟疆。 她就那樣靜靜地躺著,像睡著了一般盗冷。 火紅的嫁衣襯著肌膚如雪怠苔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天仪糖,我揣著相機(jī)與錄音嘀略,去河邊找鬼。 笑死乓诽,一個(gè)胖子當(dāng)著我的面吹牛帜羊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鸠天,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼讼育,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了稠集?” 一聲冷哼從身側(cè)響起奶段,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎剥纷,沒想到半個(gè)月后痹籍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡晦鞋,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年蹲缠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悠垛。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡线定,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出确买,到底是詐尸還是另有隱情斤讥,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布湾趾,位于F島的核電站芭商,受9級(jí)特大地震影響派草,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜铛楣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一近迁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蛉艾,春花似錦、人聲如沸衷敌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽缴罗。三九已至助琐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間面氓,已是汗流浹背兵钮。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留舌界,地道東北人掘譬。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像呻拌,于是被迫代替她去往敵國(guó)和親葱轩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 離職了藐握,把 2019 年在公司寫的文檔 copy 出來靴拱。年頭有點(diǎn)久,可能寫的不太對(duì)猾普,也不是很想改了~注:本文檔對(duì)應(yīng)...
    zpkzpk閱讀 1,131評(píng)論 0 1
  • 一.Mobx是什么袜炕? 它是簡(jiǎn)單的,可擴(kuò)展的狀態(tài)管理初家。通過透明的函數(shù)響應(yīng)式編程偎窘,使?fàn)顟B(tài)變得簡(jiǎn)單和可擴(kuò)展。 Mbox的...
    李振亞_cb74閱讀 3,719評(píng)論 0 0
  • (1) Decorator 裝飾器 Decorator是一個(gè)函數(shù)溜在,用來修改類或者類的屬性的行為评架。說的直白點(diǎn)deco...
    woow_wu7閱讀 511評(píng)論 0 1
  • 上一節(jié)我們已經(jīng)了解了為什么要使用Mobx,簡(jiǎn)單理解Mobx(一):使用目的 這節(jié)我們來看看如何引入并使用Mobx ...
    反復(fù)橫跳的龍?zhí)?/span>閱讀 3,623評(píng)論 11 17
  • Mobx解決的問題 傳統(tǒng)React使用的數(shù)據(jù)管理庫為Redux炕泳。Redux要解決的問題是統(tǒng)一數(shù)據(jù)流纵诞,數(shù)據(jù)流完全可控...
    前端大神888閱讀 438評(píng)論 1 0