Skip to content

Latest commit

 

History

History
142 lines (107 loc) · 5.12 KB

enabling-decorators.md

File metadata and controls

142 lines (107 loc) · 5.12 KB
title sidebar_label hide_title
Decorators
Decorators {🚀}
true
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CEBD4KQ7&placement=mobxjsorg" id="_carbonads_js"></script>

Decorators

Enabling decorators

After years of alterations, ES decorators have finally reached Stage 3 in the TC39 process, meaning that they are quite stable and won't undergo breaking changes again like the previous decorator proposals have. MobX has implemented support for this new "2022.3/Stage 3" decorator syntax. With modern decorators, it is no longer needed to call makeObservable / makeAutoObservable.

2022.3 Decorators are supported in:

// tsconfig.json
{
    "compilerOptions": {
        "experimentalDecorators": false /* or just remove the flag */
    }
}

// babel.config.json (or equivalent)
{
    "plugins": [
        [
            "@babel/plugin-proposal-decorators",
            {
                "version": "2023-05"
            }
        ]
    ]
}

Using decorators

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

class Todo {
    id = Math.random()
    @observable accessor title = ""
    @observable accessor finished = false

    @action
    toggle() {
        this.finished = !this.finished
    }
}

class TodoList {
    @observable accessor todos = []

    @computed
    get unfinishedTodoCount() {
        return this.todos.filter(todo => !todo.finished).length
    }
}

Notice the usage of the new accessor keyword when using @observable. It is part of the 2022.3 spec and is required if you want to use modern decorators.

Using legacy decorators

We do not recommend codebases to use TypeScript / Babel legacy decorators since they well never become an official part of the language, but you can still use them. It does require a specific setup for transpilation:

MobX before version 6 encouraged the use of legacy decorators and mark things as observable, computed and action. While MobX 6 recommends against using these decorators (and instead use either modern decorators or makeObservable / makeAutoObservable), it is in the current major version still possible. Support for legacy decorators will be removed in MobX 7.

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

class Todo {
    id = Math.random()
    @observable title = ""
    @observable finished = false

    constructor() {
        makeObservable(this)
    }

    @action
    toggle() {
        this.finished = !this.finished
    }
}

class TodoList {
    @observable todos = []

    @computed
    get unfinishedTodoCount() {
        return this.todos.filter(todo => !todo.finished).length
    }

    constructor() {
        makeObservable(this)
    }
}
Migrating from legacy decorators

To migrate from legacy decorators to modern decorators, perform the following steps:

  1. Disable / remove the experimentalDecorators flag from your TypeScript configuration (or Babel equivalent)
  2. Remove all makeObservable(this) calls from class constructors that use decorators.
  3. Replace all instances of @observable (and variations) with @observable accessor
Decorator changes / gotchas

MobX' 2022.3 Decorators are very similar to the MobX 5 decorators, so usage is mostly the same, but there are some gotchas:

  • @observable accessor decorators are not enumerable. accessors do not have a direct equivalent in the past - they're a new concept in the language. We've chosen to make them non-enumerable, non-own properties in order to better follow the spirit of the ES language and what accessor means. The main cases for enumerability seem to have been around serialization and rest destructuring.
    • Regarding serialization, implicitly serializing all properties probably isn't ideal in an OOP-world anyway, so this doesn't seem like a substantial issue (consider implementing toJSON or using serializr as possible alternatives)
    • Addressing rest-destructuring, such is an anti-pattern in MobX - doing so would (likely unwantedly) touch all observables and make the observer overly-reactive).
  • @action some_field = () => {} was and is valid usage (if makeObservable() is also used). However, @action accessor some_field = () => {} is never valid.

Using observer as a decorator

The observer function from mobx-react is both a function and a decorator that can be used on class components:

@observer
class Timer extends React.Component {
    /* ... */
}