如何使用 React + TypeScript 从 MobX 5 迁移到 MobX 6,而不添加具有 200% 语法噪音的臃肿代码?

问题描述

我正在尝试将我的 TypeScript/create-react-app 应用程序从 MobX 5 迁移到 MobX 6。在官方迁移指南 (https://mobx.js.org/migrating-from-4-or-5.html#upgrading-classes-to-use-makeobservable) 中,他们建议如下:

移除所有装饰器并在构造函数调用 makeObservable 和 明确定义应该使用哪个字段使哪个字段可观察 装饰器。

但他们没有说明如何使用 TypeScript 应用程序执行此操作。给出的示例总是直接使用纯 JavaScript 和 React.RenderDOM

这是一个简化示例的开始 - 我需要迁移的典型类:

@observer
export default class LoginPage extends React.Component<ILoginPageProps,{}> {
    @observable private email: string = process.env.REACT_APP_DEFAULT_LOGIN_EMAIL || "";
    @observable private password: string = process.env.REACT_APP_DEFAULT_LOGIN_PASSWORD || "";

    constructor(props: ILoginPageProps) {
        super(props);
        this.state = {};
    }

“官方”的做法是向构造函数添加 makeObservable 调用,并在那里设置注释。我已经设法手动重构了一个实际的类,并且它有效:

const LoginPage = observer(class LoginPageClass extends React.Component<ILoginPageProps,{}> {
    private email: string = process.env.REACT_APP_DEFAULT_LOGIN_EMAIL || "";
    private password: string = process.env.REACT_APP_DEFAULT_LOGIN_PASSWORD || "";
    private catpchaKey: number = 0;
    private catpchaToken: string | null = null;
    private errormessage: string = "";
    private overloaymessage: string = "";
    private checking: boolean = false;

    constructor(props: ILoginPageProps) {
        super(props);
        this.state = {};
        makeObservable<LoginPageClass,"email"|"password"|"catpchaKey"|"catpchaToken"|"errormessage"|"overloaymessage"|"checking"|"loginEnabled"|"verifyCaptchaCallback">(this,{
            email: observable,password: observable,catpchaKey: observable,catpchaToken: observable,errormessage: observable,overloaymessage: observable,checking: observable,loginEnabled: computed,verifyCaptchaCallback: action
        })
    }

    get loginEnabled(): boolean {
        return !!this.email && isValidEmail(this.email) && !!this.password && this.password.length > 4;
    }

    verifyCaptchaCallback = (token: string) => {
        this.catpchaToken = token;
    }
    
    // More methods and code here...
}
export default LoginPage;

重构后的代码存在难以言喻的问题。仅举几例:

  • 生成类型安全代码,每个可观察属性名称都必须键入 3 次。例如,名称“email”出现在字段声明中、makeObservable 调用的类型声明中以及 makeObservable 调用的 annotations 参数中。某些类最多可以包含一百个属性、计算属性和操作。
  • 无法再使用标准重构工具重构此代码,因为字段名称出现在不相关的位置。没有任何 IDE 可以正确重构字段名称或 MobX 操作方法
  • 所有属性 getter 和 action 的名称必须一式三份,而且它们也不能轻易重构。
  • 方法标记为“绑定操作”的信息与方法定义分开。如果您快速查看一个方法定义,那么您将无法判断这是否是 MobX 操作。
  • 属性 getter 相同 - 它们可能会被计算,但您必须向上滚动到另一个地方才能找到。

他们建议的另一件事是使用 npx mobx-undecorate - 但它不适用于 TypeScript。我试过了,它只是搞砸了我的代码。最终陷入了一个无限循环,耗尽了我所有的 cpu,打印了无尽的回溯等。最后它产生了如下代码

export default const LoginPage = observer(class LoginPage extends React.Component<ILoginPageProps,{}> {
    private email: string = process.env.REACT_APP_DEFAULT_LOGIN_EMAIL || "";
    private password: string = process.env.REACT_APP_DEFAULT_LOGIN_PASSWORD || "";

    constructor(props: ILoginPageProps) {
        super(props);

        makeObservable<LoginPage,"email" | "password">(this,});

        this.state = {};
    }

这太荒谬了,甚至不是有效的 TypeScript 代码。 (“导出认常量”只是无效的语法。)它用函数调用替换了我的类,同时创建了一个名称冲突。函数名和类名相同。

据我所知,推荐的方法让一切变得更糟。

所以我最后的问题是:有没有一种方法可以将 React/MobX/TypeScript 代码从 MobX 5 重构到 MobX 6,而不会使代码变得一团糟?有没有人找到实现这一目标的方法

解决方法

好吧,您似乎已经尝试了三种迁移方式中的第一种,但并不喜欢它。但这种方式只在以下情况下推荐:

如果您想在代码库中删除装饰器,并且项目还不太大,这是推荐的方法。

但我同意这种写 store 的方式对于 TS 来说确实很困难,很多重复等等。

如果您想继续使用装饰器,只需将它们保留在原处,并在构造函数中调用 makeObservable(this),就像文档说的那样:

留下所有装饰器并在构造函数中调用 makeObservable(this)。这将获取装饰器生成的元数据。如果您想限制 MobX 6 迁移的影响,这是推荐的方式

还有第三种方式,我认为是目前最好的方式,没有任何膨胀:

移除装饰器并在类构造函数中使用 makeAutoObservable(this)。

我只是引用了您已经阅读过的文档,我不能建议任何其他迁移方式。如果你有装饰器的大项目,那么首先使用第二种方式,如果你喜欢,然后逐渐迁移到第三种方式。