|
1 | 1 | ---
|
2 |
| -title: Enabling decorators |
3 |
| -sidebar_label: Enabling decorators {🚀} |
| 2 | +title: Decorators |
| 3 | +sidebar_label: Decorators {🚀} |
4 | 4 | hide_title: true
|
5 | 5 | ---
|
6 | 6 |
|
7 | 7 | <script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CEBD4KQ7&placement=mobxjsorg" id="_carbonads_js"></script>
|
8 | 8 |
|
9 |
| -# Enabling decorators {🚀} |
| 9 | +# Decorators |
10 | 10 |
|
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 |
12 | 12 |
|
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. |
14 | 77 |
|
15 | 78 | ```javascript
|
16 | 79 | import { makeObservable, observable, computed, action } from "mobx"
|
@@ -43,62 +106,37 @@ class TodoList {
|
43 | 106 | }
|
44 | 107 | }
|
45 | 108 | ```
|
| 109 | +</details> |
46 | 110 |
|
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> |
48 | 112 |
|
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: |
52 | 114 |
|
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` |
56 | 118 |
|
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> |
65 | 120 |
|
66 |
| -## How to enable decorator support |
| 121 | +<details id="gotchas"><summary>Decorator changes / gotchas</summary> |
67 | 122 |
|
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: |
69 | 124 |
|
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. |
71 | 130 |
|
72 |
| -Enable the compiler option `"experimentalDecorators": true` and `"useDefineForClassFields": true` in your `tsconfig.json`. |
| 131 | +</details> |
73 | 132 |
|
74 |
| -### Babel 7 |
| 133 | +## Using `observer` as a decorator |
75 | 134 |
|
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: |
77 | 136 |
|
78 | 137 | ```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 | + /* ... */ |
85 | 141 | }
|
86 | 142 | ```
|
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 |
|
0 commit comments