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}
相关推荐
前端青山4 小时前
Node.js-增强 API 安全性和性能优化
开发语言·前端·javascript·性能优化·前端框架·node.js
qq_3643717213 小时前
VueRouter 导航故障问题
javascript·vue.js·前端框架·vue-router
何老生16 小时前
spring-boot(thymeleaf前端框架,简单了解)、( 跨域请求)
spring boot·前端框架
会发光的猪。18 小时前
前端vue3若依框架pnpm run dev启动报错
前端·javascript·vue.js·前端框架·bug
羊小猪~~19 小时前
前端入门一之HTML知识讲解
前端·javascript·css·前端框架·html·html5
王解1 天前
Jest进阶知识:深入测试 React Hooks-确保自定义逻辑的可靠性
前端·javascript·react.js·typescript·单元测试·前端框架
我命由我123451 天前
CesiumJS 案例 P20:监听鼠标滚轮、监听鼠标左键按下与松开、监听鼠标右键按下与松开、监听鼠标左击落点
开发语言·前端·javascript·前端框架·html·css3·html5
~甲壳虫2 天前
react中得类组件和函数组件有啥区别,怎么理解这两个函数
前端·react.js·前端框架
羊小猪~~2 天前
前端入门一之CSS知识详解
前端·javascript·css·vscode·前端框架·html·javas
new Vue()2 天前
Vue vs React:两大前端框架的区别解析
vue.js·react.js·前端框架