Skip to content

Commit c926097

Browse files
mweststrateMatchlighterrickpastoor
authored
TC 39 decorators (#3790)
* add support for Stage3/2022.3 decorators * bump Jest stack * Update docs/enabling-decorators.md Co-authored-by: Rick Pastoor <[email protected]> * bump from TS beta to 5.0.2 * rebase unto React 18 changes * fix some decorator documentation issues * bump test GC timeout * try fix GC test * v6.11.0-pre * Doc improvements * error on on missing `accessor` fields instead of silently ignoring * fix lint error * docs: added Babel decorator setup * added class decorator * add changelog entry for mobx-react * minor readme improvements * Removed dead code --------- Co-authored-by: Ethan Knapp <[email protected]> Co-authored-by: Matchlighter <[email protected]> Co-authored-by: Rick Pastoor <[email protected]>
1 parent 807c68a commit c926097

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+3523
-2771
lines changed

.changeset/afraid-cooks-nail.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"mobx": minor
3+
---
4+
5+
Added support for modern 2022.3 Decorators. [#3790](https://github.com/mobxjs/mobx/pull/3790)
6+
* [Installation / usage instruction](https://mobx.js.org/enabling-decorators.html).
7+
* [Introduction announcement](https://michel.codes/blogs/mobx-decorators)
8+
* Original PR by [@Matchlighter](https://github.com/Matchlighter) in [#3638](https://github.com/mobxjs/mobx/pull/3638),

.changeset/little-dancers-breathe.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
"mobx-react": minor
3+
---
4+
5+
Added support for modern 2022.3 Decorators. [#3790](https://github.com/mobxjs/mobx/pull/3790)
6+
* [Installation / usage instruction](https://mobx.js.org/enabling-decorators.html).
7+
* [Introduction announcement](https://michel.codes/blogs/mobx-decorators)
8+
* Original PR by [@Matchlighter](https://github.com/Matchlighter) in [#3638](https://github.com/mobxjs/mobx/pull/3638),

.github/workflows/build_and_test.yml

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ on:
55
branches: [main]
66
pull_request:
77
branches: [main]
8+
workflow_dispatch: {}
89

910
jobs:
1011
build:

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ MobX is made possible by the generosity of the sponsors below, and many other [i
5858

5959
_Anything that can be derived from the application state, should be. Automatically._
6060

61-
MobX is a battle-tested library that makes state management simple and scalable by transparently applying functional reactive programming.
61+
MobX is a signal based, battle-tested library that makes state management simple and scalable by transparently applying functional reactive programming.
6262
The philosophy behind MobX is simple:
6363

6464
<div class="benefits">

docs/README.md

+1-5
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@ _Simple, scalable state management._
1818

1919
---
2020

21-
> Documentation for the older **unsupported** V4/V5 can be [found here](https://github.com/mobxjs/mobx/blob/mobx4and5/docs/README.md), but be sure to read about the [current documentation first](https://mobx.js.org/about-this-documentation.html).
22-
23-
---
24-
2521
MobX is made possible by the generosity of the sponsors below, and many other [individual backers](backers-sponsors.md#backers). Sponsoring directly impacts the longevity of this project.
2622

2723
**🥇 Gold sponsors (\$3000+ total contribution):** <br/>
@@ -59,7 +55,7 @@ MobX is made possible by the generosity of the sponsors below, and many other [i
5955

6056
_Anything that can be derived from the application state, should be. Automatically._
6157

62-
MobX is a battle-tested library that makes state management simple and scalable by transparently applying functional reactive programming.
58+
MobX is a signal based, battle-tested library that makes state management simple and scalable by transparently applying functional reactive programming.
6359
The philosophy behind MobX is simple:
6460

6561
<div class="benefits">

docs/actions.md

+19
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Usage:
1313
- `action` _(annotation)_
1414
- `action(fn)`
1515
- `action(name, fn)`
16+
- `@action` _(method / field decorator)_
1617

1718
All applications have actions. An action is any piece of code that modifies the state. In principle, actions always happen in response to an event. For example, a button was clicked, some input changed, a websocket message arrived, etc.
1819

@@ -50,6 +51,23 @@ class Doubler {
5051
}
5152
```
5253

54+
<!--@action-->
55+
56+
```javascript
57+
import { observable, action } from "mobx"
58+
59+
class Doubler {
60+
@observable accessor value = 0
61+
62+
@action
63+
increment() {
64+
// Intermediate states will not become visible to observers.
65+
this.value++
66+
this.value++
67+
}
68+
}
69+
```
70+
5371
<!--makeAutoObservable-->
5472

5573
```javascript
@@ -399,6 +417,7 @@ Usage:
399417

400418
- `flow` _(annotation)_
401419
- `flow(function* (args) { })`
420+
- `@flow` _(method decorator)_
402421

403422
The `flow` wrapper is an optional alternative to `async` / `await` that makes it easier to
404423
work with MobX actions.

docs/api.md

+4-4
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ It is possible to use `extendObservable` to add observable fields to an existing
5757

5858
### `observable`
5959

60-
Usage: `observable(source, overrides?, options?)` or `observable` _(annotation)_
60+
Usage: `observable(source, overrides?, options?)`, `observable` _(annotation)_ or `@observable accessor` _(field decorator)_.
6161
<small>(<b>[further information](observable-state.md#observable)</b>)</small>
6262

6363
Clones an object and makes it observable. Source can be a plain object, array, Map or Set. By default, `observable` is applied recursively. If one of the encountered values is an object or array, that value will be passed through `observable` as well.
@@ -172,7 +172,7 @@ _An action is any piece of code that modifies the state._
172172

173173
### `action`
174174

175-
Usage: `action(fn)` or `action` _(annotation)_
175+
Usage: `action(fn)`, `action` _(annotation)_ or `@action` _(method / field decorator)_
176176
<small>(<b>[further information](actions.md)</b>)</small>
177177

178178
Use on functions that intend to modify the state.
@@ -186,7 +186,7 @@ Create a one-time action that is immediately invoked.
186186

187187
### `flow`
188188

189-
Usage: `flow(fn)` or `flow` _(annotation)_
189+
Usage: `flow(fn)`, `flow` _(annotation)_ or `@flow` _(generator method decorator)_
190190
<small>(<b>[further information](actions.md#using-flow-instead-of-async--await-)</b>)</small>
191191

192192
MobX friendly replacement for `async` / `await` that supports cancellation.
@@ -207,7 +207,7 @@ _Computed values can be used to derive information from other observables._
207207

208208
### `computed`
209209

210-
Usage: `computed(fn, options?)` or `computed(options?)` _(annotation)_
210+
Usage: `computed(fn, options?)`, `computed(options?)` _(annotation)_ or `@computed` _(getter decorator)_
211211
<small>(<b>[further information](computeds.md)</b>)</small>
212212

213213
Creates an observable value that is derived from other observables, but won't be recomputed unless one of the underlying observables changes.

docs/computeds.md

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Usage:
1313
- `computed` _(annotation)_
1414
- `computed(options)` _(annotation)_
1515
- `computed(fn, options?)`
16+
- `@computed` _(getter decorator)_
17+
- `@computed(options)` _(getter decorator)_
1618

1719
Computed values can be used to derive information from other observables.
1820
They evaluate lazily, caching their output and only recomputing if one of the underlying observables has changed.

docs/enabling-decorators.md

+88-50
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,79 @@
11
---
2-
title: Enabling decorators
3-
sidebar_label: Enabling decorators {🚀}
2+
title: Decorators
3+
sidebar_label: Decorators {🚀}
44
hide_title: true
55
---
66

77
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CEBD4KQ7&placement=mobxjsorg" id="_carbonads_js"></script>
88

9-
# Enabling decorators {🚀}
9+
# Decorators
1010

11-
MobX before version 6 encouraged the use of ES.next decorators to mark things as `observable`, `computed` and `action`. However, decorators are currently not an ES standard, and the process of standardization is taking a long time. It also looks like the standard will be different from the way decorators were implemented previously. In the interest of compatibility we have chosen to move away from them in MobX 6, and recommend the use of [`makeObservable` / `makeAutoObservable`](observable-state.md) instead.
11+
## Enabling decorators
1212

13-
But many existing codebases use decorators, and a lot of the documentation and tutorial material online uses them as well. The rule is that anything you can use as an annotation to `makeObservable`, such as `observable`, `action` and `computed`, you can also use as a decorator. So let's examine what that looks like:
13+
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.
14+
With modern decorators, it is no longer needed to call `makeObservable` / `makeAutoObservable`.
15+
16+
2022.3 Decorators are supported in:
17+
* TypeScript (5.0 and higher, make sure that the `experimentalDecorators` flag is NOT enabled). [Example commit](https://github.com/mweststrate/currencies-demo/commit/acb9ac8c148e8beef88042c847bb395131e85d60).
18+
* For Babel make sure the plugin [`proposal-decorators`](https://babeljs.io/docs/babel-plugin-proposal-decorators) is enabled with the highest version (currently `2023-05`). [Example commit](https://github.com/mweststrate/currencies-demo/commit/4999d2228208f3e1e10bc00a272046eaefde8585).
19+
20+
```js
21+
// tsconfig.json
22+
{
23+
"compilerOptions": {
24+
"experimentalDecorators": false /* or just remove the flag */
25+
}
26+
}
27+
28+
// babel.config.json (or equivalent)
29+
{
30+
"plugins": [
31+
[
32+
"@babel/plugin-proposal-decorators",
33+
{
34+
"version": "2023-05"
35+
}
36+
]
37+
]
38+
}
39+
```
40+
41+
## Using decorators
42+
43+
```javascript
44+
import { observable, computed, action } from "mobx"
45+
46+
class Todo {
47+
id = Math.random()
48+
@observable accessor title = ""
49+
@observable accessor finished = false
50+
51+
@action
52+
toggle() {
53+
this.finished = !this.finished
54+
}
55+
}
56+
57+
class TodoList {
58+
@observable accessor todos = []
59+
60+
@computed
61+
get unfinishedTodoCount() {
62+
return this.todos.filter(todo => !todo.finished).length
63+
}
64+
}
65+
```
66+
67+
Notice the usage of the new `accessor` keyword when using `@observable`.
68+
It is part of the 2022.3 spec and is required if you want to use modern decorators.
69+
70+
<details id="legacy-decorators"><summary>Using legacy decorators</summary>
71+
72+
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:
73+
74+
MobX before version 6 encouraged the use of legacy decorators and mark things as `observable`, `computed` and `action`.
75+
While MobX 6 recommends against using these decorators (and instead use either modern decorators or [`makeObservable` / `makeAutoObservable`](observable-state.md)), it is in the current major version still possible.
76+
Support for legacy decorators will be removed in MobX 7.
1477

1578
```javascript
1679
import { makeObservable, observable, computed, action } from "mobx"
@@ -43,62 +106,37 @@ class TodoList {
43106
}
44107
}
45108
```
109+
</details>
46110

47-
MobX before version 6 did not require the `makeObservable(this)` call in the constructor, but because it makes the implementation of decorator simpler and more compatible, it now does. This instructs MobX to make the instances observable following the information in the decorators – the decorators take the place of the second argument to `makeObservable`.
111+
<details id="migrate-decorators"><summary>Migrating from legacy decorators</summary>
48112

49-
We intend to continue to support decorators in this form.
50-
Any existing MobX 4/5 codebase can be migrated to use `makeObservable` calls by our [code-mod](https://www.npmjs.com/package/mobx-undecorate).
51-
When migrating from MobX 4/5 to 6, we recommend to always run the code-mod, to make sure the necessary `makeObservable` calls are generated.
113+
To migrate from legacy decorators to modern decorators, perform the following steps:
52114

53-
Check out the [Migrating from MobX 4/5 {🚀}](migrating-from-4-or-5.md) section.
54-
55-
## Using `observer` as a decorator
115+
1. Disable / remove the `experimentalDecorators` flag from your TypeScript configuration (or Babel equivalent)
116+
2. Remove all `makeObservable(this)` calls from class constructors that use decorators.
117+
3. Replace all instances of `@observable` (and variations) with `@observable accessor`
56118

57-
The `observer` function from `mobx-react` is both a function and a decorator that can be used on class components:
58-
59-
```javascript
60-
@observer
61-
class Timer extends React.Component {
62-
/* ... */
63-
}
64-
```
119+
</details>
65120

66-
## How to enable decorator support
121+
<details id="gotchas"><summary>Decorator changes / gotchas</summary>
67122

68-
We do not recommend new codebases that use MobX use decorators until the point when they become an official part of the language, but you can still use them. It does require setup for transpilation so you have to use Babel or TypeScript.
123+
MobX' 2022.3 Decorators are very similar to the MobX 5 decorators, so usage is mostly the same, but there are some gotchas:
69124

70-
### TypeScript
125+
- `@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 what `accessor` means.
126+
The main cases for enumerability seem to have been around serialization and rest destructuring.
127+
- 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)
128+
- Addressing rest-destructuring, such is an anti-pattern in MobX - doing so would (likely unwantedly) touch all observables and make the observer overly-reactive).
129+
- `@action some_field = () => {}` was and is valid usage (_if_ `makeObservable()` is also used). However, `@action accessor some_field = () => {}` is never valid.
71130

72-
Enable the compiler option `"experimentalDecorators": true` and `"useDefineForClassFields": true` in your `tsconfig.json`.
131+
</details>
73132

74-
### Babel 7
133+
## Using `observer` as a decorator
75134

76-
Install support for decorators: `npm i --save-dev @babel/plugin-proposal-class-properties @babel/plugin-proposal-decorators`. And enable it in your `.babelrc` file (note that the order is important):
135+
The `observer` function from `mobx-react` is both a function and a decorator that can be used on class components:
77136

78137
```javascript
79-
{
80-
"plugins": [
81-
["@babel/plugin-proposal-decorators", { "legacy": true }],
82-
["@babel/plugin-proposal-class-properties", { "loose": false }]
83-
// In contrast to MobX 4/5, "loose" must be false! ^
84-
]
138+
@observer
139+
class Timer extends React.Component {
140+
/* ... */
85141
}
86142
```
87-
88-
### Decorator syntax and Create React App (v2)
89-
90-
Decorators are only supported out of the box when using TypeScript in `create-react-app@^2.1.1` and newer. In older versions or when using vanilla JavaScript use eject, or the [customize-cra](https://github.com/arackaf/customize-cra) package.
91-
92-
## Disclaimer: Limitations of the decorator syntax
93-
94-
The current transpiler implementations of the decorator syntax are quite limited and don't behave exactly the same.
95-
Also, many compositional patterns are currently not possible with decorators, until the stage-2 proposal has been implemented by all transpilers.
96-
For this reason the scope of decorator syntax support in MobX is currently scoped to make sure that the supported features
97-
behave consistently across all environments.
98-
99-
The following patterns are not officially supported by the MobX community:
100-
101-
- Redefining decorated class members in inheritance trees
102-
- Decorating static class members
103-
- Combining decorators provided by MobX with other decorators
104-
- Hot module reloading (HMR) / React-hot-loader might not work as expected

docs/installation.md

+12-17
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,26 @@ There are two types of React bindings, `mobx-react-lite` supports only functiona
1818

1919
**CDN:** https://cdnjs.com/libraries/mobx / https://unpkg.com/mobx/dist/mobx.umd.production.min.js
2020

21+
# Transpilation settings
22+
23+
## MobX and Decorators
24+
25+
Based on your preference, MobX can be used with or without decorators.
26+
Both the legacy implementation and the standardised TC-39 version of decorators are currently supported.
27+
See [enabling-decorators.md](enabling-decorators.md) for more details on how to enable them.
28+
Legacy decorator support will be removed in MobX 7, in favor of the standard.
29+
2130
## Use spec compliant transpilation for class properties
2231

23-
⚠️ **Warning:** When using MobX with TypeScript or Babel, and you plan to use classes; make sure to update your configuration to use a TC-39 spec compliant transpilation for class fields, since this is not the default. Without this, class fields cannot be made observable before they are initialized.
32+
When using MobX with TypeScript or Babel, and you plan to use classes; make sure to update your configuration to use a TC-39 spec compliant transpilation for class fields, since this is not always the default. Without this, class fields cannot be made observable before they are initialized.
2433

2534
- **TypeScript**: Set the compiler option `"useDefineForClassFields": true`.
2635
- **Babel**: Make sure to use at least version 7.12, with the following configuration:
2736
```json
2837
{
2938
// Babel < 7.13.0
3039
"plugins": [["@babel/plugin-proposal-class-properties", { "loose": false }]],
31-
40+
3241
// Babel >= 7.13.0 (https://babeljs.io/docs/en/assumptions)
3342
"plugins": [["@babel/plugin-proposal-class-properties"]],
3443
"assumptions": {
@@ -53,21 +62,7 @@ import { configure } from "mobx"
5362
configure({ useProxies: "never" }) // Or "ifavailable".
5463
```
5564

56-
## MobX and Decorators
57-
58-
If you have used MobX before, or if you followed online tutorials, you probably saw MobX with decorators like `@observable`.
59-
In MobX 6, we have chosen to move away from decorators by default, for maximum compatibility with standard JavaScript.
60-
They can still be used if you [enable them](enabling-decorators.md) though.
61-
62-
## Development vs production
63-
64-
Unless you're using pre-build distribution ending with `.[production|development].min.js`, Mobx uses `process.env.NODE_ENV` variable to detect the environment. Make sure it's set to `"production"` on production. This is usually done by your favourite bundler:
65-
[webpack](https://reactjs.org/docs/optimizing-performance.html#webpack)
66-
[Rollup](https://reactjs.org/docs/optimizing-performance.html#rollup)
67-
[Browserify](https://reactjs.org/docs/optimizing-performance.html#browserify)
68-
[Brunch](https://reactjs.org/docs/optimizing-performance.html#brunch)
69-
70-
Most of the safety checks, like [`enforceAction`](https://mobx.js.org/configuration.html#enforceactions) and similar, happens on development only.
65+
This option will be removed in MobX 7.
7166

7267
## MobX on other frameworks / platforms
7368

0 commit comments

Comments
 (0)