专业编程教程与实战项目分享平台

网站首页 > 技术文章 正文

Mobx-长篇源码解读,一文搞懂原理(1)

ins518 2024-10-11 16:41:10 技术文章 17 ℃ 0 评论

关于源码解读

首先欢迎大家star 或者 fork 我的源码解读系列之mobx!

地址:https://github.com/dxmz/Mobx-Chinese-Interpretation

断断续续花了一些时间,才把整个脉络理清楚,并且对于一些细节做了注释讲解,第一次解读一份较为复杂的源码,很多地方参考其他人的描述,因为我觉得描述得比自己清晰。

看源码是前端进阶的必走之路,首先能知晓其中原理,在写业务代码时能驾轻就熟,并且能扩展出更高阶的功能;其次明白原理后能帮助你快速排障以及避免bug的出现;最后解读源码能学习到优秀的编程范式,使自己的编程思维和习惯发生微妙的改变,这种改变才是最重要的。

mobx的版本是5.15.4,源码中我删除了v4版本的老代码,讲解基于v5版本。


主要概念

observable 可观察对象

在 mobx 中,我们需要在一个值或一个对象被改变时,触发相应的动作或响应,这种模式就是典型的观察者模式(或发布订阅模式),那么这里一个值或一个对象就是被观察者,动作或者响应充当观察者。

核心思想也比较容易理解,首先进行对象代理(proxy 或 defineProperty),这样对象就成了observable对象;其次观察者在执行主体逻辑时会访问代理对象属性,这时代理对象主动上报(reportObserved)自己到观察者的观察对象队列(observing)中,同时也会将观察者放入observable对象的观察者队列(observers)中,观察者和被观察者相互存有对方的引用,关系正式确立;最后,当设置代理对象属性时,代理对象触发(reportChanged)观察者执行主体逻辑。

可能文字描述起来很难弄清楚 mobx 的工作原理,所以接下来用代码以及调用链路详细说明。

阅读源码时,首先要清楚这个库的基本使用方式和接口含义,看起源码来才不会茫茫然不知所措,如果再有一份工作原理的调用链路指南,那就相当于铺好了路,自此走向了巅峰。


1、观察者和被观察者如何建立关系

这里先说明 mobx 中观察者是谁,mobx中观察者有reaction和autorun,autorun是特殊的reaction,而reaction实现自derivation,也就是说derivation是基础的观察者;而被观察者就是observable对象。

注: 下文中可观察变量和被观察者都简称 ‘observable’

调用链路

(1)observable收集观察者
reaction = new Reaction() --> reaction.track() --> trackDerivedFunction() --> bindDependencies(derivation) --> addObserver(observable, derivation) --> observable.observers.add(node)  
?
(2)观察者收集observable
observableValue.get() --> this.reportObserved(observable) --> derivation.newObserving![derivation.unboundDepsCount++] = observable
?
(3)observable变更时触发derivation执行
observableValue.set() --> this.reportChanged(observable) -->  propagateChanged(this) --> observable.observers.forEach((d) => {d.onBecomeStale()}) --> d.schedule() --> globalState.pendingReactions.push(d) 以及 runReactions() --> reactionScheduler(runReactionsHelper) --> 遍历执行相关联的衍生(derivation.runReaction()) --> this.onInvalidate()即用户定义的逻辑


2、核心逻辑讲解

Reaction

Reaction类中最核心的是track方法,track方法中开启了一个事务,在事务中调用trackDerivedFunction()执行用户定义的逻辑 fn() 以及进行关系绑定。

// 删减后的代码
track(fn: () => void) {
        startBatch()
  
        const result = trackDerivedFunction(this, fn, undefined)
     
        if (this.isDisposed) {
            // disposed during last run. Clean up everything that was bound after the dispose call.
            clearObserving(this)
        }
        
        endBatch()
    }


derivation

trackDerivedFunction()在derivation中定义。

function trackDerivedFunction<T>(derivation: IDerivation, f: () => T, context: any) {
    const prevAllowStateReads = allowStateReadsStart(true)
    // pre allocate array allocation + room for variation in deps
    // array will be trimmed by bindDependencies
    // 把 derivation.dependenciesState 和 derivation.observing数组内所有 ob.lowestObserverState 改为    IDerivationState.UP_TO_DATE (0)
    // 先调用 changeDependenciesStateTo0 方法将 derivation 和 observing 置为稳定态 UP_TO_DATE,主要是方便后续判断是否处在收集依赖阶段
    changeDependenciesStateTo0(derivation)
    // 提前为新 observing 申请空间,之后会trim
    derivation.newObserving = new Array(derivation.observing.length + 100)
    // unboundDepsCount记录尚未绑定的数量,observable被观察者观察时通过reportObserved()更新值
    derivation.unboundDepsCount = 0
    derivation.runId = ++globalState.runId
    // 保存Reaction上下文,将当前进行的reaction赋值给globalState.trackingDerivation供bindDependencies依赖收集用
    const prevTracking = globalState.trackingDerivation
    globalState.trackingDerivation = derivation
    let result
    if (globalState.disableErrorBoundaries === true) {
        result = f.call(context)
    } else {
        try {
            // 这一步将会触发 observable 的访问(因为f中会访问可观察对象的属性),
            // 即我们 ob.name --> $mobx.name.get() (ObservableValue.prototype.get)
            // -->reportObserved(ObservableValue)
            
            //调用track参数中的函数,在mobx-react里就是组件的render方法
            result = f.call(context)
        } catch (e) {
            result = new CaughtException(e)
        }
    }
    //恢复Reaction上下文
    globalState.trackingDerivation = prevTracking
    //Reaction跟Observable建立关系
    bindDependencies(derivation)
?
    warnAboutDerivationWithoutDependencies(derivation)
?
    allowStateReadsEnd(prevAllowStateReads)
?
    return result
}

其中result = f.call(context)会触发observable.get(),上面的调用链路中可以看到observable.observers.add(node)这一步将reaction观察者放入observable的观察者对列中。

接下来是Reaction跟Observable建立关系bindDependencies(derivation)。

function bindDependencies(derivation: IDerivation) {
    // invariant(derivation.dependenciesState !== IDerivationState.NOT_TRACKING, "INTERNAL ERROR bindDependencies expects derivation.dependenciesState !== -1");
    // 暂存旧的observable列表
    const prevObserving = derivation.observing
    // 用新的observable列表替换旧的列表
    const observing = (derivation.observing = derivation.newObserving!)
    let lowestNewObservingDerivationState = IDerivationState.UP_TO_DATE
?
    // Go through all new observables and check diffValue: (this list can contain duplicates):
    //   0: first occurrence, change to 1 and keep it
    //   1: extra occurrence, drop it
    // 遍历所有新的observable,去除重复的observable
    let i0 = 0,
        l = derivation.unboundDepsCount
    for (let i = 0; i < l; i++) {
        // 这里实际上用了双指针方法去重,i0为慢指针,i为快指针
        const dep = observing[i]
        // 跳过重复的值,即diffValue 等于 1的值;当跳过重复的值时i与i0就不相等了,i领先于i0
        if (dep.diffValue === 0) {
            dep.diffValue = 1
            if (i0 !== i) observing[i0] = dep
            i0++
        }
?
        // Upcast is 'safe' here, because if dep is IObservable, `dependenciesState` will be undefined,
        // not hitting the condition
        if (((dep as any) as IDerivation).dependenciesState > lowestNewObservingDerivationState) {
            lowestNewObservingDerivationState = ((dep as any) as IDerivation).dependenciesState
        }
    }
    observing.length = i0
?
    derivation.newObserving = null // newObserving shouldn't be needed outside tracking (statement moved down to work around FF bug, see #614)
?
    // Go through all old observables and check diffValue: (it is unique after last bindDependencies)
    //   0: it's not in new observables, unobserve it
    //   1: it keeps being observed, don't want to notify it. change to 0
    // 遍历旧observable列表:
    // diffValue为0表示不在新的observable列表中(每一轮新的observables的diffValue都会被设置为1),在derivation中解除观察;
    // diffValue为1表示该值仍在被观察(每一轮的依赖更新时,假如一个可观察对象dep在之前一轮也在依赖列表中,
    // 此时dep对象是同一个,新的一轮更新newObserving依赖时,diffValue会被更新为1);
    // 和newObserving去重操作一样巧妙,diffValue的作用很大呀
    l = prevObserving.length
    while (l--) {
        const dep = prevObserving[l]
        if (dep.diffValue === 0) {
            removeObserver(dep, derivation)
        }
        // 新旧遍历之后依旧将diffValue置0,即上面的 first occurrence
        dep.diffValue = 0
    }
?
    // Go through all new observables and check diffValue: (now it should be unique)
    //   0: it was set to 0 in last loop. don't need to do anything.
    //   1: it wasn't observed, let's observe it. set back to 0
    // 这里需要做这一步操作是因为第一步newObserving过滤后是新增的观察对象,
    // 第二步prevObserving将依赖的diffValue置0,但prevObserving中的依赖已经是addObserver()过的,
    // 所以就需要标记一下(diffValue置0),最后newObserving中的依赖diffValue为1的就进行addObserver()
    while (i0--) {
        const dep = observing[i0]
        if (dep.diffValue === 1) {
            dep.diffValue = 0
            // 给 observableValue 注册 observer
            // value change 时 observable(object, array, set...) 调用 this.atom.reportChanged() 发送通知
            // foreach 通知每个 reaction 调用 onBecomeStale,也就是 schedule 方法
?
            // 调用链路:value change --> observable设置值set(newVal) --> this.atom.reportChanged() 
            // --> propagateChanged(this) --> observable.observers.forEach调用observer.onBecomeStale() 
            // --> reaction.schedule() --> globalState.pendingReactions.push(this)以及runReactions() 
            // --> reactionScheduler() 
            // --> allReactions.forEach()执行每个reaction的reaction.runReaction() 
            // --> 执行每个reaction的this.onInvalidate()
            addObserver(dep, derivation)
        }
    }
    // NOTE: 收集完的依赖保存到 reaction.observing 中,在 getDependencyTree api 中会调用到
?
    // 对于新添加的观察数据,将 derivation 添加 globalState.pendingReactions 中,
    // 在当前事务周期中处理
    // Some new observed derivations may become stale during this derivation computation
    // so they have had no chance to propagate staleness (#916)
    if (lowestNewObservingDerivationState !== IDerivationState.UP_TO_DATE) {
        derivation.dependenciesState = lowestNewObservingDerivationState
        derivation.onBecomeStale()
    }
}


了解了衍生后,还有一个重要的知识点是状态,状态代表了衍生和可观察对象的不同阶段,通过状态可以更好地控制逻辑的执行。衍生或可观察对象有四个状态,值越高表示越不稳定:

NOT_TRACKING:初始时或衍生不再订阅可观察对象时的状态

UP_TO_DATE:表示当前的值是最新的或者衍生的依赖未发生变化,不需要重新计算

POSSIBLY_STALE:计算值的依赖发生变化时的状态,表示计算值可能有变更;比如计算值a依赖了observable b 和observable c,如果这时b和c都发生了变化,但最终的结果a未发生变化,那就不需要通知a的观察者observers执行逻辑了

STALE:表示衍生的逻辑需要重新执行,这时衍生依赖的对象发生了变更

enum IDerivationState {
    NOT_TRACKING = -1,
    UP_TO_DATE = 0,
    POSSIBLY_STALE = 1,
    STALE = 2
}


observable

对象经过 mobx 处理后变成可观察对象,这里的处理是指通过 proxy或者 defineProperty 代理。在mobx中一个基础类型的值可以成为observable,一个array /map / set / object也可以成为observable,但他们的处理方式有一些差别,具体看下文分析。

我们用装饰器方式作用变量时:

import { observable } from "mobx"
?
class Todo {
?
  id = Math.random()
?
  @observable title = ""
?
  @observable finished = false
?
}

observable实际调用了createObservable函数,而createObservable中又调用了observable的方法。

observable有什么方法呢,先看它的类型。

const observable: IObservableFactory & IObservableFactories & { enhancer: IEnhancer<any>}

其中IObservableFactories是个接口,observableFactories是它的具体实现,对基本数据类型以及对象进行代理和劫持。

const observableFactories: IObservableFactories = {
    // 对于基本类型 string, boolean, number 可以用 box 来劫持
    box<T = any>(value?: T, options?: CreateObservableOptions): IObservableValue<T> {
        if (arguments.length > 2) incorrectlyUsedAsDecorator("box")
        const o = asCreateObservableOptions(options)
        //  getEnhancerFromOptions(o) 生成enhancer
        return new ObservableValue(value, getEnhancerFromOptions(o), o.name, true, o.equals)
    },
    array<T = any>(initialValues?: T[], options?: CreateObservableOptions): IObservableArray<T> {
        if (arguments.length > 2) incorrectlyUsedAsDecorator("array")
        const o = asCreateObservableOptions(options)
        return createObservableArray(initialValues, getEnhancerFromOptions(o), o.name) as any
    },
    map<K = any, V = any>(
        initialValues?: IObservableMapInitialValues<K, V>,
        options?: CreateObservableOptions
    ): ObservableMap<K, V> {
        if (arguments.length > 2) incorrectlyUsedAsDecorator("map")
        const o = asCreateObservableOptions(options)
        return new ObservableMap<K, V>(initialValues, getEnhancerFromOptions(o), o.name)
    },
    set<T = any>(
        initialValues?: IObservableSetInitialValues<T>,
        options?: CreateObservableOptions
    ): ObservableSet<T> {
        if (arguments.length > 2) incorrectlyUsedAsDecorator("set")
        const o = asCreateObservableOptions(options)
        return new ObservableSet<T>(initialValues, getEnhancerFromOptions(o), o.name)
    },
    object<T = any>(
        props: T,
        decorators?: { [K in keyof T]: Function },
        options?: CreateObservableOptions
    ): T & IObservableObject {
        if (typeof arguments[1] === "string") incorrectlyUsedAsDecorator("object")
        const o = asCreateObservableOptions(options)
        if (o.proxy === false) {
            // 采用Object.defineProperty劫持
            return extendObservable({}, props, decorators, o) as any
        } else {
            const defaultDecorator = getDefaultDecoratorFromObjectOptions(o)
            // extendObservable中会将adm赋值给属性$mobx,以供proxy代理时调用
            const base = extendObservable({}, undefined, undefined, o) as any
            const proxy = createDynamicObservableObject(base)
            extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator)
            return proxy
        }
    },
    ref: refDecorator,
    shallow: shallowDecorator,
    deep: deepDecorator,
    struct: refStructDecorator
} as any

那既然observableFactories定义了数据类型的劫持方法,那怎么让observable也有同样的功能,接下来看:

Object.keys(observableFactories).forEach(name => (observable[name] = observableFactories[name]))

明白了吧。

回到刚才所说的observable实际调用了createObservable函数:

里面对传入的值或对象进行分类别劫持。

function createObservable(v: any, arg2?: any, arg3?: any) {
    // @observable someProp;
    if (typeof arguments[1] === "string" || typeof arguments[1] === "symbol") {
        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
}


object的劫持

object 函数接收三个参数,第三个参数为 options 可以定制化劫持方式。

使用示例:

const person = observable({
    name: 'lawler',
    get labelText() {
        return this.showAge ? `${this.name} (age: ${this.age})` : this.name;
    },
    setAge(age) {
        his.age = age;
    }
}, { 
    // 此为第二个参数 decorators
    // setAge设置为action类型,其他属性默认为 observables / computed
    setAge: action 
?
} /*, 这里传第三个 options 参数 *\/);

来看看它是怎么实现的:

object<T = any>(
        props: T,
        decorators?: { [K in keyof T]: Function },
        options?: CreateObservableOptions
    ): T & IObservableObject {
        if (typeof arguments[1] === "string") incorrectlyUsedAsDecorator("object")
        const o = asCreateObservableOptions(options)
        if (o.proxy === false) {
            // 采用Object.defineProperty劫持
            return extendObservable({}, props, decorators, o) as any
        } else {
            const defaultDecorator = getDefaultDecoratorFromObjectOptions(o)
            // extendObservable中会将adm赋值给属性$mobx,以供proxy代理时调用
            const base = extendObservable({}, undefined, undefined, o) as any
            const proxy = createDynamicObservableObject(base)
            extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator)
            return proxy
        }
    

第一步:生成配置选项

如果options传入的是个字符串,那么

const o = { name: thing, deep: true, proxy: true }

如果不传任何配置项,则返回默认配置项,所以默认是用proxy代理劫持

const defaultCreateObservableOptions = {
    deep: true,
    name: undefined,
    defaultDecorator: undefined,
    proxy: true
}

第二步: 生成默认装饰器

const defaultDecorator = getDefaultDecoratorFromObjectOptions(o) 默认项返回 deepDecorator

默认的 deepDecorator代码部分:

const deepDecorator = createDecoratorForEnhancer(deepEnhancer)
?
// 删除了一些开发环境的代码
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
            /**
             * asObservableObject,其传入参数为原始对象,
             * 返回值是ObservableObjectAdministration类型对象adm
             * 同时将adm绑定到$mobx属性上,共对象使用
             * 
             * 并且链式调用了 addObservableProp,
             * 通过 enhancer,把 propertyName 属性赋上劫持后的 initialValue
             */
            asObservableObject(target).addObservableProp(propertyName, initialValue, enhancer)
        }
    )
   
    const res: any = decorator
    res.enhancer = enhancer
    return res
}

重点在asObservableObject(target).addObservableProp(propertyName, initialValue, enhancer)中。

首先是`asObservableObject:

这个方法的作用是生成一个adm对象并返回,同时将adm赋值到对象的$mobx属性中,供对象使用。

export function asObservableObject(
    target: any,
    name: PropertyKey = "",
    defaultEnhancer: IEnhancer<any> = deepEnhancer
): ObservableObjectAdministration {
    if (Object.prototype.hasOwnProperty.call(target, $mobx)) return target[$mobx]

    if (!isPlainObject(target))
        name = (target.constructor.name || "ObservableObject") + "@" + getNextId()
    if (!name) name = "ObservableObject@" + getNextId()

    const adm = new ObservableObjectAdministration(
        target,
        new Map(),
        stringifyKey(name),
        defaultEnhancer
    )
    addHiddenProp(target, $mobx, adm)
    return adm
}

而ObservableObjectAdministration封装了一些read、write、has等方法

/**
 * 可以看出 adm 其实也是个封装类,具体围绕 values 展开,
 * 而 values 是个 Map,键为 PropertyKey,值为 ObservableValue像 read,write 等方法,
 * 最后都是调用的 ObservableValue 提供的 api
 */
class ObservableObjectAdministration
    constructor(
        public target: any,
        public values = new Map<PropertyKey, ObservableValue<any> | ComputedValue<any>>(),
        public name: string,
        public defaultEnhancer: IEnhancer<any>
    ) {
        this.keysAtom = new Atom(name + ".keys")
    }

    read(key: PropertyKey) {
        return this.values.get(key)!.get()
    }

    write(key: PropertyKey, newValue) {
        // 省略
    }

    has(key: PropertyKey) {
        // 省略
    }

    addObservableProp(
        propName: PropertyKey,
        newValue,
        enhancer: IEnhancer<any> = this.defaultEnhancer
    ) {
        // 省略
    }

    addComputedProp(
        propertyOwner: any, // where is the property declared?
        propName: PropertyKey,
        options: IComputedValueOptions<any>
    ) {
       // 省略
    }

    remove(key: PropertyKey) {
        // 省略
    }

    observe(callback: (changes: IObjectDidChange) => void, fireImmediately?: boolean): Lambda {
        return registerListener(this, callback)
    }
}

接下来调用adm.addObservableProp(propertyName, initialValue, enhancer):

为observable对象添加属性并劫持set / get操作;同时也将 initialValue 变成 ObservableValue,最后以属性名为键值存入adm.values对象中(实际的proxy代理时会用到,看下文)。

 addObservableProp(
        propName: PropertyKey,
        newValue,
        enhancer: IEnhancer<any> = this.defaultEnhancer
    ) {
        // this为adm
        const { target } = this
        assertPropertyConfigurable(target, propName)

        const observable = new ObservableValue(
            newValue,
            enhancer,
            `${this.name}.${stringifyKey(propName)}`,
            false
        )
        
        this.values.set(propName, observable)
        newValue = (observable as any).value // observableValue might have changed it

        Object.defineProperty(target, propName, generateObservablePropConfig(propName))
        this.notifyPropertyAddition(propName, newValue)
    }


第三步:base对象为一个空对象,但属性$mobx值为adm对象

const base = extendObservable({}, undefined, undefined, o) as any
function extendObservable<A extends Object, B extends Object>(
    target: A,
    properties?: B,
    decorators?: { [K in keyof B]?: Function },
    options?: CreateObservableOptions
): A & B {
    options = asCreateObservableOptions(options)
    const defaultDecorator = getDefaultDecoratorFromObjectOptions(options)
    //  这里target是空对象‘{}’
    initializeInstance(target) 
    // target的属性$mobx值为adm对象
    asObservableObject(target, options.name, defaultDecorator.enhancer) // make sure object is observable, even without initial props
    if (properties)
        extendObservableObjectWithProperties(target, properties, decorators, defaultDecorator)
    return target as any
}


第四步: 代理对象

const proxy = createDynamicObservableObject(base)
function createDynamicObservableObject(base) {
    // objectProxyTraps中定义了代理的属性(has/get/set等),其实还是调用了adm对象的方法
    const proxy = new Proxy(base, objectProxyTraps)
    base[$mobx].proxy = proxy
    return proxy
}

在objectProxyTraps的get方法中,会从adm.values.get(name)取出observable使用。

 get(target: IIsObservableObject, name: PropertyKey) {
        if (name === $mobx || name === "constructor" || name === mobxDidRunLazyInitializersSymbol)
            return target[name]
        const adm = getAdm(target)
        const observable = adm.values.get(name)
        if (observable instanceof Atom) {
            const result = (observable as any).get()
            if (result === undefined) {
                // This fixes #1796, because deleting a prop that has an
                // undefined value won't retrigger a observer (no visible effect),
                // the autorun wouldn't subscribe to future key changes (see also next comment)
                adm.has(name as any)
            }
            return result
        }
        // make sure we start listening to future keys
        // note that we only do this here for optimization
        if (isPropertyKey(name)) adm.has(name)
        return target[name]
    }


第五步:对属性进行处理,用各自的装饰器包裹

extendObservableObjectWithProperties(proxy, props, decorators, defaultDecorator)

这段代码的作用就是将对象的各种属性经过相应的装饰器包裹以后再赋值给对象的代理proxy的属性。

function extendObservableObjectWithProperties(
    target,
    properties,
    decorators,
    defaultDecorator
) {
    startBatch()
    try {
        const keys = getPlainObjectKeys(properties)
        for (const key of keys) {
            const descriptor = Object.getOwnPropertyDescriptor(properties, key)!
            
            const decorator =
                decorators && key in decorators
                    ? decorators[key]
                    : descriptor.get
                    ? computedDecorator
                    : defaultDecorator

            const resultDescriptor = decorator!(target, key, descriptor, true)
            if (
                resultDescriptor // otherwise, assume already applied, due to `applyToInstance`
            )
                Object.defineProperty(target, key, resultDescriptor)
        }
    } finally {
        endBatch()
    }
}

其中当decorator是用户定义的装饰器类别,这里有计算值装饰器computedDecorator, action类别的装饰器以及默认的defaultDecoratorobservable装饰器。

这里只讲一下action装饰器,其他的也比较容易理解。

在上面的使用示例中setAge: action,decorator就是action。

上代码:

const action: IActionFactory = function action(arg1, arg2?, arg3?, arg4?): any {
    // action(fn() {})
    if (arguments.length === 1 && typeof arg1 === "function")
        return createAction(arg1.name || "<unnamed action>", arg1)
    // action("name", fn() {})
    if (arguments.length === 2 && typeof arg2 === "function") return createAction(arg1, arg2)

    // @action("name") fn() {}
    if (arguments.length === 1 && typeof arg1 === "string") return namedActionDecorator(arg1)

    // @action fn() {}
    if (arg4 === true) {
        // apply to instance immediately
        addHiddenProp(arg1, arg2, createAction(arg1.name || arg2, arg3.value, this))
    } else {
        return namedActionDecorator(arg2).apply(null, arguments as any)
    }
} as any

这里我们传入了四个参数,且arg4 === true,进入addHiddenProp,作用是增加对象的不可遍历属性

function addHiddenProp(object: any, propName: PropertyKey, value: any) {
    Object.defineProperty(object, propName, {
        enumerable: false,
        writable: true,
        configurable: true,
        value
    })
}

接着我们看看createAction做了什么:

createAction返回一个函数, 而这个函数的返回值是执行descriptor的结果,即是上面示例中setAge()的执行结果。

function createAction(actionName: string, fn: Function, ref?: Object): Function & IAction {
    const res = function() {
        // 首先将runInfo等信息保存起来,然后执行 fn ,最后恢复刚才保存的信息并且会调用endBatch()
        return executeAction(actionName, fn, ref || this, arguments)
    }
    ;(res as any).isMobxAction = true
   
    return res as any
}

function executeAction(actionName: string, fn: Function, scope?: any, args?: IArguments) {
    // 将derivation等信息保存起来,在_endAction()中恢复
    const runInfo = _startAction(actionName, scope, args)
    try {
        return fn.apply(scope, args)
    } catch (err) {
        runInfo.error = err
        throw err
    } finally {
        _endAction(runInfo)
    }
}

好了,现在回过头看看 mobx 文档中定义的action:

它接收一个函数并返回具有同样签名的函数,但是用 transaction、untracked 和 allowStateChanges 包裹起来,尤其是 transaction 的自动应用会产生巨大的性能收益, 动作会分批处理变化并只在(最外层的)动作完成后通知计算值和反应。 这将确保在动作完成之前,在动作期间生成的中间值或未完成的值对应用的其余部分是不可见的。

这里的transaction是指_startAction和_endAction中开启的事务startBatch()和endBatch(),在事务处理期间globalState.trackingDerivation = null,意味着action处理期间不进行依赖收集(即描述中的untracked ,因为执行action可能访问observable的属性,触发get代理,上文中说到get代理会进行依赖收集,但action是不需要进行依赖收集的,它仅仅是执行一个动作);在endBatch()中接着执行runReactions()(即描述中所说的动作完成后通知计算值和反应); allowStateChanges 就比较好理解了,是控制observable对象是否只能在action中变更值,细节可以在文件configure.ts中查看。

那么addHiddenProp(arg1, arg2, createAction(arg1.name || arg2, arg3.value, this))就是把action类别的动作用装饰器action包裹起来再丢给proxy对象。

所以,observable.object(对象,装饰器, 配置项)最终会产生一个新的对象,这个新的对象是个代理对象。

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表