title | sidebar_label | hide_title |
---|---|---|
Decorators |
Decorators {🚀} |
true |
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:
- TypeScript (5.0 and higher, make sure that the
experimentalDecorators
flag is NOT enabled). Example commit. - For Babel make sure the plugin
proposal-decorators
is enabled with the highest version (currently2023-05
). Example commit.
// tsconfig.json
{
"compilerOptions": {
"experimentalDecorators": false /* or just remove the flag */
}
}
// babel.config.json (or equivalent)
{
"plugins": [
[
"@babel/plugin-proposal-decorators",
{
"version": "2023-05"
}
]
]
}
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:
- Disable / remove the
experimentalDecorators
flag from your TypeScript configuration (or Babel equivalent) - Remove all
makeObservable(this)
calls from class constructors that use decorators. - 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.accessor
s 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 whataccessor
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 usingserializr
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).
- 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
@action some_field = () => {}
was and is valid usage (ifmakeObservable()
is also used). However,@action accessor some_field = () => {}
is never valid.
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 {
/* ... */
}