MobX + standard decorators


November 10, 2023

MobX now supports standard decorators! (And what else is next?)

Unfamiliar with MobX? Check out the gist of MobX! This blog post covers classes and decorators specifically, which is just one of the many ways of using MobX.

We're happy to announced that we just released a new minor version of MobX (6.11), which adds support for standard decorators. Decorators are currently a TC-39 stage 3 proposal, supported both by Babel and TypeScript with minimal setup. Here is a quick example, taken from the MobX cheat sheet, to show what using decorators looks like with the new standard (yes; no makeObservable calls anymore!):

import { observable, computed, action, flow } from "mobx"

class Doubler {
    @observable accessor value

    constructor(value) {
        this.value = value
    }

    @computed
    get double() {
        return this.value * 2
    }

    @action
    increment() {
        this.value++
    }

    @flow
    *fetch() {
        const response = yield fetch("/api/value")
        this.value = response.json()
    }
}

A short history of decorators and MobX

Decorators are popular in many frameworks like Angular, Lit and Nest. MobX has supported decorators since day 1. However, the old implementation was based on the legacy decorators implementation, which was primarily introduced by TypeScript but never supported by the TC-39 committee. The decorators working group, tirelessly led by Kristen Garrett, Daniel Ehrenberg, Yehuda Katz, Ron Buckton and others has been progressing towards a better decorator proposal. This proposal is now at stage 3 and is likely to land. From MobX's side, we have been actively engaging with the working group to make sure that the MobX use case is part of the design.

Given the expected upcoming changes between legacy and standard decorators, when introducing MobX 6.0.0 over 3 years ago, we moved away from promoting decorators by default. Instead, we provided an alternative (albeit more cumbersome) makeObservable / makeAutoObservable APIs. This was to make sure that we would be ready to migrate to standard decorators once they were stable enough. That time has arrived!

Legacy decorators are still supported as well, so either compiler configuration will work. However we will stop supporting legacy decorators in MobX 7. From our benchmarks, standard decorators incur 30% less runtime overhead, so we strongly recommend upgrading.

The difference in performance primarily stems from the stable class shape, which is guaranteed by the new decorators proposal, which was one of the design goals. One side effect of the new standard is that the @observable decorator can only be used on accessor fields (which generate a getter / setter pair behind the scenes).

The standard decorators implementation for MobX was largely written by @Matchlighter, already back in February (the delay in releasing this awesome work is all purely yours truly's fault). So a really big shout out and thanks for all the work that went into this!

Migrating to standard decorators

If you are not using classes to store state with MobX, don't bother at all. But otherwise, in order to migrate to standard decorators, follow these steps:

  1. Update your compiler configuration.

    1. In TypeScript, standard decorators will work in principle out of the box. Just make sure:

      1. To upgrade to TypeScript to version 5 or higher
      2. Make sure the compiler targets "ES2015" or higher
      3. Disable or remove the experimentalDecorators flag
    2. For Babel, support is provided through the proposal-decorators plugin.

      1. Make sure to specify version "2023-05" or higher
      2. Make sure any legacy decorator config is removed
    3. More details can be found on http://mobx.js.org/enabling-decorators.html
  2. If you were already using legacy decorators:

    1. Remove all calls to makeObservable(this) (that have no second argument).
    2. Replace all invocations of @observable with @observable accessor. Do the same for @observable.ref and @observable.shallow.
  3. If you were not using decorators already:

    1. First of all, if you are using makeObservable(this, annotations) / makeAutoObservable(this), there is no need to migrate to the new decorator setup!. This API will remain supported. Only do so if your team feels this is an improvement and worthwhile. For example for the co-location of the field declaration and observability specifier.
    2. At the moment of writing no codemod is yet available to convert make(Auto)Observable calls automatically into decorators. We recommend manually migrating those.
    3. If someone wants to contribute a codemod, feel free to take the mobx-undecorate codemod as a starting point.

The MobX cheat sheet has been updated as well to reflect the latest changes.

What else is next for Mobx?

In the short term, now that standard decorators support has landed, we can start working towards a leaner MobX, in which we can remove already deprecated APIs, legacy decorator support, and possibly some seldomly used features that have been accrued over time.

When we look at the farther horizon, interesting things are currently happening in JavaScript around signals. Signals are hotter than ever, popularized by libraries like Solid and recently Svelte Runes (and many more). As many of you have already realised, MobX has been providing this reactivity model already since 2015 (called Atoms in the MobX source code). Branding MobX as “Signals for React” wouldn't be far off the mark.

MobX provides some fairly unique abstractions on top of signals: A rich, mutable object model that is compatible with idiomatic JavaScript mutability APIs and allows to freely and deeply compose objects, classes, maps, arrays etc. - without additional hassle. We expect that other signal libraries will follow a similar object model in the near future, if they don't already.

However, there is another cool thing happening in this area; there are some explorations to find out whether signals can be standardized. If this becomes reality, it will make MobX leaner. But more interestingly, it will make the object model and reactivity utilities compatible with other frameworks that support the standard. It’s too early to draw any conclusions, but rest assured that MobX will be actively providing input to the working group, like we did for decorators. (This work is not yet public, to simplify some initial validation with existing frameworks, but it will be).

Thanks for all the support and believing in this state management model throughout the years.

The future of reactivity is bright!

Michel Weststrate