MOBX的MakeAutoObservable方法究竟auto了什么?

一个栗子

ts 复制代码
class Todo {
    private title = ''
    constructor() {
        makeAutoObservable(this)
    }
    aaa() {
        console.log('aaa');
    }
    bbb = () => {
        console.log('bbb');
    }
    ccc = function() {
        console.log('ccc');
    }
}

例子中,title 是一个属性,aaa是原型上的方法,bbb和ccc都是实例方法。

向gpt4进行如下提问

根据官方文档的分析

如果你阅读过mobx的官方文档,你一定会知道makeAutoObservable做了什么。文档所说如下:

makeAutoObservable 就像是加强版的 makeObservable,在默认情况下它将推断所有的属性。

推断规则:

  • 所有 自有 属性都成为 observable
  • 所有 getters 都成为 computed
  • 所有 setters 都成为 action
  • 所有 prototype 中的 functions 都成为 autoAction
  • 所有 prototype 中的 generator functions 都成为 flow。(需要注意,generators 函数在某些编译器配置中无法被检测到,如果 flow 没有正常运行,请务必明确地指定 flow 注解。)
  • overrides 参数中标记为 false 的成员将不会被添加注解。例如,将其用于像标识符这样的只读字段。

根据推断规则 ,title 标记为observable,aaa()标记为autoAction,bbb() 和ccc()被标记为observable

此时的你是否会有疑惑:

  • 一向以计算机技术专家著称的gpt4,为什么它的回答与你所看的文档有如此大的出入?
  • bbb()和ccc()成为observable好象也没有太大的意义,难道是文档是旧版没有更新吗?
  • 究竟谁是对的?

尽信书不如无书,为了确认makeAutoObservable做了什么,我们就自己去看看。

真相探究

从源码的角度

下面所示源码均已简化逻辑:

ts 复制代码
// makeAutoObservable 方法的实现
export function makeAutoObservable<T extends object, AdditionalKeys extends PropertyKey = never>(
    target: T,
    overrides?: AnnotationsMap<T, NoInfer<AdditionalKeys>>,
    options?: MakeObservableOptions
): T {
    // 如果target是纯Object(构造方法为Object的对象)则执行该逻辑;
    // 但我们在类的构造函数中执行该方法时target是this,this是类的实例,
    // 它的构造方法指向该类的定义,所以不会执行下面的逻辑
    if (isPlainObject(target)) {
        return extendObservable(target, target, overrides, options)
    }
    // 执行该方法
    initObservable(() => {
        const adm: ObservableObjectAdministration = asObservableObject(target, options)[$mobx]
        if (!target[keysSymbol]) {
            const proto = Object.getPrototypeOf(target)
            // 收集类中属性和原型上的所有key值
            const keys = new Set([...ownKeys(target), ...ownKeys(proto)])
            keys.delete("constructor")
            keys.delete($mobx)
            addHiddenProp(proto, keysSymbol, keys)
        }
        // 遍历所有key值,并赋予标识
        target[keysSymbol].forEach(key =>
            adm.make_(
                key,
                // must pass "undefined" for { key: undefined }
                !overrides ? true : key in overrides ? overrides[key] : true
            )
        )
    })

    return target
}
ts 复制代码
export const autoAnnotation: Annotation = createAutoAnnotation()
export function createAutoAnnotation(options?: object): Annotation {
    return {
        annotationType_: AUTO,
        options_: options,
        make_,
        extend_,
        decorate_20223_
    }
}
// adm的make_方法
// key值为定义上类上的每一个属性和方法,annotation值为makeAutoObservable的overrides参数解析
// 由于overrides没有传所有这里的值是true
 make_(key: PropertyKey, annotation: Annotation | boolean): void {
        if (annotation === true) {
            // 该默认annotation是autoAnnotation,如上所示
            annotation = this.defaultAnnotation_
        }
        if (annotation === false) {
            return
        }
        assertAnnotable(this, annotation, key)
        if (!(key in this.target_)) {
           ...略
        }
        let source = this.target_
        // 遍历属性和原型上的属性,并赋予标识
        while (source && source !== objectPrototype) {
            const descriptor = getDescriptor(source, key)
            if (descriptor) {
                // 执行autoAnnotation上的make_方法对每一个定义在类上的属性和方法赋予标识
                const outcome = annotation.make_(this, key, descriptor, source)
                if (outcome === MakeResult.Cancel) {
                    return
                }
                if (outcome === MakeResult.Break) {
                    break
                }
            }
            source = Object.getPrototypeOf(source)
        }
        recordAnnotationApplied(this, annotation, key)
    }
ts 复制代码
// autoAnnotation上的make_方法
function make_(
    adm: ObservableObjectAdministration,
    key: PropertyKey,
    descriptor: PropertyDescriptor,
    source: object
): MakeResult {
    // getter 赋予 computed
    if (descriptor.get) {
        return computed.make_(adm, key, descriptor, source)
    }
    // lone setter -> action setter
    if (descriptor.set) {
        ...略
    }
    // function on proto 赋予 autoAction/flow
    if (source !== adm.target_ && typeof descriptor.value === "function") {
        if (isGenerator(descriptor.value)) {
            const flowAnnotation = this.options_?.autoBind ? flow.bound : flow
            return flowAnnotation.make_(adm, key, descriptor, source)
        }
        const actionAnnotation = this.options_?.autoBind ? autoAction.bound : autoAction
        return actionAnnotation.make_(adm, key, descriptor, source)
    }
    // other 赋予 observable
    // Copy props from proto as well, see test:
    // "decorate should work with Object.create"
    let observableAnnotation = this.options_?.deep === false ? observable.ref : observable
    // if function respect autoBind option
    if (typeof descriptor.value === "function" && this.options_?.autoBind) {
        descriptor.value = descriptor.value.bind(adm.proxy_ ?? adm.target_)
    }
    return observableAnnotation.make_(adm, key, descriptor, source)
}

由源码可知道,title、 bbb() 和ccc()标记为observable,aaa()标记为autoAction。我们再从实验的角度来验证一下。

实验的角度

ts 复制代码
import {makeAutoObservable, runInAction, autorun} from 'mobx'
class Todo {
    private title = ''
    constructor() {
        makeAutoObservable(this)
    }
    aaa() {
        console.log('aaa');
    }
    bbb = () => {
        console.log('bbb');
    }
    ccc = function() {
        console.log('ccc');
    }
}

test1:

ts 复制代码
const todo = new Todo;
autorun(() => {
    console.log(todo.aaa)
})

runInAction(() => {
    todo.aaa = 1
})
// 输出:
// [Function: aaa] { isMobxAction: true, toString: [Function (anonymous)] }

test2:

ts 复制代码
const todo = new Todo;
autorun(() => {
    console.log(todo.bbb)
})

runInAction(() => {
    todo.bbb = 1
})
// 输出:
// [Function: Todo@1.bbb] { isMobxAction: true, toString: [Function (anonymous)] }
// 1

test3:

ts 复制代码
const todo = new Todo;
autorun(() => {
    console.log(todo.ccc)
})

runInAction(() => {
    todo.ccc = 1
})
// 输出:
// [Function: Todo@1.ccc] { isMobxAction: true, toString: [Function (anonymous)] }
// 1

由测试结果所示,bbb()和ccc() 确定被标记为了observable

结论及推论

根据我们的源码探究和实验验证,我们得出了如下的结论及推论:

  • 官网的说明是正确的
  • 属性方法被自动标记为observable,在该方法中执行多个修改值的操作不会被视为同一个事务,而且该属性的可观测性只体现在重新赋值该属性。
  • 正确定义autoAction需要写成aaa方法的模式,不可写成bbbccc方法的模式,bbbccc方法虽然也可以触发action的行为,但没有action基于事务的批处理优化
  • 对于aaa方法的this指向问题,我们可以在定义makeAutoObservable方法的时候写成makeAutoObservable(this, {}, { autoBind: true }),这样就解决了aaa()方法内原型指向的问题。
  • autoActionaction标识的子集,相比action简化了异步流程,只要是被标记为autoAction的方法,在其中使用异步逻辑即可不再手动将赋值逻辑包裹在runInAction方法中。
  • makeAutoObservable的第三参options的默认值{autoBind: false, deep: true}
相关推荐
GISer_Jing4 小时前
React核心功能详解(一)
前端·react.js·前端框架
鑫宝Code8 小时前
【React】React Router:深入理解前端路由的工作原理
前端·react.js·前端框架
沉默璇年17 小时前
react中useMemo的使用场景
前端·react.js·前端框架
2401_8827275718 小时前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
红绿鲤鱼19 小时前
React-自定义Hook与逻辑共享
前端·react.js·前端框架
zhenryx1 天前
前端-react(class组件和Hooks)
前端·react.js·前端框架
Thomas游戏开发1 天前
Unity3D 逻辑服的Entity, ComponentData与System划分详解
前端框架·unity3d·游戏开发
前端青山1 天前
webpack进阶(一)
前端·javascript·webpack·前端框架·node.js
沉默璇年1 天前
react中Fragment的使用场景
前端·react.js·前端框架
Fanfffff7202 天前
React中组件通信的几种方式
前端·react.js·前端框架