├── API.md
├── README.md
└── docs
├── API_header.md
├── getting-started.md
└── middleware.md
/API.md:
--------------------------------------------------------------------------------
1 | # Mobx-State-Tree API 参考教程
2 |
3 | _这份参考教程列出了 MST 暴露的所有方法。 无论你是对代码进行改进,还是帮助增加更多描述示例都是非常值得赞赏的贡献!请注意文档是通过源码生成的,大部分的方法都定义在 [mst-operations.ts](https://github.com/mobxjs/mobx-state-tree/blob/master/src/core/mst-operations.ts) 文件中。_
4 |
5 |
6 |
7 | ### 目录
8 |
9 | - [addDisposer](#adddisposer)
10 | - [addMiddleware](#addmiddleware)
11 | - [applyAction](#applyaction)
12 | - [applyPatch](#applypatch)
13 | - [applySnapshot](#applysnapshot)
14 | - [clone](#clone)
15 | - [ComplexType](#complextype)
16 | - [ComplexType](#complextype-1)
17 | - [ComplexType](#complextype-2)
18 | - [ComplexType](#complextype-3)
19 | - [ComplexType](#complextype-4)
20 | - [createActionTrackingMiddleware](#createactiontrackingmiddleware)
21 | - [decorate](#decorate)
22 | - [destroy](#destroy)
23 | - [detach](#detach)
24 | - [escapeJsonPath](#escapejsonpath)
25 | - [flow](#flow)
26 | - [getChildType](#getchildtype)
27 | - [getEnv](#getenv)
28 | - [getParent](#getparent)
29 | - [getPath](#getpath)
30 | - [getPathParts](#getpathparts)
31 | - [getRelativePath](#getrelativepath)
32 | - [getRoot](#getroot)
33 | - [getSnapshot](#getsnapshot)
34 | - [getType](#gettype)
35 | - [hasParent](#hasparent)
36 | - [Identifier](#identifier)
37 | - [IdentifierCache](#identifiercache)
38 | - [isAlive](#isalive)
39 | - [isProtected](#isprotected)
40 | - [isRoot](#isroot)
41 | - [isStateTreeNode](#isstatetreenode)
42 | - [Node](#node)
43 | - [onAction](#onaction)
44 | - [onPatch](#onpatch)
45 | - [onSnapshot](#onsnapshot)
46 | - [process](#process)
47 | - [protect](#protect)
48 | - [recordActions](#recordactions)
49 | - [recordPatches](#recordpatches)
50 | - [resolveIdentifier](#resolveidentifier)
51 | - [resolvePath](#resolvepath)
52 | - [StoredReference](#storedreference)
53 | - [tryResolve](#tryresolve)
54 | - [Type](#type)
55 | - [Type](#type-1)
56 | - [Type](#type-2)
57 | - [Type](#type-3)
58 | - [Type](#type-4)
59 | - [Type](#type-5)
60 | - [Type](#type-6)
61 | - [Type](#type-7)
62 | - [Type](#type-8)
63 | - [typecheck](#typecheck)
64 | - [types.array](#typesarray)
65 | - [types.boolean](#typesboolean)
66 | - [types.compose](#typescompose)
67 | - [types.Date](#typesdate)
68 | - [types.enumeration](#typesenumeration)
69 | - [types.frozen](#typesfrozen)
70 | - [types.identifier](#typesidentifier)
71 | - [types.late](#typeslate)
72 | - [types.literal](#typesliteral)
73 | - [types.map](#typesmap)
74 | - [types.maybe](#typesmaybe)
75 | - [types.model](#typesmodel)
76 | - [types.null](#typesnull)
77 | - [types.number](#typesnumber)
78 | - [types.optional](#typesoptional)
79 | - [types.reference](#typesreference)
80 | - [types.refinement](#typesrefinement)
81 | - [types.string](#typesstring)
82 | - [types.undefined](#typesundefined)
83 | - [types.union](#typesunion)
84 | - [unescapeJsonPath](#unescapejsonpath)
85 | - [unprotect](#unprotect)
86 | - [walk](#walk)
87 |
88 | ## addDisposer
89 |
90 | 使用此方法注册一个函数,当目标状态树被销毁的时候就会被调用。This is a useful alternative to managing
91 | cleanup methods yourself using the `beforeDestroy` hook.
92 |
93 | **参数**
94 |
95 | - `target` **IStateTreeNode**
96 | - `disposer`
97 |
98 | **Examples**
99 |
100 | ```javascript
101 | const Todo = types.model({
102 | title: types.string
103 | }).actions(self => ({
104 | afterCreate() {
105 | const autoSaveDisposer = reaction(
106 | () => getSnapshot(self),
107 | snapshot => sendSnapshotToServerSomehow(snapshot)
108 | )
109 | // stop sending updates to server if this
110 | // instance is destroyed
111 | addDisposer(self, autoSaveDisposer)
112 | }
113 | }))
114 | ```
115 |
116 | ## addMiddleware
117 |
118 | 中间件通常用来拦截那些在子树中被引用的任意 action。如果一个树是受保护的(默认情况),这意味着树的任何突变都将会通过你的中间件。更多详细信息,可查看[中间件文档](docs/middleware.md)。
119 |
120 | **参数**
121 |
122 | - `target` **IStateTreeNode**
123 | - `middleware`
124 |
125 | 返回 **IDisposer**
126 |
127 | ## applyAction
128 |
129 | 将一个 action 或者一系列 action 应用到一个单一 MobX 事务上。不会返回任何值。可以通过`onAction`中间件获取一个 action 的详细描述信息。
130 |
131 | **参数**
132 |
133 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
134 | - `actions` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<IActionCall>**
135 | - `options` **IActionCallOptions?**
136 |
137 | ## applyPatch
138 |
139 | 在给定的 model 上应用一个 JSON-patch 或者如果此 patch 无法被应用,则将其捞出来。可查看[patche 文档](https://github.com/mobxjs/mobx-state-tree#patches)。
140 |
141 | Can apply a single past, or an array of patches.
142 |
143 | **参数**
144 |
145 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
146 | - `patch` **IJsonPatch**
147 |
148 | ## applySnapshot
149 |
150 | 将一个快照应用到给定的 model 实例上,并且 patch 和 快照监听器也会被调用。
151 |
152 | **参数**
153 |
154 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
155 | - `snapshot` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
156 |
157 | ## clone
158 |
159 | 深拷贝给定的树节点,并返回一个新的树。快捷方式:`snapshot(x) = getType(x).create(getSnapshot(x))`。
160 |
161 | _Tip: clone will create a literal copy, including the same identifiers. To modify identifiers etc during cloning, don't use clone but take a snapshot of the tree, modify it, and create new instance_
162 |
163 | **参数**
164 |
165 | - `source` **T**
166 | - `keepEnvironment` **([boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean) | any)** indicates whether the clone should inherit the same environment (`true`, the default), or not have an environment (`false`). If an object is passed in as second argument, that will act as the environment for the cloned tree.
167 |
168 | Returns **T**
169 |
170 | ## ComplexType
171 |
172 | ## ComplexType
173 |
174 | ## ComplexType
175 |
176 | ## ComplexType
177 |
178 | ## ComplexType
179 |
180 | ## createActionTrackingMiddleware
181 |
182 | Convenience utility to create action based middleware that supports async processes more easily.
183 | All hooks are called for both synchronous and asynchronous actions. Except that either `onSuccess` or `onFail` is called
184 |
185 | The create middleware tracks the process of an action (assuming it passes the `filter`).
186 | `onResume` can return any value, which will be passed as second argument to any other hook. This makes it possible to keep state during a process.
187 |
188 | See the `atomic` middleware for an example
189 |
190 | **Parameters**
191 |
192 | - `hooks`
193 |
194 | Returns **IMiddlewareHandler**
195 |
196 | ## decorate
197 |
198 | 给指定的 action 绑定中间件
199 |
200 | **参数**
201 |
202 | - `middleware` **IMiddlewareHandler**
203 | - `fn`
204 | - `Function` } fn
205 |
206 | **Examples**
207 |
208 | ```javascript
209 | type.actions(self => {
210 | function takeA____() {
211 | self.toilet.donate()
212 | self.wipe()
213 | self.wipe()
214 | self.toilet.flush()
215 | }
216 | return {
217 | takeA____: decorate(atomic, takeA____)
218 | }
219 | })
220 | ```
221 |
222 | Returns **any** the original function
223 |
224 | ## destroy
225 |
226 | 将一个 model 元素从状态树中移除,并且标记为生命已终结,再也不能被使用。
227 |
228 | **参数**
229 |
230 | - `target`
231 |
232 | ## detach
233 |
234 | 从状态树中移除一个 model 元素,并将其移植到一个新的状态树上。
235 |
236 | **参数**
237 |
238 | - `target`
239 |
240 | ## escapeJsonPath
241 |
242 | escape slashes and backslashes
243 |
244 |
245 | **参数**
246 |
247 | - `str`
248 |
249 | ## flow
250 |
251 | 可查看 [异步 action](https://github.com/mobxjs/mobx-state-tree/blob/master/docs/async-actions.md)。
252 |
253 | **参数**
254 |
255 | - `asyncAction`
256 |
257 | 返回 **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)**
258 |
259 | ## getChildType
260 |
261 | Returns the _declared_ type of the given sub property of an object, array or map.
262 |
263 | **Parameters**
264 |
265 | - `object` **IStateTreeNode**
266 | - `child` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)**
267 |
268 | **Examples**
269 |
270 | ```javascript
271 | const Box = types.model({ x: 0, y: 0 })
272 | const box = Box.create()
273 |
274 | console.log(getChildType(box, "x").name) // 'number'
275 | ```
276 |
277 | Returns **IType<any, any>**
278 |
279 | ## getEnv
280 |
281 | Returns the environment of the current state tree. For more info on environments,
282 | see [Dependency injection](https://github.com/mobxjs/mobx-state-tree#dependency-injection)
283 |
284 | Returns an empty environment if the tree wasn't initialized with an environment
285 |
286 | **Parameters**
287 |
288 | - `target` **IStateTreeNode**
289 |
290 | Returns **any**
291 |
292 | ## getParent
293 |
294 | Returns the immediate parent of this object, or null.
295 |
296 | Note that the immediate parent can be either an object, map or array, and
297 | doesn't necessarily refer to the parent model
298 |
299 | **Parameters**
300 |
301 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
302 | - `depth` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** = 1, how far should we look upward?
303 |
304 | Returns **any**
305 |
306 | ## getPath
307 |
308 | Returns the path of the given object in the model tree
309 |
310 | **Parameters**
311 |
312 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
313 |
314 | Returns **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)**
315 |
316 | ## getPathParts
317 |
318 | Returns the path of the given object as unescaped string array
319 |
320 | **Parameters**
321 |
322 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
323 |
324 | Returns **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)>**
325 |
326 | ## getRelativePath
327 |
328 | Given two state tree nodes that are part of the same tree,
329 | returns the shortest jsonpath needed to navigate from the one to the other
330 |
331 | **Parameters**
332 |
333 | - `base` **IStateTreeNode**
334 | - `target` **IStateTreeNode**
335 |
336 | Returns **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)**
337 |
338 | ## getRoot
339 |
340 | Given an object in a model tree, returns the root object of that tree
341 |
342 | **Parameters**
343 |
344 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
345 |
346 | Returns **any**
347 |
348 | ## getSnapshot
349 |
350 | Calculates a snapshot from the given model instance. The snapshot will always reflect the latest state but use
351 | structural sharing where possible. Doesn't require MobX transactions to be completed.
352 |
353 | **Parameters**
354 |
355 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
356 |
357 | Returns **any**
358 |
359 | ## getType
360 |
361 | Returns the _actual_ type of the given tree node. (Or throws)
362 |
363 | **Parameters**
364 |
365 | - `object` **IStateTreeNode**
366 |
367 | Returns **IType<S, T>**
368 |
369 | ## hasParent
370 |
371 | Given a model instance, returns `true` if the object has a parent, that is, is part of another object, map or array
372 |
373 | **Parameters**
374 |
375 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
376 | - `depth` **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** = 1, how far should we look upward?
377 |
378 | Returns **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)**
379 |
380 | ## Identifier
381 |
382 | ## IdentifierCache
383 |
384 | ## isAlive
385 |
386 | Returns true if the given state tree node is not killed yet.
387 | This means that the node is still a part of a tree, and that `destroy`
388 | has not been called. If a node is not alive anymore, the only thing one can do with it
389 | is requesting it's last path and snapshot
390 |
391 | **Parameters**
392 |
393 | - `target` **IStateTreeNode**
394 |
395 | Returns **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)**
396 |
397 | ## isProtected
398 |
399 | Returns true if the object is in protected mode, @see protect
400 |
401 | **Parameters**
402 |
403 | - `target`
404 |
405 | ## isRoot
406 |
407 | Returns true if the given object is the root of a model tree
408 |
409 | **Parameters**
410 |
411 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
412 |
413 | Returns **[boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)**
414 |
415 | ## isStateTreeNode
416 |
417 | Returns true if the given value is a node in a state tree.
418 | More precisely, that is, if the value is an instance of a
419 | `types.model`, `types.array` or `types.map`.
420 |
421 | **Parameters**
422 |
423 | - `value` **any**
424 |
425 | ## Node
426 |
427 | ## onAction
428 |
429 | Registers a function that will be invoked for each action that is called on the provided model instance, or to any of its children.
430 | See [actions](https://github.com/mobxjs/mobx-state-tree#actions) for more details. onAction events are emitted only for the outermost called action in the stack.
431 | Action can also be intercepted by middleware using addMiddleware to change the function call before it will be run.
432 |
433 | Not all action arguments might be serializable. For unserializable arguments, a struct like `{ $MST_UNSERIALIZABLE: true, type: "someType" }` will be generated.
434 | MST Nodes are considered non-serializable as well (they could be serialized as there snapshot, but it is uncertain whether an replaying party will be able to handle such a non-instantiated snapshot).
435 | Rather, when using `onAction` middleware, one should consider in passing arguments which are 1: an id, 2: a (relative) path, or 3: a snapshot. Instead of a real MST node.
436 |
437 | **Parameters**
438 |
439 | - `target` **IStateTreeNode**
440 | - `listener`
441 | - `attachAfter` {boolean} (default false) fires the listener _after_ the action has executed instead of before.
442 |
443 | **Examples**
444 |
445 | ```javascript
446 | const Todo = types.model({
447 | task: types.string
448 | })
449 |
450 | const TodoStore = types.model({
451 | todos: types.array(Todo)
452 | }).actions(self => ({
453 | add(todo) {
454 | self.todos.push(todo);
455 | }
456 | }))
457 |
458 | const s = TodoStore.create({ todos: [] })
459 |
460 | let disposer = onAction(s, (call) => {
461 | console.log(call);
462 | })
463 |
464 | s.add({ task: "Grab a coffee" })
465 | // Logs: { name: "add", path: "", args: [{ task: "Grab a coffee" }] }
466 | ```
467 |
468 | Returns **IDisposer**
469 |
470 | ## onPatch
471 |
472 | Registers a function that will be invoked for each mutation that is applied to the provided model instance, or to any of its children.
473 | See [patches](https://github.com/mobxjs/mobx-state-tree#patches) for more details. onPatch events are emitted immediately and will not await the end of a transaction.
474 | Patches can be used to deep observe a model tree.
475 |
476 | **Parameters**
477 |
478 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)** the model instance from which to receive patches
479 | - `callback`
480 | - `boolean` **includeOldValue** if oldValue is included in the patches, they can be inverted. However patches will become much bigger and might not be suitable for efficient transport
481 |
482 | Returns **IDisposer** function to remove the listener
483 |
484 | ## onSnapshot
485 |
486 | Registers a function that is invoked whenever a new snapshot for the given model instance is available.
487 | The listener will only be fire at the and of the current MobX (trans)action.
488 | See [snapshots](https://github.com/mobxjs/mobx-state-tree#snapshots) for more details.
489 |
490 | **Parameters**
491 |
492 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
493 | - `callback`
494 |
495 | Returns **IDisposer**
496 |
497 | ## process
498 |
499 | **Parameters**
500 |
501 | - `asyncAction`
502 |
503 | Returns **[Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)**
504 |
505 | **Meta**
506 |
507 | - **deprecated**: has been renamed to `flow()`.
508 | See for more information.
509 | Note that the middleware event type prefixes starting with `process` now start with `flow`.
510 |
511 |
512 | ## protect
513 |
514 | The inverse of `unprotect`
515 |
516 | **Parameters**
517 |
518 | - `target` **IStateTreeNode**
519 |
520 | ## recordActions
521 |
522 | Small abstraction around `onAction` and `applyAction`, attaches an action listener to a tree and records all the actions emitted.
523 | Returns an recorder object with the following signature:
524 |
525 | **Parameters**
526 |
527 | - `subject` **IStateTreeNode**
528 |
529 | **Examples**
530 |
531 | ```javascript
532 | export interface IActionRecorder {
533 | // the recorded actions
534 | actions: ISerializedActionCall[]
535 | // stop recording actions
536 | stop(): any
537 | // apply all the recorded actions on the given object
538 | replay(target: IStateTreeNode): any
539 | }
540 | ```
541 |
542 | Returns **IPatchRecorder**
543 |
544 | ## recordPatches
545 |
546 | Small abstraction around `onPatch` and `applyPatch`, attaches a patch listener to a tree and records all the patches.
547 | Returns an recorder object with the following signature:
548 |
549 | **Parameters**
550 |
551 | - `subject` **IStateTreeNode**
552 |
553 | **Examples**
554 |
555 | ```javascript
556 | export interface IPatchRecorder {
557 | // the recorded patches
558 | patches: IJsonPatch[]
559 | // the inverse of the recorded patches
560 | inversePatches: IJsonPatch[]
561 | // stop recording patches
562 | stop(target?: IStateTreeNode): any
563 | // resume recording patches
564 | resume()
565 | // apply all the recorded patches on the given target (the original subject if omitted)
566 | replay(target?: IStateTreeNode): any
567 | // reverse apply the recorded patches on the given target (the original subject if omitted)
568 | // stops the recorder if not already stopped
569 | undo(): void
570 | }
571 | ```
572 |
573 | Returns **IPatchRecorder**
574 |
575 | ## resolveIdentifier
576 |
577 | Resolves a model instance given a root target, the type and the identifier you are searching for.
578 | Returns undefined if no value can be found.
579 |
580 | **Parameters**
581 |
582 | - `type` **IType<any, any>**
583 | - `target` **IStateTreeNode**
584 | - `identifier` **([string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number))**
585 |
586 | Returns **any**
587 |
588 | ## resolvePath
589 |
590 | Resolves a path relatively to a given object.
591 | Returns undefined if no value can be found.
592 |
593 | **Parameters**
594 |
595 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
596 | - `path` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** escaped json path
597 |
598 | Returns **any**
599 |
600 | ## StoredReference
601 |
602 | ## tryResolve
603 |
604 | **Parameters**
605 |
606 | - `target` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)**
607 | - `path` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)**
608 |
609 | Returns **any**
610 |
611 | ## Type
612 |
613 | ## Type
614 |
615 | ## Type
616 |
617 | ## Type
618 |
619 | ## Type
620 |
621 | ## Type
622 |
623 | ## Type
624 |
625 | ## Type
626 |
627 | ## Type
628 |
629 | ## typecheck
630 |
631 | Run's the typechecker on the given type.
632 | Throws if the given value is not according the provided type specification.
633 | Use this if you need typechecks even in a production build (by default all automatic runtime type checks will be skipped in production builds)
634 |
635 | **Parameters**
636 |
637 | - `type` **IType<any, any>**
638 | - `value` **any**
639 |
640 | ## types.array
641 |
642 | Creates an index based collection type who's children are all of a uniform declared type.
643 |
644 | This type will always produce [observable arrays](https://mobx.js.org/refguide/array.html)
645 |
646 | **Parameters**
647 |
648 | - `subtype` **IType<S, T>**
649 |
650 | **Examples**
651 |
652 | ```javascript
653 | const Todo = types.model({
654 | task: types.string
655 | })
656 |
657 | const TodoStore = types.model({
658 | todos: types.array(Todo)
659 | })
660 |
661 | const s = TodoStore.create({ todos: [] })
662 | unprotect(s) // needed to allow modifying outside of an action
663 | s.todos.push({ task: "Grab coffee" })
664 | console.log(s.todos[0]) // prints: "Grab coffee"
665 | ```
666 |
667 | Returns **IComplexType<[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<S>, IObservableArray<T>>**
668 |
669 | ## types.boolean
670 |
671 | Creates a type that can only contain a boolean value.
672 | This type is used for boolean values by default
673 |
674 | **Examples**
675 |
676 | ```javascript
677 | const Thing = types.model({
678 | isCool: types.boolean,
679 | isAwesome: false
680 | })
681 | ```
682 |
683 | ## types.compose
684 |
685 | Composes a new model from one or more existing model types.
686 | This method can be invoked in two forms:
687 | Given 2 or more model types, the types are composed into a new Type.
688 |
689 | ## types.Date
690 |
691 | Creates a type that can only contain a javascript Date value.
692 |
693 | **Examples**
694 |
695 | ```javascript
696 | const LogLine = types.model({
697 | timestamp: types.Date,
698 | })
699 |
700 | LogLine.create({ timestamp: new Date() })
701 | ```
702 |
703 | ## types.enumeration
704 |
705 | Can be used to create an string based enumeration.
706 | (note: this methods is just sugar for a union of string literals)
707 |
708 | **Parameters**
709 |
710 | - `name` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** descriptive name of the enumeration (optional)
711 | - `options` **[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)>** possible values this enumeration can have
712 |
713 | **Examples**
714 |
715 | ```javascript
716 | const TrafficLight = types.model({
717 | color: types.enumeration("Color", ["Red", "Orange", "Green"])
718 | })
719 | ```
720 |
721 | Returns **ISimpleType<[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)>**
722 |
723 | ## types.frozen
724 |
725 | Frozen can be used to story any value that is serializable in itself (that is valid JSON).
726 | Frozen values need to be immutable or treated as if immutable.
727 | Values stored in frozen will snapshotted as-is by MST, and internal changes will not be tracked.
728 |
729 | This is useful to store complex, but immutable values like vectors etc. It can form a powerful bridge to parts of your application that should be immutable, or that assume data to be immutable.
730 |
731 | **Examples**
732 |
733 | ```javascript
734 | const GameCharacter = types.model({
735 | name: string,
736 | location: types.frozen
737 | })
738 |
739 | const hero = GameCharacter.create({
740 | name: "Mario",
741 | location: { x: 7, y: 4 }
742 | })
743 |
744 | hero.location = { x: 10, y: 2 } // OK
745 | hero.location.x = 7 // Not ok!
746 | ```
747 |
748 | ## types.identifier
749 |
750 | Identifiers are used to make references, lifecycle events and reconciling works.
751 | Inside a state tree, for each type can exist only one instance for each given identifier.
752 | For example there couldn't be 2 instances of user with id 1. If you need more, consider using references.
753 | Identifier can be used only as type property of a model.
754 | This type accepts as parameter the value type of the identifier field that can be either string or number.
755 |
756 | **Parameters**
757 |
758 | - `baseType` **IType<T, T>**
759 |
760 | **Examples**
761 |
762 | ```javascript
763 | const Todo = types.model("Todo", {
764 | id: types.identifier(types.string),
765 | title: types.string
766 | })
767 | ```
768 |
769 | Returns **IType<T, T>**
770 |
771 | ## types.late
772 |
773 | Defines a type that gets implemented later. This is useful when you have to deal with circular dependencies.
774 | Please notice that when defining circular dependencies TypeScript isn't smart enough to inference them.
775 | You need to declare an interface to explicit the return type of the late parameter function.
776 |
777 | **Parameters**
778 |
779 | - `nameOrType`
780 | - `maybeType`
781 | - `name` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** The name to use for the type that will be returned.
782 | - `type` **ILateType<S, T>** A function that returns the type that will be defined.
783 |
784 | **Examples**
785 |
786 | ```javascript
787 | interface INode {
788 | childs: INode[]
789 | }
790 |
791 | // TypeScript is'nt smart enough to infer self referencing types.
792 | const Node = types.model({
793 | childs: types.optional(types.array(types.late(() => Node)), [])
794 | })
795 | ```
796 |
797 | Returns **IType<S, T>**
798 |
799 | ## types.literal
800 |
801 | The literal type will return a type that will match only the exact given type.
802 | The given value must be a primitive, in order to be serialized to a snapshot correctly.
803 | You can use literal to match exact strings for example the exact male or female string.
804 |
805 | **Parameters**
806 |
807 | - `value` **S** The value to use in the strict equal check
808 |
809 | **Examples**
810 |
811 | ```javascript
812 | const Person = types.model({
813 | name: types.string,
814 | gender: types.union(types.literal('male'), types.literal('female'))
815 | })
816 | ```
817 |
818 | Returns **ISimpleType<S>**
819 |
820 | ## types.map
821 |
822 | Creates a key based collection type who's children are all of a uniform declared type.
823 | If the type stored in a map has an identifier, it is mandatory to store the child under that identifier in the map.
824 |
825 | This type will always produce [observable maps](https://mobx.js.org/refguide/map.html)
826 |
827 | **Parameters**
828 |
829 | - `subtype` **IType<S, T>**
830 |
831 | **Examples**
832 |
833 | ```javascript
834 | const Todo = types.model({
835 | id: types.identifier(types.number),
836 | task: types.string
837 | })
838 |
839 | const TodoStore = types.model({
840 | todos: types.map(Todo)
841 | })
842 |
843 | const s = TodoStore.create({ todos: {} })
844 | unprotect(s)
845 | s.todos.set(17, { task: "Grab coffee", id: 17 })
846 | s.todos.put({ task: "Grab cookie", id: 18 }) // put will infer key from the identifier
847 | console.log(s.todos.get(17).task) // prints: "Grab coffee"
848 | ```
849 |
850 | Returns **IComplexType<[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<S>, IObservableArray<T>>**
851 |
852 | ## types.maybe
853 |
854 | Maybe will make a type nullable, and also null by default.
855 |
856 | **Parameters**
857 |
858 | - `type` **IType<S, T>** The type to make nullable
859 |
860 | Returns **(IType<(S | null | [undefined](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/undefined)), (T | null)>)**
861 |
862 | ## types.model
863 |
864 | Creates a new model type by providing a name, properties, volatile state and actions.
865 |
866 | See the [model type](https://github.com/mobxjs/mobx-state-tree#creating-models) description or the [getting started](https://github.com/mobxjs/mobx-state-tree/blob/master/docs/getting-started.md#getting-started-1) tutorial.
867 |
868 | ## types.null
869 |
870 | The type of the value `null`
871 |
872 | ## types.number
873 |
874 | Creates a type that can only contain a numeric value.
875 | This type is used for numeric values by default
876 |
877 | **Examples**
878 |
879 | ```javascript
880 | const Vector = types.model({
881 | x: types.number,
882 | y: 0
883 | })
884 | ```
885 |
886 | ## types.optional
887 |
888 | `types.optional` can be used to create a property with a default value.
889 | If the given value is not provided in the snapshot, it will default to the provided `defaultValue`.
890 | If `defaultValue` is a function, the function will be invoked for every new instance.
891 | Applying a snapshot in which the optional value is _not_ present, causes the value to be reset
892 |
893 | **Parameters**
894 |
895 | - `type`
896 | - `defaultValueOrFunction`
897 |
898 | **Examples**
899 |
900 | ```javascript
901 | const Todo = types.model({
902 | title: types.optional(types.string, "Test"),
903 | done: types.optional(types.boolean, false),
904 | created: types.optional(types.Date, () => new Date())
905 | })
906 |
907 | // it is now okay to omit 'created' and 'done'. created will get a freshly generated timestamp
908 | const todo = Todo.create({ title: "Get coffee "})
909 | ```
910 |
911 | ## types.reference
912 |
913 | Creates a reference to another type, which should have defined an identifier.
914 | See also the [reference and identifiers](https://github.com/mobxjs/mobx-state-tree#references-and-identifiers) section.
915 |
916 | **Parameters**
917 |
918 | - `subType`
919 |
920 | ## types.refinement
921 |
922 | `types.refinement(baseType, (snapshot) => boolean)` creates a type that is more specific than the base type, e.g. `types.refinement(types.string, value => value.length > 5)` to create a type of strings that can only be longer than 5.
923 |
924 | **Parameters**
925 |
926 | - `name` **[string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)**
927 | - `type` **IType<T, T>**
928 |
929 | Returns **IType<T, T>**
930 |
931 | ## types.string
932 |
933 | Creates a type that can only contain a string value.
934 | This type is used for string values by default
935 |
936 | **Examples**
937 |
938 | ```javascript
939 | const Person = types.model({
940 | firstName: types.string,
941 | lastName: "Doe"
942 | })
943 | ```
944 |
945 | ## types.undefined
946 |
947 | The type of the value `undefined`
948 |
949 | ## types.union
950 |
951 | types.union(dispatcher?, types...) create a union of multiple types. If the correct type cannot be inferred unambiguously from a snapshot, provide a dispatcher function of the form (snapshot) => Type.
952 |
953 | **Parameters**
954 |
955 | - `dispatchOrType` **(ITypeDispatcher | IType<any, any>)**
956 | - `otherTypes` **...[Array](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array)<IType<any, any>>**
957 |
958 | Returns **IType<any, any>**
959 |
960 | ## unescapeJsonPath
961 |
962 | unescape slashes and backslashes
963 |
964 | **Parameters**
965 |
966 | - `str`
967 |
968 | ## unprotect
969 |
970 | By default it is not allowed to directly modify a model. Models can only be modified through actions.
971 | However, in some cases you don't care about the advantages (like replayability, traceability, etc) this yields.
972 | For example because you are building a PoC or don't have any middleware attached to your tree.
973 |
974 | In that case you can disable this protection by calling `unprotect` on the root of your tree.
975 |
976 | **Parameters**
977 |
978 | - `target`
979 |
980 | **Examples**
981 |
982 | ```javascript
983 | const Todo = types.model({
984 | done: false
985 | }).actions(self => ({
986 | toggle() {
987 | self.done = !self.done
988 | }
989 | }))
990 |
991 | const todo = Todo.create()
992 | todo.done = true // throws!
993 | todo.toggle() // OK
994 | unprotect(todo)
995 | todo.done = false // OK
996 | ```
997 |
998 | ## walk
999 |
1000 | Performs a depth first walk through a tree
1001 |
1002 | **Parameters**
1003 |
1004 | - `target`
1005 | - `processor`
1006 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://badge.fury.io/js/mobx-state-tree)
2 | [](https://travis-ci.org/mobxjs/mobx-state-tree)
3 | [](https://coveralls.io/github/mobxjs/mobx-state-tree?branch=master)
4 | [](https://gitter.im/mobxjs/mobx-state-tree?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
5 |
6 |
7 |
8 | 原文链接:https://github.com/mobxjs/mobx-state-tree
9 |
10 |
11 |
13 |
mobx-state-tree
14 |
15 |
16 | Opinionated, transactional, MobX powered state container combining the best features of the immutable and mutable world for an optimal DX
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | > Mobx 和 MST 都是非常令人惊奇的软件, for me it is the missing brick when you build React based apps. Thanks for the great work!# Contents
25 |
26 | Nicolas Galle [full post](https://medium.com/@nicolasgall/i-started-to-use-react-last-year-and-i-loved-it-1ce8d53fec6a)
27 | Introduction blog post [The curious case of MobX state tree](https://medium.com/@mweststrate/the-curious-case-of-mobx-state-tree-7b4e22d461f)
28 |
29 | ---
30 | # Contents
31 |
32 | * [安装](#installation)
33 | * [入门指南](docs/getting-started.md)
34 | * [Talks & blogs](#talks--blogs)
35 | * [理念概述](#philosophy--overview)
36 | * [示例](#examples)
37 | * [概念](#concepts)
38 | * [树、类型和状态](#trees-types-and-state)
39 | * [创建 model](#creating-models)
40 | * [树的语义详解](#tree-semantics-in-detail)
41 | * [树的组成](#composing-trees)
42 | * [Actions](#actions)
43 | * [Views](#views)
44 | * [快照](#snapshots)
45 | * [补丁](#patches)
46 | * [引用和标识符](#references-and-identifiers)
47 | * [Listening to observables, snapshots, patches or actions](#listening-to-observables-snapshots-patches-or-actions)
48 | * [Volatile state](#volatile-state)
49 | * [依赖注入](#dependency-injection)
50 | * [Type 概述](#types-overview)
51 | * [生命周期勾子](#lifecycle-hooks-for-typesmodel)
52 | * [Api 概述](#api-overview)
53 | * [提示](#tips)
54 | * [常见问题](#FAQ)
55 | * [Api 完整文档](API.md)
56 | * [内置的中间件示例](packages/mst-middlewares/README.md)
57 | * [Changelog](changelog.md)
58 |
59 | # 安装
60 |
61 | * NPM: `npm install mobx mobx-state-tree --save`
62 | * Yarn: `yarn add mobx mobx-state-tree`
63 | * CDN: https://unpkg.com/mobx-state-tree@1.1.0/dist/mobx-state-tree.umd.js (暴露为 `window.mobxStateTree`)
64 | * Playground: [https://mattiamanzati.github.io/mobx-state-tree-playground/](https://mattiamanzati.github.io/mobx-state-tree-playground/) (with React UI, snapshots, patches and actions display)
65 | * CodeSandbox [TodoList demo](https://codesandbox.io/s/nZ26kGMD) fork for testing and bug reporting
66 |
67 | Typescript typings are included in the packages. Use `module: "commonjs"` or `moduleResolution: "node"` to make sure they are picked up automatically in any consuming project.
68 |
69 | # 入门指南
70 |
71 | 查看[入门指南](https://github.com/chenxiaochun/mobx-state-tree/blob/master/docs/getting-started.md)教程。
72 |
73 | # Talks & blogs
74 |
75 | * Talk React Europe 2017: [Next generation state management](https://www.youtube.com/watch?v=rwqwwn_46kA)
76 | * Talk ReactNext 2017: [React, but for Data](https://www.youtube.com/watch?v=xfC_xEA8Z1M&index=6&list=PLMYVq3z1QxSqq6D7jxVdqttOX7H_Brq8Z) ([slides](http://react-next-2017-slides.surge.sh/#1), [demo](https://codesandbox.io/s/8y4p23j32j))
77 | * Talk ReactJSDay Verona 2017: [Immutable or immutable? Both!]() ([slides](https://mweststrate.github.io/reactjsday2017-presentation/index.html#1), [demo](https://github.com/mweststrate/reatjsday2017-demo))
78 | * Talk React Alicante 2017: [Mutable or Immutable? Let's do both!]() ([slides](https://mattiamanzati.github.io/slides-react-alicante-2017/#2))
79 | * Talk ReactiveConf 2016: [Immer-mutable state management](https://www.youtube.com/watch?v=Ql8KUUUOHNc&list=PLa2ZZ09WYepMCRRGCRPhTYuTCat4TiDlX&index=30)
80 |
81 | # 理念概述
82 |
83 | `mobx-state-tree` is a state container that combines the _simplicity and ease of mutable data_ with the _traceability of immutable data_ and the _reactiveness and performance of observable data_.
84 |
85 | Simply put, mobx-state-tree tries to combine the best features of both immutability (transactionality, traceability and composition) and mutability (discoverability, co-location and encapsulation) based approaches to state management; everything to provide the best developer experience possible.
86 | Unlike MobX itself, mobx-state-tree is very opinionated on how data should be structured and updated.
87 | This makes it possible to solve many common problems out of the box.
88 |
89 | MST (mobx-state-tree) 的核心思想就是一个动态树。它由严格受保护的易变对象以及运行时类型信息浓缩而成。换句话说,每个树都是由一个结构(类型信息)和一个状态(数据)组成。
90 |
91 | 通过这个树,共享固定的对象结构,并自动生成快照。
92 |
93 | ```javascript
94 | import { types, onSnapshot } from "mobx-state-tree"
95 |
96 | const Todo = types.model("Todo", {
97 | title: types.string,
98 | done: false
99 | }).actions(self => ({
100 | toggle() {
101 | self.done = !self.done
102 | }
103 | }))
104 |
105 | const Store = types.model("Store", {
106 | todos: types.array(Todo)
107 | })
108 |
109 | // 通过快照创建一个实例
110 | const store = Store.create({ todos: [{
111 | title: "Get coffee"
112 | }]})
113 |
114 | // 监听新快照
115 | onSnapshot(store, (snapshot) => {
116 | console.dir(snapshot)
117 | })
118 |
119 | // 调用 action 去修改树
120 | store.todos[0].toggle()
121 | // prints: `{ todos: [{ title: "Get coffee", done: true }]}`
122 | ```
123 |
124 | 通过使用这些可用的类型信息,快照可以被转换为一个动态树,反之亦然,也是零操作即可转换回去。因此,MST 默认就能支持[时间旅行](https://github.com/mobxjs/mobx-state-tree/blob/master/packages/mst-example-boxes/src/stores/time.js)和类似于热替换的功能,可参考[示例](https://github.com/mobxjs/mobx-state-tree/blob/4c2b19ec4a6a8d74064e4b8a87c0f8b46e97e621/examples/boxes/src/stores/domain-state.js#L94)
125 |
126 | 之所以用这样的方式去设计类型信息,就是为了在设计时以及运行时都可以检查类型的正确性(在 TypeScript 中类型检查都是片刻就可以完成的)。
127 |
128 | ```
129 | [mobx-state-tree] Value '{\"todos\":[{\"turtle\":\"Get tea\"}]}' is not assignable to type: Store, expected an instance of Store or a snapshot like '{ todos: { title: string; done: boolean }[] }' instead.
130 | ```
131 |
132 | 运行时类型错误:
133 |
134 | 
135 |
136 | 设计时类型错误:
137 |
138 | 因为状态树是一个动态可变的模型,action 可对它直接进行改写,就像修改本地实例属性一样。可参见上面示例中的`toggleTodo()`方法。不需要你自己手动去产生一个新的状态树,因为 MST 的快照功能会自动产生一个新的出来。
139 |
140 | 易变的特性听起来好像有些可怕,但是先不要急着害怕,因为 action 给我们提供了很多有趣的特性:
141 | 1. 默认情况下,当前树只能被隶属于相同子树的 action 所修改。
142 | 2. Action 可以被复制,可以用来分发变化([example](https://github.com/mobxjs/mobx-state-tree/blob/master/packages/mst-example-boxes/src/stores/socket.js))。
143 | 3. 变化可以在一个相当细的粒度上被检测到,默认支持 [JSON patch](http://jsonpatch.com/)。
144 |
145 | Simply subscribing to the patch stream of a tree is another way to sync diffs with, for example, back-end servers or other clients ([example](https://github.com/mobxjs/mobx-state-tree/blob/master/packages/mst-example-boxes/src/stores/socket.js)).
146 |
147 | 
148 |
149 | MST 无缝结合了 [mobx](https://mobx.js.org) 和 [mobx-react](https://github.com/mobxjs/mobx-react)。更酷的是,因为它默认支持快照、中间件以及可复制的 action,所以它甚至可以替代 Redux 和 reducer。
150 |
151 | 这甚至可以将 Redux 开发者工具和 MST 联接起来。可查看一个[使用 Redux 与 MST 实现的待办事项示例]。
152 |
153 | 
154 |
155 | MST 内置了引用、标识符、依赖注入、变更记录以及类型循环定义(甚至是跨文件)。
156 |
157 | Even fancier: it analyses liveliness of objects, failing early when you try to access accidentally cached information! (More on that later)。
158 |
159 | MST 中的一个独特特性就是提供了很灵活的担保,当你试图从对象中读写不属于状态树的部分时,它就会抛出异常。这可以保护你免受那些仍然被引用的对象的意外读取,例如:闭包。
160 |
161 | ```javascript
162 | const oldTodo = store.todos[0]
163 | store.removeTodo(0)
164 |
165 | function logTodo(todo) {
166 | setTimeout(
167 | () => console.log(todo.title),
168 | 1000
169 | )
170 | )
171 |
172 | logTodo(store.todos[0])
173 | store.removeTodo(0)
174 | // 使用过期对象,在一秒钟后会抛出异常
175 | ```
176 |
177 | 可查看简洁明了的 [API](API.md) 文档。
178 |
179 | ---
180 |
181 | Another way to look at mobx-state-tree is to consider it, as argued by Daniel Earwicker, to be ["React, but for data"](http://danielearwicker.github.io/json_mobx_Like_React_but_for_Data_Part_2_.html).
182 |
183 | 类似于 React,MST 是由被称为 model 的组件所构成,它们用来捕捉小块的数据。它们通过 props(快照)被实例化以后,用来管理和保护它们内部的数据(使用 action)。当应用快照的时候,树节点就会尽量保持一致。这就类似于上下文环境机制,可将信息传递给深层的子节点。
184 |
185 | An introduction to the philosophy can be watched [here](https://youtu.be/ta8QKmNRXZM?t=21m52s). [Slides](https://immer-mutable-state.surge.sh/). Or, as [markdown](https://github.com/mweststrate/reactive2016-slides/blob/master/slides.md) to read it quickly.
186 |
187 | mobx-state-tree "immutable trees" and "graph model" features talk, ["Next Generation State Management"](https://www.youtube.com/watch?v=rwqwwn_46kA) at React Europe 2017. [Slides](http://tree.surge.sh/#1).
188 |
189 | # 示例
190 |
191 | * [Bookshop](https://github.com/mobxjs/mobx-state-tree/tree/master/packages/mst-example-bookshop) Example webshop application with references, identifiers, routing, testing etc.
192 | * [Boxes](https://github.com/mobxjs/mobx-state-tree/tree/master/packages/mst-example-boxes) Example app where one can draw, drag, and drop boxes. With time-travelling and multi-client synchronization over websockets.
193 | * [Redux TodoMVC](https://github.com/mobxjs/mobx-state-tree/tree/master/packages/mst-example-redux-todomvc) Redux TodoMVC application, except that the reducers are replaced with a MST. Tip: open the Redux devtools; they will work!
194 |
195 | # 概念
196 |
197 | MobX 状态树,顾名思义,就是用来构建状态的树。
198 |
199 | ### 树,类型和数据
200 |
201 | 树中的每一个节点都被两个东西所定义:它的类型(组成结构)和它的数据(目前的状态)。一个最简单的树可能是这样的:
202 |
203 | ```javascript
204 | import {types} from "mobx-state-tree"
205 |
206 | // 定义节点的组成结构
207 | const Todo = types.model({
208 | title: types.string
209 | })
210 |
211 | // 基于 Todo 节点声明创建一个树,并初始化数据
212 | const coffeeTodo = Todo.create({
213 | title: "Get coffee"
214 | })
215 | ```
216 |
217 | `types.model`通常用来定义对象的结构。其它内置的 type,还有 array、map和原始类型等,可以查看[types 综述](#types-overview)。
218 |
219 | ### 创建 model
220 |
221 | 在 MST 中最重要的类型就是`types.model`,用来描述一个对象的组成结构。例如:
222 |
223 | ```javascript
224 | const TodoStore = types
225 | .model("TodoStore", { // 1
226 | loaded: types.boolean // 2
227 | endpoint: "http://localhost", // 3
228 | todos: types.array(Todo), // 4
229 | selectedTodo: types.reference(Todo) // 5
230 | })
231 | .views(self => {
232 | return {
233 | get completedTodos() { // 6
234 | return self.todos.filter(t => t.done)
235 | },
236 | findTodosByUser(user) { // 7
237 | return self.todos.filter(t => t.assignee === user)
238 | }
239 | };
240 | })
241 | .actions(self => {
242 | return {
243 | addTodo(title) {
244 | self.todos.push({
245 | id: Math.random(),
246 | title
247 | })
248 | }
249 | };
250 | })
251 | ```
252 | 创建一个 model 时,建议给 model 方法定义一个名称以用于 debug 代码时使用;第二个对象参数定义了它所有的属性。(标有 1 的代码行)
253 |
254 | 属性参数是一个 key-value 的集合,key 表示属性名称,value 表示它的数据类型。下面的类型都是可接受的:
255 |
256 | 1. 可以是一个简单的原始类型,例如:`types.boolean`(标有 2 的代码行);可以是一个复杂的事先定义好的类型(标有 4 的代码行)
257 | 2. 可以直接使用一个原始类型值作为默认值(标有 3 的代码行),`endpoint: "http://localhost"`等同于`endpoint: types.optional(types.string, "http://localhost")`。MST 可以通过默认值推测出其数据类型是什么,拥有默认值的属性在创建快照时可以进行省略。
258 | 3. 可以是一个[计算属性](https://mobx.js.org/refguide/computed-decorator.html)(标有 6 的代码行)。MobX 会记忆和追踪计算属性。计算属性将不会被存储在快照里,也不会触发 patch 事件。也可能会给计算属性提供一个 setter 方法,并且 setter 方法只能在 action 里被调用。
259 | 4. 可以是一个 view 函数(标有 7 的代码行)。View 函数跟计算属性不同,它可以获得任意数量的参数。虽然它不会被记忆,但是它的值可以被 MobX 追踪。View 函数不允许修改 model,通常只是用它来检索 model 的信息。
260 |
261 | _提示:`(self) => ({ action1() { }, action2() { }})`是 ES6 的语法,它等价行`function (self) { return { action1: function() { }, action2: function() { } }}`,换句话说,它是一种返回对象字面量的简写方式。
262 | 一个 model 的每个成员之间必须强制添加一个逗号,这个与 class 的语法规则是完全不同的。_
263 |
264 | `types.model`支持链式写法,每一个链上的方法都会产生一个新的 type:
265 |
266 | * `.named(name)`方法会以新的 name 克隆当前 type
267 | * `.props(props)`方法会基于当前产生一个新的 type,并且可以添加或者覆盖掉指定的属性
268 | * `.actions(self => object literal with actions)`方法会基于当前产生一个新的 type,并且可以添加或者覆盖指定的 action
269 | * `.views(self => object literal with view functions)`方法会基于当前产生一个新的 type,并且可以添加或者覆盖指定的 view 方法
270 | * `.preProcessSnapshot(snapshot => snapshot)`通常在实例化一个新的 model 之前用来预处理原始 JSON 数据。可查看[生命周期勾子](#lifecycle-hooks-for-typesmodel)
271 |
272 | 注意:`views`和`actions`不会直接定义 action 和 view,但仍需要给它们传递一个 function。此 function 会在一个新的 model 实例被创建时引入,此 model 实例会被作为唯一参数传递给 function,通常被命名为`self`。
273 |
274 | 这会带来两点好处:
275 | 1. 所有的方法都会正确的绑定 this 上下文。
276 | 2. 闭包通常用来存储实例的私有状态或者方法。可查看[actions](#actions) 和 [volatile state](#volatile-state)。
277 |
278 | 简单示例:
279 |
280 | ```javascript
281 | const TodoStore = types
282 | .model("TodoStore", { /* props */ })
283 | .actions(self => {
284 | const instantiationTime = Date.now()
285 |
286 | function addTodo(title) {
287 | console.log(`Adding Todo ${title} after ${(Date.now() - instantiationTime) / 1000}s.`)
288 | self.todos.push({
289 | id: Math.random(),
290 | title
291 | })
292 | }
293 |
294 | return { addTodo }
295 | })
296 | ```
297 |
298 | 可以很完美的以任意顺序链式调用多个`views`和`props`。这种方式可以用来非常好的组织 types,以及混入一些实用的函数。每次的链式调用都会创建一个可以被它自己存储以及作为其它 types 的一部分重复使用的、新的、不可变的 type。
299 |
300 | 可以在 action 对象内定义生命周期的勾子,这些拥有预先定义的名称的 action 将会在特定时刻被执行。可查看[生命周期勾子](#lifecycle-hooks-for-typesmodel)。
301 |
302 | ### 树结构语义详解
303 |
304 | MST 树拥有非常特别的语义,这些语义的目的就是为了在你使用 MST 时进行约束。它带来的好处就是提供了各种各样的通用特性,比如:快照、可玩性等。如果这些约束不适用于你的应用,你最好使用普通的 mobx 去结合你的 model 类,这样对于你可能更好一些。
305 |
306 | 1. 在 MST 中每个对象被认为是一个“node”。每一个原始值被认为是一片“叶子”。
307 | 2. MST 仅仅拥有三种节点类型:model、array 和 map。
308 | 3. MST 中的每一个 _节点_ 就是它本身。任何可以应用到完整树上的操作都可以应用到子树上。
309 | 4. 一个节点只能在一个树中存在一次,这样可确保它是唯一可辨识的。
310 | 5. 可以在相同的树中使用 reference 去引用另一个对象。
311 | 6. 对存在于一个应用中的 MST 树没有数量限制,但是一个节点仅仅只能存在于一个树中。
312 | 7. 树上的所有叶子都必须是可序列化的,否则,就不能被存储,例如:MST 中的函数。
313 | 8. The only free-form type in MST is frozen; with the requirement that frozen values are immutable and serializable so that the MST semantics can still be upheld.
314 | 9. At any point in the tree it is possible to assign a snapshot to the tree instead of a concrete instance of the expected type. In that case an instance of the correct type, based on the snapshot, will be automatically created for you.
315 | 10. Nodes in the MST tree will be reconciled (the exact same instance will be reused) when updating the tree by any means, based on their _identifier_ property. If there is no identifier property, instances won't be reconciled.
316 | 11. 如果树中的一个节点被另一个节点替代了,那么源节点就会死去变成不可用的状态。这可以确保你不会在应用程序中意外地操作了过期对象。
317 | 12. 如果你想基于树中的已有节点创建一个新的节点,你可以`detach`或者`clone`那个它。
318 |
319 | ### 树的构成
320 |
321 | In MST every node in the tree is a tree in itself.
322 | Trees can be composed by composing their types:
323 |
324 | ```javascript
325 | const TodoStore = types.model({
326 | todos: types.array(Todo)
327 | })
328 |
329 | const storeInstance = TodoStore.create({
330 | todos: [{
331 | title: "Get biscuit"
332 | }]
333 | })
334 | ```
335 |
336 | 传递给`create`方法的_快照_将会被递归地转换成 MST 节点。因此,你可以安全地这样调用:
337 |
338 | ```javascript
339 | storeInstance.todos[0].setTitle("Chocolate instead plz")
340 | ```
341 |
342 | 因为树中的任何节点就是它自己本身,所以 MST 中内置的任何方法都可以在任何节点上被调用,而不仅仅是在根节点上。这使得获取某一子树的 patch 流或者是仅仅在某一子树上应用一个中间件都是可行的。
343 |
344 | ### Actions
345 |
346 | 默认情况下,只有其中的一个 action 或者更高层级的 action 才能修改节点数据。可以通过传递给`action`的初始化函数中返回一个对象来定义 action。每次实例化时,初始化函数都会被执行,因此,`self`一直指向的都是当前实例。同时,可以在函数中创建用来储存数据的闭包,这种也被称为实例的 _volatile_ 状态;或者是创建只能被 action 调用的私有方法。
347 |
348 | ```javascript
349 | const Todo = types.model({
350 | title: types.string
351 | })
352 | .actions(self => {
353 | function setTitle(newTitle) {
354 | self.title = newTitle
355 | }
356 |
357 | return {
358 | setTitle
359 | }
360 | })
361 | ```
362 |
363 | 如果没有本地数据和私有方法需要调用的话,也可以简写成:
364 |
365 | ```javascript
366 | const Todo = types.model({
367 | title: types.string
368 | })
369 | .actions(self => ({ // 注意 `({`,这样返回了一个对象字面量
370 | setTitle(newTitle) {
371 | self.title = newTitle
372 | }
373 | }))
374 | ```
375 |
376 | Action 是可被复制的,因此有若干使用约束需要你知道:
377 |
378 | - 不使用 action 修改节点将会抛出异常
379 | - 建议 action 的参数都是可序列化的。有些参数是可以被自动序列化,例如,相对于其它节点的相对路径
380 | - 不要在 action 内部使用`this`,应该用`self`来代替它。这使得可以很安全的在没有绑定`this`上下文的函数以及箭头函数中去传递 action
381 |
382 | 一些有用的方法:
383 |
384 | - [`onAction`](API.md#onaction)列出在 model 中,以及它的后代节点中已经引用的任何 action
385 | - [`addMiddleware`](API.md#addmiddleware) listens to any action that is invoked on the model or any of its descendants.(此处的官方说明和[API](API.md#addmiddleware)的说明有出入)
386 | - [`applyAction`](API.md#applyaction)在 model 中,根据给定的 action 描述去引入一个 action
387 |
388 | #### 异步的 action
389 |
390 | 在 MST 中,异步 action 是作为第一级支持的,详情信息描述可查看[这里](docs/async-actions.md#asynchronous-actions-and-middleware)。
391 |
392 | 异步 action 是用 generator 实现的,并且总是返回一个 promise。可参考实例:[bookshop sources](https://github.com/mobxjs/mobx-state-tree/blob/adba1943af263898678fe148a80d3d2b9f8dbe63/examples/bookshop/src/stores/BookStore.js#L25)。下面是实例的部分主要代码:
393 |
394 | ```javascript
395 | import { types, flow } from "mobx-state-tree"
396 |
397 | someModel.actions(self => {
398 | const fetchProjects = flow(function* () { // <- note the star, this a generator function!
399 | self.state = "pending"
400 | try {
401 | // ... yield can be used in async/await style
402 | self.githubProjects = yield fetchGithubProjectsSomehow()
403 | self.state = "done"
404 | } catch (error) {
405 | // ... including try/catch error handling
406 | console.error("Failed to fetch projects", error)
407 | self.state = "error"
408 | }
409 | // The action will return a promise that resolves to the returned value
410 | // (or rejects with anything thrown from the action)
411 | return self.githubProjects.length
412 | })
413 |
414 | return { fetchProjects }
415 | })
416 | ```
417 |
418 | #### Action 监听器与中间件的对比
419 |
420 | Action 监听器与中间件的区别是:中间件可以主动拦截那些调用了它的 action,modify arguments, return types etc. Action 监听器不能主动拦截,它只能被动的接受通知。Action listeners receive the action arguments in a serializable format, while middleware receives the raw arguments. (`onAction` is actually just a built-in middleware)
421 |
422 | 对于创建中间件的更多内容可查看[中间件](docs/middleware.md)
423 |
424 | #### 禁用保护模式
425 |
426 | This may be desired if the default protection of `mobx-state-tree` doesn't fit your use case. For example, if you are not interested in replayable actions, or hate the effort of writing actions to modify any field; `unprotect(tree)` will disable the protected mode of a tree, allowing anyone to directly modify the tree.
427 |
428 | ### Views
429 |
430 | 任何从你的数据状态中的派生都可以被称之为“view”或者“derivation”(推导)。想了解更多背景信息可查看[Mobx 概念与原则](https://mobx.js.org/intro/concepts.html)
431 |
432 | View 有两种形式:有参数和无参数。后者一般被称为计算值,基于的是 MobX 的[计算](https://mobx.js.org/refguide/computed-decorator.html)概念。两者最主要的区别就是计算属性有一个明确的缓存点,但是它们更深层的工作方式是相同的 and any other computed value or Mobx based reaction like [`@observer`](https://mobx.js.org/refguide/observer-component.html) components can react to them. 计算值是基于 _getter_ 方法定义的。
433 |
434 | Example:
435 |
436 | ```javascript
437 | import { autorun } from "mobx"
438 |
439 | const UserStore = types
440 | .model({
441 | users: types.array(User)
442 | })
443 | .views(self => ({
444 | get amountOfChildren() {
445 | return self.users.filter(user => user.age < 18).length
446 | },
447 | amountOfPeopleOlderThan(age) {
448 | return self.users.filter(user => user.age > age).length
449 | }
450 | }))
451 |
452 | const userStore = UserStore.create(/* */)
453 |
454 | // Every time the userStore is updated in a relevant way, log messages will be printed
455 | autorun(() => {
456 | console.log("There are now ", userStore.amountOfChildren, " children")
457 | })
458 | autorun(() => {
459 | console.log("There are now ", userStore.amountOfPeopleOlderThan(75), " pretty old people")
460 | })
461 | ```
462 |
463 | 如果你想在 view 和 action 之间共享 volatile 数据,就得使用`.extend`代替`.views`和`.actions`了,可查看[volatile 数据](#volatile-state)。
464 |
465 | ### 快照
466 |
467 | 快照就是一个树中特定时间点的、固定的、序列化的纯对象。可以通过使用`getSnapshot(node)`来对它进行检查。
468 |
469 | 快照不包含任何类型信息并且从所有 action 中剥离了出来,因此,它非常适合用于传输。请求一个快照是非常廉价的,MST 在后台维护着每一个在结构上都是共享的节点快照。
470 |
471 | ```javascript
472 | coffeeTodo.setTitle("Tea instead plz")
473 |
474 | console.dir(getSnapshot(coffeeTodo))
475 | // prints `{ title: "Tea instead plz" }`
476 | ```
477 |
478 | 关于快照的一些有趣的特性:
479 |
480 | - 快照是不可变的
481 | - 快照可以用来传输
482 | - 快照可以用来更新 model 或者是恢复它们到某一状态
483 | - 快照可以在需要的时候自动转换成 model。因此下面的两种表述是等价的:`store.todos.push(Todo.create({ title: "test" }))`和`store.todos.push({ title: "test" })`。
484 |
485 | 一些有用的方法:
486 |
487 | - `getSnapshot(model)`:返回一个表示当前 model 状态的快照
488 | - `onSnapshot(model, callback)`:无论何时,当一个新快照可用时,就创建一个监听器(but only one per MobX transaction)
489 | - `applySnapshot(model, snapshot)`:使用快照更新 model 以及它所有后代的状态
490 |
491 | ## Patches
492 |
493 | 对一个 model 进行修改时,不仅会产生一个新的快照,而且会生一个描述修改的 [JSON-patches](http://jsonpatch.com/) 流。
494 |
495 | Patche 拥有以下特征:
496 |
497 | ```
498 | export interface IJsonPatch {
499 | op: "replace" | "add" | "remove"
500 | path: string
501 | value?: any
502 | }
503 | ```
504 | - Patches 是根据 JSON-Patch, RFC 6902 构造的
505 | - Patches are emitted immediately when a mutation is made, and don't respect transaction boundaries (like snapshots)
506 | - Patch 监听器通常用来完成深层次的观察
507 | - 一个 patch 的`path`属性包含着相对于事件监听器被加载的位置的路径
508 | - 一个单一的 mutation 可能会产生多个 patch,例如切分数组的时候
509 | - Patches can be reverse applied, which enables many powerful patterns like undo / redo
510 |
511 | 一些有用的方法:
512 | - `onPatch(model, listener)`给当前 model 添加一个 patch 监听器,当前 model 或者它的任何后代产生突变时都会被调用
513 | - `applyPatch(model, patch)`给当前 model 应用一个 patch(或者是一个 patch 数组)
514 | - `revertPatch(model, patch)`反向应用一个 patch(或者是一个 patch 数组)到当前提供的 model。这种给 model 反向回放一系列 patch 的方式,通常用来将 model 带回到它的初始的状态
515 |
516 | ### 引用和标识符
517 |
518 | 在 MST 中,引用和标识符是一个一级的概念。
519 |
520 | This makes it possible to declare references, and keep the data normalized in the background, while you interact with it in a denormalized manner.
521 |
522 | Example:
523 | ```javascript
524 | const Todo = types.model({
525 | id: types.identifier(),
526 | title: types.string
527 | })
528 |
529 | const TodoStore = types.model({
530 | todos: types.array(Todo),
531 | selectedTodo: types.reference(Todo)
532 | })
533 |
534 | // create a store with a normalized snapshot
535 | const storeInstance = TodoStore.create({
536 | todos: [{
537 | id: "47",
538 | title: "Get coffee"
539 | }],
540 | selectedTodo: "47"
541 | })
542 |
543 | // 因为 `selectedTodo` 被定义为一个标识符,所以它实际返回的是一个与标识符相匹配的 Todo 节点,也就是`id=47`那个节点。console.log(storeInstance.selectedTodo.title)
544 | // prints "Get coffee"
545 | ```
546 |
547 | #### 标识符
548 |
549 | - 每个 model 可定义零个或者一个`identifier()`属性
550 | - 一个对象的标识符属性不可以在后面的初始化中被修改
551 | - Each identifier / type combination should be unique within the entire tree
552 | - Identifiers are used to reconcile items inside arrays and maps - wherever possible - when applying snapshots
553 | - The `map.put()` method can be used to simplify adding objects that have identifiers to [maps](API.md#typesmap)
554 | - The primary goal of identifiers is not validation, but reconciliation and reference resolving. For this reason identifiers cannot be defined or updated after creation. If you want to check if some value just looks as an identifier, without providing the above semantics; use something like: `types.refinement(types.string, v => v.match(/someregex/))`
555 |
556 | _Tip: If you know the format of the identifiers in your application, leverage `types.refinement` to actively check this, for example the following definition enforces that identifiers of `Car` always start with the string `Car_`:_
557 |
558 | ```javascript
559 | const Car = types.model("Car", {
560 | id: types.identifier(types.refinement(types.string, identifier => identifier.indexOf("Car_") === 0))
561 | })
562 | ```
563 |
564 | #### 引用
565 |
566 | References are defined by mentioning the type they should resolve to. The targeted type should have exactly one attribute of the type `identifier()`.
567 | References are looked up through the entire tree, but per type. So identifiers need to be unique in the entire tree.
568 |
569 | #### 自定义的引用
570 |
571 | The default implementation uses the `identifier` cache to resolve references (See [`resolveIdentifier`](API.md#resolveIdentifier)).
572 | However, it is also possible to override the resolve logic, and provide your own custom resolve logic.
573 | This also makes it possible to, for example, trigger a data fetch when trying to resolve the reference ([example](https://github.com/mobxjs/mobx-state-tree/blob/cdb3298a5621c3229b3856bb469327da6deb31ea/packages/mobx-state-tree/test/reference-custom.ts#L150)).
574 |
575 | Example:
576 |
577 | ```javascript
578 | const User = types.model({
579 | id: types.identifier(),
580 | name: types.string
581 | })
582 |
583 | const UserByNameReference = types.maybe(
584 | types.reference(User, {
585 | // given an identifier, find the user
586 | get(identifier /* string */, parent: any /*Store*/) {
587 | return parent.users.find(u => u.name === identifier) || null
588 | },
589 | // given a user, produce the identifier that should be stored
590 | set(value /* User */) {
591 | return value.name
592 | }
593 | })
594 | )
595 |
596 | const Store = types.model({
597 | users: types.array(User),
598 | selection: UserByNameReference
599 | })
600 |
601 | const s = Store.create({
602 | users: [{ id: "1", name: "Michel" }, { id: "2", name: "Mattia" }],
603 | selection: "Mattia"
604 | })
605 | ```
606 |
607 | ### Listening to observables, snapshots, patches or actions
608 |
609 | MST is powered by MobX. This means that it is immediately compatible with `observer` components, or reactions like `autorun`:
610 |
611 | ```javascript
612 | import { autorun } from "mobx"
613 |
614 | autorun(() => {
615 | console.log(storeInstance.selectedTodo.title)
616 | })
617 | ```
618 |
619 | But, because MST keeps immutable snapshots in the background, it is also possible to be notified when a new snapshot of the tree is available. This is similar to `.subscribe` on a redux store:
620 |
621 | ```javascript
622 | onSnapshot(storeInstance, newSnapshot => {
623 | console.dir("Got new state: ", newSnapshot)
624 | })
625 | ```
626 |
627 | However, sometimes it is more useful to precisely know what has changed, rather than just receiving a complete new snapshot.
628 | For that, MST supports json-patches out of the box
629 |
630 | ```javascript
631 | onPatch(storeInstance, patch => {
632 | console.dir("Got change: ", patch)
633 | })
634 |
635 | storeInstance.todos[0].setTitle("Add milk")
636 | // prints:
637 | {
638 | path: "/todos/0",
639 | op: "replace",
640 | value: "Add milk"
641 | }
642 | ```
643 |
644 | Similarly, you can be notified whenever an action is invoked by using `onAction`
645 |
646 | ```javascript
647 | onAction(storeInstance, call => {
648 | console.dir("Action was called: ", call)
649 | })
650 |
651 | storeInstance.todos[0].setTitle("Add milk")
652 | // prints:
653 | {
654 | path: "/todos/0",
655 | name: "setTitle",
656 | args: ["Add milk"]
657 | }
658 | ```
659 |
660 | It is even possible to intercept actions before they are applied by adding middleware using `addMiddleware`:
661 |
662 | ```javascript
663 | addMiddleware(storeInstance, (call, next) => {
664 | call.args[0] = call.args[0].replace(/tea/gi, "Coffee")
665 | return next(call)
666 | })
667 | ```
668 |
669 | A more extensive middleware example can be found in this [code sandbox](https://codesandbox.io/s/mQrqy8j73).
670 | For more details on creating middleware and the exact specification of middleware events, see the [docs](docs/middleware.md)
671 |
672 | Finally, it is not only possible to be notified about snapshots, patches or actions; it is also possible to re-apply them by using `applySnapshot`, `applyPatch` or `applyAction`!
673 |
674 | ## Volatile state
675 |
676 | MST models primarily aid in storing _persistable_ state. State that can be persisted, serialized, transferred, patched, replaced etc.
677 | However, sometimes you need to keep track of temporary, non-persistable state. This is called _volatile_ state in MST. Examples include promises, sockets, DOM elements etc. - state which is needed for local purposes as long as the object is alive.
678 |
679 | Volatile state (which is also private) can be introduced by creating variables inside any of the action initializer functions.
680 |
681 | Volatile is preserved for the life-time of an object, and not reset when snapshots are applied etc. Note that the life time of an object depends on proper reconciliation, see the [how does reconciliation work?](#how-does-reconciliation-work) section below.
682 |
683 | The following is an example of an object with volatile state. Note that volatile state here is used to track a XHR request, and clean up resources when it is disposed. Without volatile state this kind of information would need to be stored in an external WeakMap or something similar.
684 |
685 | ```javascript
686 | const Store = types.model({
687 | todos: types.array(Todo),
688 | state: types.enumeration("State", ["loading", "loaded", "error"])
689 | })
690 | .actions(self => {
691 | const pendingRequest = null // a Promise
692 |
693 | function afterCreate() {
694 | self.state = "loading"
695 | pendingRequest = someXhrLib.createRequest("someEndpoint")
696 | }
697 |
698 | function beforeDestroy() {
699 | // abort the request, no longer interested
700 | pendingRequest.abort()
701 | }
702 |
703 | return {
704 | afterCreate,
705 | beforeDestroy
706 | }
707 | })
708 | ```
709 |
710 | Some tips:
711 |
712 | 1. Note that multiple `actions` calls can be chained. This makes it possible to create multiple closures with their own protected volatile state.
713 | 1. Although in the above example the `pendingRequest` could be initialized directly in the action initializer, it is recommended to do this in the `afterCreate` hook, which will only once the entire instance has been set up (there might be many action and property initializers for a single type).
714 |
715 | 1. The above example doesn't actually use the promise. For how to work with promises / asynchronous flows, see the [asynchronous actions](#asynchronous-actions) section above.
716 |
717 | 1. It is possible to share volatile state between views and actions by using `extend`. `.extend` works like a combination of `.actions` and `.views` and should return an object with a `actions` and `views` field:
718 |
719 | ```javascript
720 | const Todo = types.model({}).extend(self => {
721 | let localState = 3
722 |
723 | return {
724 | views: {
725 | get x() {
726 | return localState
727 | }
728 | },
729 | actions: {
730 | setX(value) {
731 | localState = value
732 | }
733 | }
734 | }
735 | })
736 | ```
737 |
738 | ### model.volatile
739 |
740 | In many cases it is useful to have volatile state that is _observable_ (in terms of Mobx observables) and _readable_ from outside the instance.
741 | In that case, in the above example, `localState` could have been declared as `const localState = observable.box(3)`.
742 | Since this is such a common pattern, there is a shorthand to declare such properties, and the example above could be rewritten to:
743 |
744 | ```javascript
745 | const Todo = types.model({})
746 | .volatile(self => ({
747 | localState: 3
748 | }))
749 | .actions(self => ({
750 | setX(value) {
751 | self.localState = value
752 | }
753 | }))
754 | ```
755 |
756 | 从`volatile`的初始化函数中可以返回任意数量的数据对象,并且它们具有相同名字的实例属性。Volatile 属性具有以下特性:
757 |
758 | 1. The can be read from outside the model (if you want hidden volatile state, keep the state in your closure as shown in the previous section)
759 | 2. The volatile properties will be only observable be [observable _references_](https://mobx.js.org/refguide/modifiers.html). Values assigned to them will be unmodified and not automatically converted to deep observable structures.
760 | 3. Like normal properties, they can only be modified through actions
761 | 5. Volatile props will not show up in snapshots, and cannot be updated by applying snapshots
762 | 5. Volatile props are preserved during the lifecycle of an instance. See also [reconciliation](#reconciliation)
763 | 4. Changes in volatile props won't show up in the patch or snapshot stream
764 | 4. It is currently not supported to define getters / setters in the object returned by `volatile`
765 |
766 | ## 依赖注入
767 |
768 | When creating a new state tree it is possible to pass in environment specific data by passing an object as the second argument to a `.create` call.
769 | This object should be (shallowly) immutable and can be accessed by any model in the tree by calling `getEnv(self)`.
770 |
771 | This is useful to inject environment, or test-specific utilities like a transport layer, loggers etc. This is also very useful to mock behavior in unit tests or provide instantiated utilities to models without requiring singleton modules.
772 | See also the [bookshop example](https://github.com/mobxjs/mobx-state-tree/blob/a4f25de0c88acf0e239acb85e690e91147a8f0f0/examples/bookshop/src/stores/ShopStore.test.js#L9) for inspiration.
773 |
774 | ```javascript
775 | import { types, getEnv } from "mobx-state-tree"
776 |
777 | const Todo = types.model({
778 | title: ""
779 | })
780 | .actions(self => ({
781 | setTitle(newTitle) {
782 | // grab injected logger and log
783 | getEnv(self).logger.log("Changed title to: " + newTitle)
784 | self.title = newTitle
785 | }
786 | }))
787 |
788 | const Store = types.model({
789 | todos: types.array(Todo)
790 | })
791 |
792 | // setup logger and inject it when the store is created
793 | const logger = {
794 | log(msg) {
795 | console.log(msg)
796 | }
797 | }
798 |
799 | const store = Store.create({
800 | todos: [{ title: "Grab tea" }]
801 | }, {
802 | logger: logger // inject logger to the tree
803 | }
804 | )
805 |
806 | store.todos[0].setTitle("Grab coffee")
807 | // prints: Changed title to: Grab coffee
808 | ```
809 |
810 | # Types overview
811 |
812 | These are the types available in MST. All types can be found in the `types` namespace, e.g. `types.string`. See [Api Docs](API.md) for examples.
813 |
814 | ## Complex types
815 |
816 | * `types.model(properties, actions)` Defines a "class like" type, with properties and actions to operate on the object.
817 | * `types.array(type)` Declares an array of the specified type.
818 | * `types.map(type)` Declares a map of the specified type.
819 |
820 | ## Primitive types
821 |
822 | * `types.string`
823 | * `types.number`
824 | * `types.boolean`
825 | * `types.Date`
826 |
827 | ## Utility types
828 |
829 | * `types.union(dispatcher?, types...)` create a union of multiple types. If the correct type cannot be inferred unambiguously from a snapshot, provide a dispatcher function of the form `(snapshot) => Type`.
830 | * `types.optional(type, defaultValue)` marks an value as being optional (in e.g. a model). If a value is not provided the `defaultValue` will be used instead. If `defaultValue` is a function, it will be evaluated. This can be used to generate, for example, IDs or timestamps upon creation.
831 | * `types.literal(value)` can be used to create a literal type, where the only possible value is specifically that value. This is very powerful in combination with `union`s. E.g. `temperature: types.union(types.literal("hot"), types.literal("cold"))`.
832 | * `types.enumeration(name?, options: string[])` creates an enumeration. This method is a shorthand for a union of string literals.
833 | * `types.refinement(name?, baseType, (snapshot) => boolean)` creates a type that is more specific than the base type, e.g. `types.refinement(types.string, value => value.length > 5)` to create a type of strings that can only be longer then 5.
834 | * `types.maybe(type)` makes a type optional and nullable, shorthand for `types.optional(types.union(type, types.literal(null)), null)`.
835 | * `types.null` the type of `null`
836 | * `types.undefined` the type of `undefined`
837 | * `types.late(() => type)` can be used to create recursive or circular types, or types that are spread over files in such a way that circular dependencies between files would be an issue otherwise.
838 | * `types.frozen` Accepts any kind of serializable value (both primitive and complex), but assumes that the value itself is **immutable** and **serializable**.
839 | * `types.compose(name?, type1...typeX)`, creates a new model type by taking a bunch of existing types and combining them into a new one
840 |
841 | ## Property types
842 |
843 | Property types can only be used as a direct member of a `types.model` type and not further composed (for now).
844 | * `types.identifier(subType?)` Only one such member can exist in a `types.model` and should uniquely identify the object. See [identifiers](#identifiers) for more details. `subType` should be either `types.string` or `types.number`, defaulting to the first if not specified.
845 | * `types.reference(targetType)` creates a property that is a reference to another item of the given `targetType` somewhere in the same tree. See [references](#references) for more details.
846 |
847 | ## LifeCycle hooks for `types.model`
848 |
849 | All of the below hooks can be created by returning an action with the given name, like:
850 |
851 | ```javascript
852 | const Todo = types
853 | .model("Todo", { done: true })
854 | .actions(self => ({
855 | afterCreate() {
856 | console.log("Created a new todo!")
857 | }
858 | }))
859 | ```
860 |
861 | The exception to this rule is the `preProcessSnapshot` hook. Because it is needed before instantiating model elements, it needs to be defined on the type itself:
862 |
863 | ```javascript
864 | types
865 | .model("Todo", { done: true })
866 | .preProcessSnapshot(snapshot => ({
867 | // auto convert strings to booleans as part of preprocessing
868 | done: snapshot.done === "true" ? true : snapshot.done === "false" ? false : snapshot.done
869 | }))
870 | .actions(self => ({
871 | afterCreate() {
872 | console.log("Created a new todo!")
873 | }
874 | }))
875 | ```
876 |
877 |
878 | | Hook | Meaning |
879 | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
880 | | `preProcessSnapshot` | Before creating an instance or applying a snapshot to an existing instance, this hook is called to give the option to transform the snapshot before it is applied. The hook should be a _pure_ function that returns a new snapshot. This can be useful to do some data conversion, enrichment, property renames etc. This hook is not called for individual property updates. _**Note 1: Unlike the other hooks, this one is _not_ created as part of the `actions` initializer, but directly on the type!**_ _**Note 2: The `preProcessSnapshot` transformation must be pure; it should not modify its original input argument!**_ |
881 | | `afterCreate` | Immediately after an instance is created and initial values are applied. Children will fire this event before parents |
882 | | `afterAttach` | As soon as the _direct_ parent is assigned (this node is attached to another node). If an element is created as part of a parent, `afterAttach` is also fired. Unlike `afterCreate`, `afterAttach` will fire breadth first. So, in `afterAttach` one can safely make assumptions about the parent, but in `afterCreate` not |
883 | | `postProcessSnapshot` | This hook is called every time a new snapshot is being generated. Typically it is the inverse function of `preProcessSnapshot`. This function should be a pure function that returns a new snapshot.
884 | | `beforeDetach` | As soon as the node is removed from the _direct_ parent, but only if the node is _not_ destroyed. In other words, when `detach(node)` is used |
885 | | `beforeDestroy` | Called before the node is destroyed, as a result of calling `destroy`, or by removing or replacing the node from the tree. Child destructors will fire before parents |
886 |
887 | Note, except for `preProcessSnapshot`, all hooks should be defined as actions.
888 |
889 | All hooks can be defined multiple times and can be composed automatically.
890 |
891 |
892 |
893 | # Api overview
894 |
895 | See the [full API docs](API.md) for more details.
896 |
897 | | signature | |
898 | | ---- | --- |
899 | | [`addDisposer(node, () => void)`](API.md#adddisposer) | Function to be invoked whenever the target node is to be destroyed |
900 | | [`addMiddleware(node, middleware: (actionDescription, next) => any)`](API.md#addmiddleware) | Attaches middleware to a node. See [middleware](docs/middleware.md). Returns disposer. |
901 | | [`applyAction(node, actionDescription)`](API.md#applyaction) | Replays an action on the targeted node |
902 | | [`applyPatch(node, jsonPatch)`](API.md#applypatch) | Applies a JSON patch, or array of patches, to a node in the tree |
903 | | [`applySnapshot(node, snapshot)`](API.md#applysnapshot) | Updates a node with the given snapshot |
904 | | [`createActionTrackingMiddleware`](API.md#createactiontrackingmiddleware) | Utility to make writing middleware that tracks async actions less cumbersome |
905 | | [`clone(node, keepEnvironment?: true \| false \| newEnvironment)`](API.md#clone) | Creates a full clone of the given node. By default preserves the same environment |
906 | | [`decorate(middleware, function)`](API.md#decorate) | Attaches middleware to a specific action (or flow) |
907 | | [`destroy(node)`](API.md#destroy) | Kills `node`, making it unusable. Removes it from any parent in the process |
908 | | [`detach(node)`](API.md#detach) | Removes `node` from its current parent, and lets it live on as standalone tree |
909 | | [`flow(generator)`](API.md#flow) | creates an asynchronous flow based on a generator function |
910 | | [`getChildType(node, property?)`](API.md#getchildtype) | Returns the declared type of the given `property` of `node`. For arrays and maps `property` can be omitted as they all have the same type |
911 | | [`getEnv(node)`](API.md#getenv) | Returns the environment of `node`, see [environments](#environments) |
912 | | [`getParent(node, depth=1)`](API.md#getparent) | Returns the intermediate parent of the `node`, or a higher one if `depth > 1` |
913 | | [`getPath(node)`](API.md#getpath) | Returns the path of `node` in the tree |
914 | | [`getPathParts(node)`](API.md#getpathparts) | Returns the path of `node` in the tree, unescaped as separate parts |
915 | | [`getRelativePath(base, target)`](API.md#getrelativepath) | Returns the short path, which one could use to walk from node `base` to node `target`, assuming they are in the same tree. Up is represented as `../` |
916 | | [`getRoot(node)`](API.md#getroot) | Returns the root element of the tree containing `node` |
917 | | [`getSnapshot(node)`](API.md#getsnapshot) | Returns the snapshot of the `node`. See [snapshots](#snapshots) |
918 | | [`getType(node)`](API.md#gettype) | Returns the type of `node` |
919 | | [`hasParent(node, depth=1)`](API.md#hasparent) | Returns `true` if `node` has a parent at `depth` |
920 | | [`isAlive(node)`](API.md#isalive) | Returns `true` if `node` is alive |
921 | | [`isStateTreeNode(value)`](API.md#isstatetreenode) | Returns `true` if `value` is a node of a mobx-state-tree |
922 | | [`isProtected(value)`](API.md#isprotected) | Returns `true` if the given node is protected, see [actions](#actions) |
923 | | [`isRoot(node)`](API.md#isroot) | Returns true if `node` has no parents |
924 | | [`joinJsonPath(parts)`](API.md#joinjsonpath) | Joins and escapes the given path `parts` into a JSON path |
925 | | [`onAction(node, (actionDescription) => void)`](API.md#onaction) | A built-in middleware that calls the provided callback with an action description upon each invocation. Returns disposer |
926 | | [`onPatch(node, (patch) => void)`](API.md#onpatch) | Attach a JSONPatch listener, that is invoked for each change in the tree. Returns disposer |
927 | | [`onSnapshot(node, (snapshot, inverseSnapshot) => void)`](API.md#onsnapshot) | Attach a snapshot listener, that is invoked for each change in the tree. Returns disposer |
928 | | [`process(generator)`](API.md#process) | `DEPRECATED` – replaced with [flow](API.md#flow) |
929 | | [`protect(node)`](API.md#protect) | Protects an unprotected tree against modifications from outside actions |
930 | | [`recordActions(node)`](API.md#recordactions) | Creates a recorder that listens to all actions in `node`. Call `.stop()` on the recorder to stop this, and `.replay(target)` to replay the recorded actions on another tree |
931 | | [`recordPatches(node)`](API.md#recordpatches) | Creates a recorder that listens to all patches emitted by the node. Call `.stop()` on the recorder to stop this, and `.replay(target)` to replay the recorded patches on another tree |
932 | | [`resolve(node, path)`](API.md#resolve) | Resolves a `path` (json path) relatively to the given `node` |
933 | | [`splitJsonPath(path)`](API.md#splitjsonpath) | Splits and unescapes the given JSON `path` into path parts |
934 | | [`typecheck(type, value)`](API.md#typecheck) | Typechecks a value against a type. Throws on errors. Use this if you need typechecks even in a production build. |
935 | | [`tryResolve(node, path)`](API.md#tryresolve) | Like `resolve`, but just returns `null` if resolving fails at any point in the path |
936 | | [`unprotect(node)`](API.md#unprotect) | Unprotects `node`, making it possible to directly modify any value in the subtree, without actions |
937 | | [`walk(startNode, (node) => void)`](API.md#walk) | Performs a depth-first walk through a tree |
938 | | [`escapeJsonPath(path)`](API.md#escapejsonpath) | escape special characters in an identifier, according to http://tools.ietf.org/html/rfc6901 |
939 | | [`unescapeJsonPath(path)`](API.md#unescapejsonpath) | escape special characters in an identifier, according to http://tools.ietf.org/html/rfc6901 |
940 | | [`resolveIdentifier(type, target, identifier)`](API.md#resolveidentifier) | resolves an identifier of a given type in a model tree |
941 | | [`resolvePath(target, path)`](API.md#resolvepath) | resolves a JSON path, starting at the specified target |
942 |
943 | A _disposer_ is a function that cancels the effect it was created for.
944 |
945 | # Tips
946 |
947 | ### Building with production environment
948 |
949 | MobX-state-tree provides a lot of dev-only checks. They check the correctness of function calls and perform runtime type-checks over your models. It is recommended to disable them in production builds. To do so, you should use webpack's DefinePlugin to set environment as production and remove them. More information could be found in the [official webpack guides](https://webpack.js.org/plugins/environment-plugin/#usage).
950 |
951 | ### Generate MST models from JSON
952 |
953 | The following service can generate MST models based on JSON: https://transform.now.sh/json-to-mobx-state-tree
954 |
955 | ### `optionals` and default value functions
956 |
957 | `types.optional` can take an optional function parameter which will be invoked each time a default value is needed. This is useful to generate timestamps, identifiers or even complex objects, for example:
958 |
959 | `createdDate: types.optional(types.Date, () => new Date())`
960 |
961 | ### `toJSON()` for debugging
962 |
963 | For debugging you might want to use `getSnapshot(model)` to print the state of a model. But if you didn't import `getSnapshot` while debugging in some debugger; don't worry, `model.toJSON()` will produce the same snapshot. (For API consistency, this feature is not part of the typed API)
964 |
965 | ### Handle circular dependencies between files using `late`
966 |
967 | On the exporting file:
968 |
969 | ```javascript
970 | export function LateStore() {
971 | return types.model({
972 | title: types.string
973 | })
974 | }
975 | ```
976 |
977 | In the importing file
978 | ```javascript
979 | import { LateStore } from "./circular-dep"
980 |
981 | const Store = types.late(LateStore)
982 | ```
983 |
984 | Thanks to function hoisting in combination with `types.late`, this lets you have circular dependencies between types, across files.
985 |
986 | ### Simulate inheritance by using type composition
987 |
988 | There is no notion of inheritance in MST. The recommended approach is to keep references to the original configuration of a model in order to compose it into a new one, for example by using `types.compose` (which combines two types) or producing fresh types using `.props|.views|.actions`. An example of classical inheritance could be expressed using composition as follows:
989 |
990 | ```javascript
991 | const Square = types
992 | .model(
993 | "Square",
994 | {
995 | width: types.number
996 | }
997 | )
998 | .views(self => ({
999 | surface() {
1000 | return self.width * self.width
1001 | }
1002 | }))
1003 |
1004 | // create a new type, based on Square
1005 | const Box = Square
1006 | .named("Box")
1007 | .views(self => {
1008 | // save the base implementation of surface
1009 | const superSurface = self.surface
1010 |
1011 | return {
1012 | // super contrived override example!
1013 | surface() {
1014 | return superSurface() * 1
1015 | },
1016 | volume() {
1017 | return self.surface * self.width
1018 | }
1019 | }
1020 | }))
1021 |
1022 | // no inheritance, but, union types and code reuse
1023 | const Shape = types.union(Box, Square)
1024 | ```
1025 |
1026 | Similarly, compose can be used to simply mixin types:
1027 |
1028 | ```javascript
1029 | const CreationLogger = types.model().actions(self => ({
1030 | afterCreate() {
1031 | console.log("Instantiated " + getType(self).name)
1032 | }
1033 | }))
1034 |
1035 | const BaseSquare = types
1036 | .model({
1037 | width: types.number
1038 | })
1039 | .views(self => ({
1040 | surface() {
1041 | return self.width * self.width
1042 | }
1043 | }))
1044 |
1045 | export const LoggingSquare = types
1046 | .compose(
1047 | // combine a simple square model...
1048 | BaseSquare,
1049 | // ... with the logger type
1050 | CreationLogger
1051 | )
1052 | // ..and give it a nice name
1053 | .named("LoggingSquare")
1054 | ```
1055 |
1056 | # FAQ
1057 |
1058 | ### When not to use MST?
1059 |
1060 | MST makes state management very tangible by offering access to snapshots, patches and by providing interceptable actions.
1061 | Also it fixes the `this` problem.
1062 | All these features have the downside that they incur a little runtime overhead.
1063 | Although in many places the MST core can still be optimized significantly, there will always be a constant overhead.
1064 | If you have a performance critical application that handles huge amounts of mutable data, you will probably be better
1065 | off by using 'raw' mobx.
1066 | Which has predictable and well-known performance characteristics, and has much less overhead.
1067 |
1068 | Likewise, if your application is mainly dealing with stateless information (such as a logging system) MST doesn't add much values.
1069 |
1070 | ### How does reconciliation work?
1071 |
1072 | * When applying snapshots, MST will always try to reuse existing object instances for snapshots with the same identifier (see `types.identifier()`).
1073 | * If no identifier is specified, but the type of the snapshot is correct, MST will reconcile objects as well if they are stored in a specific model property or under the same map key.
1074 | * In arrays, items without an identifier are never reconciled.
1075 |
1076 | If an object is reconciled, the consequence is that localState is preserved and `postCreate` / `attach` life-cycle hooks are not fired because applying a snapshot results just in an existing tree node being updated.
1077 |
1078 | ### Creating async flows
1079 |
1080 | See [creating asynchronous flow](docs/async-actions.md).
1081 |
1082 | ### Using mobx and mobx-state-tree together
1083 |
1084 | Yep, perfectly fine. No problem. Go on. `observer`, `autorun` etc. will work as expected.
1085 |
1086 | ### Should all state of my app be stored in `mobx-state-tree`?
1087 | No, or, not necessarily. An application can use both state trees and vanilla MobX observables at the same time.
1088 | State trees are primarily designed to store your domain data, as this kind of state is often distributed and not very local.
1089 | For local component state, for example, vanilla MobX observables might often be simpler to use.
1090 |
1091 | ### Can I use Hot Module Reloading?
1092 |
1093 | Yes, with MST it is pretty straight forward to setup hot reloading for your store definitions, while preserving state. See the [todomvc example](https://github.com/mobxjs/mobx-state-tree/blob/745904101fdaeb51f16f40ebb80cd7fecf742572/packages/mst-example-todomvc/src/index.js#L60-L64)
1094 |
1095 | ### TypeScript & MST
1096 |
1097 | TypeScript support is best-effort, as not all patterns can be expressed in TypeScript. But except for assigning snapshots to properties we get pretty close! As MST uses the latest fancy Typescript features it is recommended to use TypeScript 2.3 or higher, with `noImplicitThis` and `strictNullChecks` enabled.
1098 |
1099 | When using models, you write an interface, along with its property types, that will be used to perform type checks at runtime.
1100 | What about compile time? You can use TypeScript interfaces to perform those checks, but that would require writing again all the properties and their actions!
1101 |
1102 | Good news! You don't need to write it twice! Using the `typeof` operator of TypeScript over the `.Type` property of a MST Type will result in a valid TypeScript Type!
1103 |
1104 | ```typescript
1105 | const Todo = types.model({
1106 | title: types.string
1107 | })
1108 | .actions(self => ({
1109 | setTitle(v: string) {
1110 | self.title = v
1111 | }
1112 | }))
1113 |
1114 | type ITodo = typeof Todo.Type // => ITodo is now a valid TypeScript type with { title: string; setTitle: (v: string) => void }
1115 | ```
1116 |
1117 | Due to the way typeof operator works, when working with big and deep models trees, it might make your IDE/ts server takes a lot of CPU time and freeze vscode (or others)
1118 | A partial solution for this is to turn the `.Type` into an interface.
1119 | ```ts
1120 | type ITodoType = typeof Todo.Type;
1121 | interface ITodo extends ITodoType {};
1122 | ```
1123 |
1124 | Sometimes you'll need to take into account where your typings are available and where they aren't. The code below will not compile: TypeScript will complain that `self.upperProp` is not a known property. Computed properties are only available after `.views` is evaluated.
1125 |
1126 | ```typescript
1127 | const Example = types
1128 | .model('Example', {
1129 | prop: types.string,
1130 | })
1131 | .views(self => ({
1132 | get upperProp(): string {
1133 | return self.prop.toUpperCase();
1134 | },
1135 | get twiceUpperProp(): string {
1136 | return self.upperProp + self.upperProp;
1137 | },
1138 | }));
1139 | ```
1140 |
1141 | You can circumvent this situation by declaring the views in two steps:
1142 |
1143 | ```typescript
1144 | const Example = types
1145 | .model('Example', { prop: types.string })
1146 | .views(self => ({
1147 | get upperProp(): string {
1148 | return self.prop.toUpperCase();
1149 | },
1150 | }))
1151 | .views(self => ({
1152 | get twiceUpperProp(): string {
1153 | return self.upperProp + self.upperProp;
1154 | },
1155 | }));
1156 | ```
1157 |
1158 | Another approach would be to use helper functions, as demonstrated in the following code. This definition allows for circular references, but is more verbose.
1159 |
1160 | ```typescript
1161 | const Example = types
1162 | .model('Example', { prop: types.string })
1163 | .views(self => {
1164 | function upperProp(): string {
1165 | return self.prop.toUpperCase();
1166 | }
1167 | function twiceUpperProp(): string {
1168 | return upperProp() + upperProp();
1169 | }
1170 |
1171 | return {
1172 | get upperProp(): string {
1173 | return upperProp();
1174 | },
1175 | get twiceUpperProp(): string {
1176 | return twiceUpperProp();
1177 | },
1178 | };
1179 | });
1180 | ```
1181 |
1182 | #### Known Typescript Issue 5938
1183 |
1184 | Theres a known issue with typescript and interfaces as described by: https://github.com/Microsoft/TypeScript/issues/5938
1185 |
1186 | This rears its ugly head if you try to define a model such as:
1187 |
1188 | ```typescript
1189 | import { types } from "mobx-state-tree"
1190 |
1191 | export const Todo = types.model({
1192 | title: types.string
1193 | });
1194 |
1195 | export type ITodo = typeof Todo.Type
1196 | ```
1197 |
1198 | And you have your tsconfig.json settings set to:
1199 |
1200 | ```json
1201 | {
1202 | "compilerOptions": {
1203 | ...
1204 | "declaration": true,
1205 | "noUnusedLocals": true
1206 | ...
1207 | }
1208 | }
1209 | ```
1210 |
1211 | Then you will get errors such as:
1212 |
1213 | > error TS4023: Exported variable 'Todo' has or is using name 'IModelType' from external module "..." but cannot be named.
1214 |
1215 | Until Microsoft fixes this issue the solution is to re-export IModelType:
1216 |
1217 | ```typescript
1218 | import { types, IModelType } from "mobx-state-tree"
1219 |
1220 | export type __IModelType = IModelType;
1221 |
1222 | export const Todo = types.model({
1223 | title: types.string
1224 | });
1225 |
1226 | export type ITodo = typeof Todo.Type
1227 | ```
1228 |
1229 | It ain't pretty, but it works.
1230 |
1231 | ### How does MST compare to Redux
1232 |
1233 | So far this might look a lot like an immutable state tree as found for example in Redux apps, but there are a few differences:
1234 |
1235 | - Like Redux, and unlike MobX, MST prescribes a very specific state architecture.
1236 | - mobx-state-tree allows direct modification of any value in the tree; it is not necessary to construct a new tree in your actions.
1237 | - mobx-state-tree allows for fine-grained and efficient observation of any point in the state tree.
1238 | - mobx-state-tree generates JSON patches for any modification that is made.
1239 | - mobx-state-tree provides utilities to turn any MST tree into a valid Redux store.
1240 | - Having multiple MSTs in a single application is perfectly fine.
1241 |
1242 | ## Contributing
1243 |
1244 | 1. Clone this repository
1245 | 2. Run `yarn run bootstrap` and `yarn run build` once.
1246 | 3. Extensive pull requests are best discussed in an issue first
1247 | 3. Have fun!
1248 |
1249 |
1250 | ## Thanks!
1251 |
1252 | * [Mendix](https://mendix.com) for sponsoring and providing the opportunity to work on exploratory projects like MST.
1253 | * [Dan Abramov](https://twitter.com/dan_abramov)'s work on [Redux](http://redux.js.org) has strongly influenced the idea of snapshots and transactional actions in MST.
1254 | * [Giulio Canti](https://twitter.com/GiulioCanti)'s work on [tcomb](http://github.com/gcanti/tcomb) and type systems in general has strongly influenced the type system of MST.
1255 | * All the early adopters encouraging to pursue this whole idea and proving it is something feasible.
1256 |
--------------------------------------------------------------------------------
/docs/API_header.md:
--------------------------------------------------------------------------------
1 | # Mobx-State-Tree API reference guide
2 |
3 | _This reference guide lists all methods exposed by MST. Contributions like linguistic improvements, adding more details to the descriptions or additional examples are highly appreciated! Please note that the docs are generated from source. Most methods are declared in the [mst-operations.ts](https://github.com/mobxjs/mobx-state-tree/blob/master/src/core/mst-operations.ts) file._
4 |
--------------------------------------------------------------------------------
/docs/getting-started.md:
--------------------------------------------------------------------------------
1 | # 入门指南
2 |
3 | 本教程将通过构建一个能够给用户分配待办事项的 TODO 应用来为你介绍 mobx-state-tree (MST) 的基础知识。
4 |
5 | ## 预备知识
6 | 开始此教程之前,我假设你已经对 React 有了基础的了解。否则,建议首先去阅读一下这份 [React 教程](https://facebook.github.io/react/tutorial/tutorial.html)。
7 |
8 | ### 我需要提前学习 MobX 吗?
9 | MST 深度依赖于 MobX。因此,如果你使用过 MobX 的话,将对你处理一些复杂的情况和怎样把数据与 React 组件连接起来非常有帮助。但是,如果你没有使用过 MobX 也没关系,因为使用 MST 不需要任何 MobX API 方面的知识。
10 |
11 | ## 如何跟随这门教程
12 | 你可以使用 [codesandbox](https://codesandbox.io/) 在浏览器中直接写代码,也可以使用你偏爱的某种编辑器(例如:VSCode)都是可以的。
13 |
14 | ### 在浏览器中写代码
15 | 我们为每一个实例都提供了 codesandbox 链接。你可以按照知识点一步接一步的去学习教程,如果在什么地方卡住了,可以直接参考下一个实例链接哈 :)。
16 |
17 | ### 在编辑器中写代码
18 | 配置 React 项目的开发环境是一件让人觉得很繁琐的事情,你可能需要引入各种编译器、打包器等等。幸好有`create-react-app`这样的脚手架使这种事情简单到只需要在终端中输入几条命令即可。
19 |
20 | ```
21 | npm install -g create-react-app
22 | create-react-app mst-todo
23 | ```
24 | 你需要安装一下 mobx、mobx-react 和 mobx-state-tree。
25 | ```
26 | npm install mobx mobx-react mobx-state-tree --save
27 | ```
28 | 然后执行`npm run start`之后,一个基础的 React 页面就会展示在你面前。
29 |
30 | ## 概述
31 | mobx-state-tree is a state container that combines the simplicity and ease of mutable data with the traceability of immutable data and the reactiveness and performance of observable data.
32 |
33 | 如果你对这个说明很困惑,不用担心。让我们一起一步步地来探索它。
34 |
35 | ## 入门指南
36 | When building applications with MST, the first exercise that will help you building your application is thinking which is the minimal set of entities and their relative attributes of our application.
37 |
38 | 在我们的示例应用中处理的是 todo 代办事项,因此我们需要一个 todo 实体,它包含 name 和 todo 两个属性,并且 todo 属性有两种状态:完成与未完成。我们还需要一个 user 实体,用来分配代办事项。
39 |
40 | ## 创建第一个 model
41 | MST 的最主要概念就是一个动态树。这个树结构由可变的,但是受严格的运行时类型信息保护的对象组成。换句话说,每个树都是由模型(类型信息)和状态(数据)组成的。从这个动态树中可以自动生成不可变的、结构上共享的快照。
42 |
43 | 这意味着如果想让应用运行起来,我们需要向 MST 描述清楚我们的实体模型。知道了这些信息,MST 就可以自动为我们生成所有的边界,以避免犯一些愚蠢的错误。比如,把字符串赋值给了价格字段或者把布尔值赋值给了本来期望是数组的字段。
44 |
45 | 使用 MST 定义 model 实体的一个最简单方式就是提供一个将来会被用作默认值的数据给 types.model 方法。
46 |
47 | ```javascript
48 | import { types } from "mobx-state-tree"
49 |
50 | const Todo = types.model({
51 | name: "",
52 | done: false
53 | })
54 |
55 | const User = types.model({
56 | name: ""
57 | })
58 | ```
59 | [View sample in playground](https://codesandbox.io/s/98x2n959ky)
60 |
61 | 上面的代码会创建两种类型数据,分别是 User 和 Todo 类型。但我们前面说过,一个 model tree 是由类型信息和状态组成的,那我们怎样定义一个 Todo 和 User 类型的实例呢?
62 |
63 | ## 创建 model 实例 (tree nodes)
64 | 可以很容易的在 User 和 Todo 类型上调用`.create()`方法来完成这件事情。
65 |
66 | ```javascript
67 | import { types } from "mobx-state-tree"
68 |
69 | const Todo = types.model({
70 | name: "",
71 | done: false
72 | })
73 |
74 | const User = types.model({
75 | name: ""
76 | })
77 |
78 | const john = User.create()
79 | const eat = Todo.create()
80 |
81 | console.log("John:", john.toJSON())
82 | console.log("Eat TODO:", eat.toJSON())
83 |
84 | ```
85 | [View sample in playground](https://codesandbox.io/s/6jo1o9n9qk)
86 |
87 | 正如你所见,使用 models 可以确保所有被定义字段都是预先定义过的默认值。但如果你想在创建 model 实例时改变它的值,可以简单的给 create 方法传递一个对象即可。
88 |
89 | ```javascript
90 | const eat = Todo.create({ name: "eat" })
91 |
92 | console.log("Eat TODO:", eat.toJSON()) // => will print {name: "eat", done: false}
93 | ```
94 | [View sample in playground](https://codesandbox.io/s/ymqpj71oj9)
95 |
96 | ## 遇见 types
97 | 当你运行下面代码时,发现它会抛出错误异常:
98 |
99 | ```javascript
100 | const eat = Todo.create({ name: "eat", done: 1 })
101 | ```
102 | ```
103 | Error: [mobx-state-tree] Error while converting `{"name":"eat","done":1}` to `AnonymousModel`:
104 | at path "/done" value `1` is not assignable to type: `boolean`.
105 | ```
106 |
107 | 这是因为 MST 的节点都是强类型的,你不能给一个布尔类型提供一个数值类型的值。这种特性对构建应用非常有好处,可以保证你的状态类型始终如一,不会有非法的状态类型插入进来。下面是定义 model 的一种快捷方式。
108 |
109 | ```javascript
110 | const Todo = types.model({
111 | name: types.optional(types.string, ""),
112 | done: types.optional(types.boolean, false)
113 | })
114 |
115 | const User = types.model({
116 | name: types.optional(types.string, "")
117 | })
118 | ```
119 | [View sample in playground](https://codesandbox.io/s/j27j41828v)
120 |
121 | MST 中的命名空间类型还内置了很多实用的类型,例如:array、map、maybe、refinements 和 unions。如果你对他们感兴趣,可以去查阅 api 文档。我们现在将 types 和定义的一个 RootStore 结合起来用于约束 users 和 todos。
122 |
123 | 注意:如果你没有给`type`的`create`方法传递值,那么`types.optional`的第二个参数值就不能省略。
124 |
125 | ```js
126 | const User = types.model({
127 | name: types.optional(types.string, "")
128 | })
129 | ```
130 |
131 | 比如在上面这段代码中,我们给`name`属性定义的是`string`类型值,假如你省略`optional`函数的第二个参数之后。`name`的默认值就变成了 undefined ,并不是`string`类型,代码也就自然会抛出异常了。
132 |
133 | 例如,你想在调用`create`时让 Todo 的 name 属性值成为必填项,可以去掉`optional`方法并且把`types.string`传进去就可以了。
134 |
135 | ```javascript
136 | import { types } from "mobx-state-tree"
137 |
138 | const Todo = types.model({
139 | name: types.optional(types.string, ""),
140 | done: types.optional(types.boolean, false)
141 | })
142 |
143 | const User = types.model({
144 | name: types.optional(types.string, "")
145 | })
146 |
147 | const RootStore = types.model({
148 | users: types.map(User),
149 | todos: types.optional(types.map(Todo), {})
150 | })
151 |
152 | const store = RootStore.create({
153 | users: { } // users is required here because it's not marked as optional
154 | })
155 | ```
156 | [View sample in playground](https://codesandbox.io/s/5wyx1xvvj4)
157 |
158 | ## 修改数据
159 | MST 的树节点(也就是 model 实例)可以使用 action 来修改它。可以很容易的通过在`types`上调用`action`方法,给它传递一个回调函数,回调函数的参数为 model 实例,然后在回调函数内部将修改之后的 model 实例返回就可以了。
160 |
161 | 如下示例中,在 Todo 类型上定义了`action`方法,你就可以在它里面通过提供的 Todo 实例来切换`done`的状态和设置`name`属性值。
162 |
163 | ```javascript
164 | const Todo = types.model({
165 | name: types.optional(types.string, ""),
166 | done: types.optional(types.boolean, false)
167 | }).actions(self => {
168 | function setName(newName) {
169 | self.name = newName
170 | }
171 |
172 | function toggle() {
173 | self.done = !self.done
174 | }
175 |
176 | return {setName, toggle}
177 | })
178 |
179 | const User = types.model({
180 | name: types.optional(types.string, "")
181 | })
182 |
183 | const RootStore = types.model({
184 | users: types.map(User),
185 | todos: types.optional(types.map(Todo), {})
186 | }).actions(self => {
187 | function addTodo(id, name) {
188 | self.todos.set(id, Todo.create({ name }))
189 | }
190 |
191 | return {addTodo}
192 | })
193 | ```
194 | [View sample in playground](https://codesandbox.io/s/928l6pw7pr)
195 |
196 | 这里你需要知道,`self`的对象结构就是由你创建的 model 实例构成的。并且`action`方法已经被正确地绑定了`this`作用域,所以`self`指向的就是上面定义的 model 实例。
197 |
198 | 通过调用这些`action`方法就可以如此简单的实现 js 类,然后通过一个 model 实例来调用它们的方法了。
199 |
200 | ```javascript
201 | store.addTodo(1, "Eat a cake")
202 | store.todos.get(1).toggle()
203 | ```
204 | [View sample in playground](https://codesandbox.io/s/928l6pw7pr)
205 |
206 | ## 强大的快照!
207 | Dealing with mutable data and objects makes it easy to change data on the fly, but on the other hand it makes testing hard. Immutable data makes that very easy. Is there a way to have the best of both worlds? Nature is a great example of that. Beings are living and mutable, but we may eternalize nature's beauty by taking awesome snapshots. Can we do the same with the state of our application?
208 |
209 | Thanks to MST's knowledge of models and relative property types, MST is able to generate serializable snapshots of our store! You can easily get a snapshot of the store by using the `getSnapshot` function exported by the MST package.
210 |
211 | ```javascript
212 | console.log(getSnapshot(store))
213 | /*
214 | {
215 | "users": {},
216 | "todos": {
217 | "1": {
218 | "name": "Eat a cake",
219 | "done": true
220 | }
221 | }
222 | }
223 | */
224 | ```
225 |
226 | 注意:你在以前教程中用过的`toJSON()`方法就是`getSnapshot`的快捷方式。
227 |
228 | Because the nature of state is mutable, a snapshot will be emitted whenever the state is mutated! To listen to those new snapshot, you can use `onSnapshot(store, snapshot => console.log(snapshot))` to log them as they are emitted!
229 |
230 | ## 从快照到 model
231 | 正如我们所见,从一个 model 实例获取一个快照是相当的简单,但是能不能很灵巧的把一个快照恢复成一个 model 呢?当然是可以的。
232 |
233 | That basically means that you can restore your objects with your custom methods by just knowing the type of the tree and its snapshot! 你有两种方法可以完成这个操作。
234 |
235 | 第一种方法就是创建一个新的 model 实例,然后把快照作为参数传递给它。That means that you will need to update all your store references, if used in React components, to the new one.
236 |
237 | 第二种方法是通过把快照应用到一个已存在的 model 实例上,来避免出现这种引用问题。属性会被更新,但是存储引用不会发生变化。这将会触发一个被称为“调和”的操作,我们在后面会讨论这个。
238 |
239 | ```javascript
240 | // 1st
241 | const store = RootStore.create({
242 | "users": {},
243 | "todos": {
244 | "1": {
245 | "name": "Eat a cake",
246 | "done": true
247 | }
248 | }
249 | })
250 |
251 | // 2nd
252 | applySnapshot(store, {
253 | "users": {},
254 | "todos": {
255 | "1": {
256 | "name": "Eat a cake",
257 | "done": true
258 | }
259 | }
260 | })
261 | ```
262 | [View sample in playground](https://codesandbox.io/s/xjm99kkopp)
263 |
264 | ## 时间旅行
265 | 获取和应用快照的能力使得用户能够很容易的实现时间旅行。你只需要基于快照的监听,存储并重新应用他们就可以实现时间旅行。
266 |
267 | 下面是一个简单的实现:
268 |
269 | ```javascript
270 | import { applySnapshot, onSnapshot } from "mobx-state-tree"
271 |
272 | var states = []
273 | var currentFrame = -1
274 |
275 | onSnapshot(store, snapshot => {
276 | if (currentFrame === states.length - 1) {
277 | currentFrame++
278 | states.push(snapshot)
279 | }
280 | })
281 |
282 | export function previousState() {
283 | if (currentFrame === 0) return
284 | currentFrame--
285 | applySnapshot(store, states[currentFrame])
286 | }
287 |
288 | export function nextState() {
289 | if (currentFrame === states.length - 1) return
290 | currentFrame++
291 | applySnapshot(store, states[currentFrame])
292 | }
293 | ```
294 |
295 | ## 和界面关联起来
296 | MST 完全兼容 MobX 的 autorun、reaction、observe 等功能特性。你可以使用 mobx-react 将 MST 的 store 和 React 组件关联起来,更多详细信息可查阅 mobx-react 的文档。MST 可以很容易的和任何视图引擎整合起来,只要对快照进行监听并进行相应的更新即可。
297 |
298 | ```javascript
299 | const App = observer(props =>
574 | )
575 | ```
576 | [View sample in playground](https://codesandbox.io/s/mzvx6o7r0j)
577 |
578 | ## References are safe!
579 | One neat feature of references, is that they will throw if you accidentally remove a model that is required by a computed! If you try to remove a user that's used by a reference, you'll get something like this:
580 |
581 | ```
582 | [mobx-state-tree] Failed to resolve reference of type : '1' (in: /todos/1/user)
583 | ```
584 |
585 | ## Next up
586 | In part 2 of this tutorial, we will discover how to use MST lifecycle hooks and local state to fetch user data from an XHR endpoint, and see how environments will help dealing with dependency injection of the parameters needed to fetch our endpoint. We will implement auto-save using mobx helpers and learn more about patches and actions event streams.
587 |
--------------------------------------------------------------------------------
/docs/middleware.md:
--------------------------------------------------------------------------------
1 | # Middleware
2 |
3 | MST ships with a small set of [pre-built / example middlewares](../packages/mst-middlewares/README.md)
4 |
5 | Middleware can be used to intercept any action is invoked on the subtree where it is attached.
6 | If a tree is protected (by default), this means that any mutation of the tree will pass through your middleware.
7 |
8 | [SandBox example](https://codesandbox.io/s/mQrqy8j73)
9 |
10 | Middleware can be attached by using `addMiddleware(node, handler: (call, next(call) => void) => void)`
11 |
12 | It is allowed to attach multiple middlewares. The order in which middleware is invoked is inside-out:
13 | local middleware is invoked before parent middleware. On the same object, earlier attached middleware is run before later attached middleware.
14 |
15 | A middleware handler receives two arguments: 1. the description of the the call, 2: a function to invoke the next middleware in the chain.
16 | If `next(call)` is not invoked by your middleware, the action will be aborted and not actually executed.
17 | Before passing the call to the next middleware using `next`, feel free to (clone and) modify the call arguments. Other properties should not be modified
18 |
19 | A call description looks like:
20 |
21 | ```javascript
22 | export type IMiddleWareEvent = {
23 | type: IMiddlewareEventType
24 | name: string
25 | id: number
26 | parentId: number
27 | rootId: number
28 | tree: IStateTreeNode
29 | context: IStateTreeNode
30 | args: any[]
31 | }
32 |
33 | export type IMiddlewareEventType =
34 | | "action"
35 | | "flow_spawn"
36 | | "flow_resume"
37 | | "flow_resume_error"
38 | | "flow_return"
39 | | "flow_throw"
40 | ```
41 |
42 | A very simple middleware that just logs the invocation of actions will look like:
43 |
44 | @example
45 | ```typescript
46 | const store = SomeStore.create()
47 | const disposer = addMiddleWare(store, (call, next) => {
48 | console.log(`action ${call.name} was invoked`)
49 | return next(call) // runs the next middleware (or the implementation of the targeted action if there is no middleware to run left)
50 | })
51 | ```
52 |
53 | _Note: for middleware, it is extremely important that `next(action)` is called for asynchronous actions (`call.type !== "action"`), otherwise the generator will remain in an unfinished state forever_
54 |
55 | # Built-in middlewares
56 |
57 | * [`onAction`](https://github.com/mobxjs/mobx-state-tree/blob/09708ba86d04f433cc23fbcb6d1dc4db170f798e/src/core/action.ts#L174)
58 | * More will follow soon
59 |
60 | ## Call attributes
61 |
62 | * `name` is the name of the action
63 | * `context` is the object on which the action was defined & invoked
64 | * `tree` is the root of the MST tree in which the action was fired (`tree === getRoot(context)`)
65 | * `args` are the original arguments passed to the action
66 | * `id` is a number that is unique per external action invocation.
67 | * `parentId` is the number of the action / process that called this action. `0` if it wasn't called by another action but directly from user code
68 | * `rootid` is the id of the action that spawned this action. If an action was not spawned by another action, but something external (user event etc), `id` and `rootId` will be equal (and `parentid` `0`)
69 |
70 | `type` Indicates which kind of event this is
71 |
72 | * `action`: this is a normal synchronous action invocation
73 | * `flow_spawn`: The invocation / kickoff of a `process` block (see [asynchronous actions](async-actions.md))
74 | * `flow_resume`: a promise that was returned from `yield` earlier has resolved. `args` contains the value it resolved to, and the action will now continue with that value
75 | * `flow_resume_error`: a promise that was returned from `yield` earlier was rejected. `args` contains the rejection reason, and the action will now continue throwing that error into the generator
76 | * `flow_return`: the generator completed successfully. The promise returned by the action will resolve with the value found in `args`
77 | * `flow_throw`: the generator threw an uncatched exception. The promise returned by the action will reject with the exception found in `args`
78 |
79 | To see how a bunch of calls from an asynchronous process look, see the [unit tests](https://github.com/mobxjs/mobx-state-tree/blob/09708ba86d04f433cc23fbcb6d1dc4db170f798e/test/async.ts#L289)
80 |
81 | A minimal, empty process will fire the following events if started as action:
82 |
83 | 1. `action`: An `action` event will always be emitted if a process is exposed as action on a model)
84 | 2. `flow_spawn`: This is just the notification that a new generator was started
85 | 3. `flow_resume`: This will be emitted when the first "code block" is entered. (So, with zero yields there is one `flow_resume` still)
86 | 4. `flow_return`: The process has completed
87 |
--------------------------------------------------------------------------------