You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
An implementation of the Mobx/Vue state tracking approach, for library authors
2
+
An implementation of the Mobx/Vue state tracking approach with a state tree, for library authors
3
3
4
4
`npm install proxy-state-tree`
5
5
@@ -10,205 +10,178 @@ The **proxy-state-tree** project is created to stimulate innovation in state man
10
10
11
11
**proxy-state-tree** is a low level implementation of the **getter/setter interception** with a **single state tree** to help library authors innovate. I hope to see innovations that removes the burden that immutability currently causes, but keeps the guarantees that was introduced in **Flux**. I invite you to make a mobx and redux baby! ;-)
12
12
13
-
## Examples
14
-
15
-
-[Vue](https://codesandbox.io/s/5vy5jxrpop) example with a simple implementation that allows you to define a state tree and expose a store to the components which can be mutated in the methods
16
-
17
-
-[Preact](https://codesandbox.io/s/lpmv68r8y9) example with a simple implementation of a external state and actions passed on a Provider. Also includes a simple, but inspiring debugger
18
-
19
13
## Create a tree
20
14
21
15
```js
22
-
importProxyStateTreefrom'proxy-state-tree'
16
+
import{ ProxyStateTree }from'proxy-state-tree'
23
17
24
-
consttree=newProxyStateTree({})
18
+
constinitialState={}
25
19
26
-
console.log(tree.get()) // {}
20
+
consttree=newProxyStateTree(initialState)
27
21
```
28
22
29
-
As a library author you would typically expose a mechanism to define the initial state of the application, which you would pass to the **ProxyStateTree**. You would also expose a way to access the state, hiding the `tree.get()` from the consumer of your library.
23
+
As a library author you would typically expose a mechanism to define the initial state of the application, which you would pass to the **ProxyStateTree**.
30
24
31
-
## Track access
32
-
33
-
You can track access to the state by using the **startPathsTracking** and **clearPathsTracking** methods.
25
+
## TrackStateTree
34
26
35
27
```js
36
-
importProxyStateTreefrom'proxy-state-tree'
37
-
38
28
consttree=newProxyStateTree({
39
-
foo:'bar',
40
-
bar:'baz'
29
+
foo:'bar'
41
30
})
42
-
conststate=tree.get()
43
31
44
-
consttrackId=tree.startPathsTracking()
45
-
constfoo=state.foo
46
-
constbar=state.bar
47
-
constpaths=tree.clearPathsTracking(trackId)
32
+
consttrackStateTree=tree.getTrackStateTree()
48
33
49
-
console.log(paths)//Set { 'foo', 'bar' }
34
+
trackStateTree.state.foo//"bar"
50
35
```
51
36
52
-
You would typically use this mechanism to track usage of state. For example rendering a component, calculating a a computed value etc. The returned paths set is stored for later usage. The paths structure is used internally by proxy-state-tree, but you can also consume it as a library author to for example showing components and what paths they depend on in a devtool. Nested paths uses dot notation, for example `['foo.bar']`. Path tracking needs to be synchronous from the beginning of the tracking, but the clearing of tracking can be asynchronous.
37
+
This is a "fork" of the tree which allows you to access and track access to state. You can have multiple forks and you would typically give each component its own state tracking tree.
53
38
54
-
##Track mutations
39
+
### track
55
40
56
41
```js
57
-
importProxyStateTreefrom'proxy-state-tree'
42
+
consttrackStateTree=tree.getTrackStateTree()
58
43
59
-
consttree=newProxyStateTree({
60
-
foo:'bar',
61
-
bar: []
44
+
trackStateTree.track(() => {
45
+
// Called when any tracked state mutates
62
46
})
63
-
conststate=tree.get()
64
-
65
-
tree.startMutationTracking()
66
-
state.foo='bar2'
67
-
state.bar.push('baz')
68
-
constmutations=tree.clearMutationTracking()
69
-
70
-
console.log(mutations)
71
-
/*
72
-
[{
73
-
method: 'set',
74
-
path: ['foo'],
75
-
args: ['bar2']
76
-
}, {
77
-
method: 'push',
78
-
path: ['bar'],
79
-
args: ['baz']
80
-
}]
81
-
*/
82
47
```
83
48
84
-
You would use **startMutationTracking** and **clearMutationTracking** around logic that is allowed to do mutations, for example actions or similar. Trying to mutate without this tracking active results in an error. The returned array can be used in combination with a devtool.
49
+
You only need to start tracking state, there is no need to stop tracking. The reason is that only one forked tree can track at any time. That means one tree stops tracking when an other starts tracking. Since all component libraries produces their UI description synchronously, this gives a predictable behaviour. It is also makes the implementation simple. The tracking of each fork stops on the next event loop.
85
50
86
-
## Check need to update
51
+
## trackScope
87
52
88
53
```js
89
-
importProxyStateTreefrom'proxy-state-tree'
54
+
consttrackStateTree=tree.getTrackStateTree()
90
55
91
-
consttree=newProxyStateTree({
92
-
foo:'bar',
93
-
bar:'baz'
56
+
trackStateTree.trackScope((tree) => {
57
+
// Some logic accessing the state of the tree
94
58
})
95
-
conststate=tree.get()
59
+
```
96
60
97
-
functionrender () {
98
-
consttrackId=tree.startPathsTracking()
99
-
constfoo=state.foo
100
-
constbar=state.bar
61
+
Sometimes you do want to scope the tracking to a callback. This ensures that the tracking indeed runs completely synchronous within the scope of the callback. Optionally you can also give a callback to be notified when mutations affects the tracked state.
// Whenever mutations affecting these paths occurs
109
-
// we typically create the paths again due to possible
110
-
// conditional logic, in "render" in this example
111
-
listener.update(render())
75
+
```js
76
+
consttrackStateTree=tree.getTrackStateTree()
77
+
78
+
trackStateTree.addTrackingPath('foo.bar')
79
+
```
80
+
81
+
You can manually add paths that the tree should track.
82
+
83
+
## MutationTree
84
+
85
+
```js
86
+
consttree=newProxyStateTree({
87
+
foo:'bar'
112
88
})
113
89
114
-
tree.startMutationTracking()
115
-
state.foo='bar2'
116
-
state.bar.push('baz')
117
-
tree.clearMutationTracking()
90
+
constmutationTree=tree.getMutationTree()
118
91
119
-
// This command flushes out the current mutations and
120
-
// notifies any listeners
121
-
tree.flush()
92
+
mutationTree.state.foo="bar"
93
+
```
94
+
95
+
This forked tree is allowed to perform actual mutations.
96
+
97
+
### onMutation
98
+
99
+
```js
100
+
constmutationTree=tree.getMutationTree()
122
101
123
-
// Remove listener
124
-
listener.dispose()
102
+
mutationTree.onMutation((mutation) => {
103
+
// mutation: { method, args, path }
104
+
})
125
105
```
126
106
127
-
Here we combine the tracked paths with the mutations performed to see if this components, computed or whatever indeed needs to run again, doing a new **startPathsTracking** and **clearPathsTracking**.
107
+
Allows you to listen to mutations on the specific tree.
128
108
129
-
You can optionally declare a global listener which will be informed by all mutation flushes:
109
+
## diposeTree
130
110
131
111
```js
132
-
importProxyStateTreefrom'proxy-state-tree'
112
+
consttree=newProxyStateTree({})
113
+
114
+
consttrackStateTree=tree.getTrackStateTree()
115
+
116
+
tree.disposeTree(trackStateTree)
117
+
```
118
+
119
+
Allows you to dipose of a tree no longer in use. ProxyStateTree will keep a reference and reuse the tree whenever a new fork is requested.
In addition to this you can also listen to each individual mutation:
135
+
To notify trees tracking state about mutations made run the **flush** method. This allows you to control when the trackers should actually be notified.
It is important here to check the **paths** argument for a list of all paths possibly changed. Reason is that a single mutation might cause changes to multiple paths.
187
-
188
-
## Dynamic state values
147
+
Get notified when any mutation is made to any fork of **MutationTree**.
189
148
190
-
If you insert a function into the state tree it will be called when accessed. The function is passed the **proxy-state-tree** instance and the path of where the function lives in the tree.
149
+
## onFlush
191
150
192
151
```js
193
-
importProxyStateTreefrom'proxy-state-tree'
194
-
195
152
consttree=newProxyStateTree({
196
-
foo:(proxyStateTree, path) => {}
153
+
foo:'bar'
197
154
})
155
+
156
+
tree.onFlush(() => {})
198
157
```
199
158
200
-
The allows you to easily extend functionality with for example a computed concept that lives in the tree, as you can see in this [codesandbox](https://codesandbox.io/s/xnv45zmkz).
This helps you expose library entities to these functions.
179
+
Rescoping proxies between trees is useful in development as the MutationTrees has their own proxies. Unlike in production where all trees shares the same proxies.
180
+
181
+
## Production
182
+
183
+
When running in development all **TrackStateTree** forks has the same *proxifier*, meaning they share proxies. They can do this cause the trees "hand over" tracking to each other.
184
+
185
+
Every fork of a **MutationTree** has its own *proxifier*. The reason for this is that in development each mutation tree fork should live on its own for tracking purposes.
186
+
187
+
When running in production there is only one *proxifier* shared among all trees, and there is only one mutationtree instance as tracking is no longer needed.
0 commit comments