Skip to content

Commit e5717d2

Browse files
feat(proxy-state-tree): allow tracking with unique proxifier
1 parent c3f8d7b commit e5717d2

File tree

6 files changed

+121
-13
lines changed

6 files changed

+121
-13
lines changed

packages/node_modules/proxy-state-tree/README.md

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,36 @@ trackStateTree.track(() => {
4646
})
4747
```
4848

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.
49+
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. You still have to stop tracking when the component is done rendering though, to avoid any asynchronous additions to the rendering of the last component (which would just keep tracking until a new component renders).
5050

51-
## trackScope
51+
```js
52+
const trackStateTree = tree.getTrackStateTree()
53+
54+
trackStateTree.stopTracking()
55+
```
56+
57+
### track with unique proxifier
58+
59+
```js
60+
const trackStateTree = tree.getTrackStateTreeWithProxifier()
61+
62+
trackStateTree.track(() => {
63+
// Called when any tracked state mutates
64+
})
65+
```
66+
67+
This version of the tree also has its own **Proxifier** instance. That means the state provided to the component is owned by the component. The benefit of this approach is that you do not depend on synchronous rendering. The component can start to render, then render something else and then continue rendering the component and still track correctly. You will typically call **track** again whenever there is an update, to refresh the tracked paths. You can stop the tracking also if you know when the rendering is done. This avoid any asynchronous tracking inside the component to happen.
68+
69+
To support passing a proxy from one component to an other, the **rescope** method can be used:
70+
71+
```js
72+
const trackStateTreeA = tree.getTrackStateTreeWithProxifier()
73+
const trackStateTreeB = tree.getTrackStateTreeWithProxifier()
74+
75+
const movedProxy = tree.rescope(trackStateTreeA.state.someObjectOrArray, trackStateTreeB)
76+
```
77+
78+
## track scope
5279

5380
```js
5481
const trackStateTree = tree.getTrackStateTree()

packages/node_modules/proxy-state-tree/src/Proxyfier.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,9 @@ const arrayMutations = new Set([
1919

2020
export class Proxifier {
2121
private proxyCache = {}
22+
private disposeRemoveProxy: () => void
2223
constructor(private tree: TTree) {
23-
tree.master.onRemoveProxy(this.removeProxies)
24+
this.disposeRemoveProxy = tree.master.onRemoveProxy(this.removeProxies)
2425
}
2526
private concat(path, prop) {
2627
return path ? path + '.' + prop : prop
@@ -98,6 +99,10 @@ export class Proxifier {
9899
}
99100
}
100101

102+
isDefaultProxifier() {
103+
return this.tree.proxifier === this.tree.master.proxifier
104+
}
105+
101106
ensureValueDosntExistInStateTreeElsewhere(value) {
102107
if (value && value[IS_PROXY] === true) {
103108
throw new Error(
@@ -109,7 +114,11 @@ export class Proxifier {
109114
}
110115

111116
trackPath(path: string) {
112-
if (this.tree.canTrack()) {
117+
if (!this.tree.canTrack()) {
118+
return
119+
}
120+
121+
if (this.isDefaultProxifier()) {
113122
const trackStateTree = this.tree.master.currentTree as ITrackStateTree<
114123
any
115124
>
@@ -119,17 +128,17 @@ export class Proxifier {
119128
}
120129

121130
trackStateTree.addTrackingPath(path)
131+
} else {
132+
;(this.tree as ITrackStateTree<any>).addTrackingPath(path)
122133
}
123134
}
124135
// With tracking trees we want to ensure that we are always
125136
// on the currently tracked tree. This ensures when we access
126137
// a tracking proxy that is not part of the current tracking tree (pass as prop)
127138
// we move the ownership to the current tracker
128139
getTrackingTree() {
129-
const currentTree = this.tree.master.currentTree
130-
131-
if (currentTree) {
132-
return currentTree
140+
if (this.tree.master.currentTree && this.isDefaultProxifier()) {
141+
return this.tree.master.currentTree
133142
}
134143

135144
if (!this.tree.canTrack()) {
@@ -377,4 +386,7 @@ export class Proxifier {
377386

378387
return value
379388
}
389+
destroy() {
390+
this.proxyCache = null
391+
}
380392
}

packages/node_modules/proxy-state-tree/src/TrackStateTree.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ export class TrackStateTree<T extends object> implements ITrackStateTree<T> {
3838
track(cb?: ITrackCallback) {
3939
this.master.changeTrackStateTree(this)
4040
this.shouldTrack = true
41-
setTimeout(() => (this.shouldTrack = false))
4241
if (this.callback) {
4342
for (let path of this.pathDependencies) {
4443
this.master.removePathDependency(path, this.callback)
@@ -58,6 +57,9 @@ export class TrackStateTree<T extends object> implements ITrackStateTree<T> {
5857

5958
return this
6059
}
60+
stopTracking() {
61+
this.shouldTrack = false
62+
}
6163
trackScope(scope: ITrackScopedCallback<T>, cb?: ITrackCallback) {
6264
const previousPreviousTree = this.master.previousTree
6365
const previousCurrentTree = this.master.currentTree
@@ -66,10 +68,14 @@ export class TrackStateTree<T extends object> implements ITrackStateTree<T> {
6668
const result = scope(this)
6769
this.master.currentTree = previousCurrentTree
6870
this.master.previousTree = previousPreviousTree
69-
71+
this.stopTracking()
7072
return result
7173
}
7274
dispose() {
75+
if (this.proxifier !== this.master.proxifier) {
76+
this.proxifier.destroy()
77+
}
78+
7379
if (!this.callback) {
7480
this.pathDependencies.clear()
7581

packages/node_modules/proxy-state-tree/src/index.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,56 @@ describe('TrackStateAccessTree', () => {
6868

6969
expect(reactionCount).toBe(1)
7070
})
71+
test('should stop tracking on command', () => {
72+
let reactionCount = 0
73+
const tree = new ProxyStateTree({
74+
foo: 'bar',
75+
bar: 'baz',
76+
})
77+
78+
const accessTree = tree
79+
.getTrackStateTree()
80+
.track((mutations, paths, flushId) => {
81+
reactionCount++
82+
expect(flushId).toBe(0)
83+
})
84+
85+
accessTree.state.foo
86+
87+
accessTree.stopTracking()
88+
89+
accessTree.state.bar
90+
91+
const mutationTree = tree.getMutationTree()
92+
93+
mutationTree.state.foo = 'bar2'
94+
mutationTree.flush()
95+
mutationTree.state.bar = 'baz2'
96+
mutationTree.flush()
97+
expect(reactionCount).toBe(1)
98+
})
99+
test('should allow tracking trees individually', () => {
100+
const tree = new ProxyStateTree({
101+
foo: 'bar',
102+
bar: 'baz',
103+
})
104+
105+
const accessTreeA = tree.getTrackStateTreeWithProxifier()
106+
const accessTreeB = tree.getTrackStateTreeWithProxifier()
107+
108+
accessTreeA.track(() => {})
109+
accessTreeB.track(() => {})
110+
111+
accessTreeA.state.foo
112+
accessTreeB.state.bar
113+
114+
return Promise.resolve().then(() => {
115+
accessTreeB.state.foo
116+
117+
expect(Array.from(accessTreeA.pathDependencies)).toEqual(['foo'])
118+
expect(Array.from(accessTreeB.pathDependencies)).toEqual(['bar', 'foo'])
119+
})
120+
})
71121
})
72122
describe('TrackMutationTree', () => {})
73123

@@ -110,6 +160,7 @@ describe('OBJECTS', () => {
110160
])
111161
})
112162
})
163+
113164
describe('MUTATIONS', () => {
114165
test('should throw when mutating without tracking', () => {
115166
const state = {

packages/node_modules/proxy-state-tree/src/index.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,11 @@ export class ProxyStateTree<T extends object> implements IProxyStateTree<T> {
8484
)
8585
}
8686
onRemoveProxy(cb: IRemoveProxyCallback) {
87-
this.removeProxyCallbacks.push(cb)
87+
const index = this.removeProxyCallbacks.push(cb) - 1
88+
89+
return () => {
90+
this.removeProxyCallbacks.splice(index, 1)
91+
}
8892
}
8993
removeProxy(path: string) {
9094
for (let cb of this.removeProxyCallbacks) {
@@ -102,7 +106,13 @@ export class ProxyStateTree<T extends object> implements IProxyStateTree<T> {
102106
return tree
103107
}
104108
getTrackStateTree(): ITrackStateTree<T> {
105-
const tree = this.cache.trackStateTree.pop() || new TrackStateTree(this)
109+
return this.cache.trackStateTree.pop() || new TrackStateTree(this)
110+
}
111+
getTrackStateTreeWithProxifier(): ITrackStateTree<T> {
112+
const tree = this.getTrackStateTree()
113+
114+
tree.proxifier = new Proxifier(tree)
115+
tree.state = tree.proxifier.proxify(this.sourceState, '')
106116

107117
return tree
108118
}

packages/node_modules/proxy-state-tree/src/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export interface IProxifier<T extends object> {
22
proxify(state: T, path: string): T
33
trackPath(path: string): void
4+
destroy(): void
45
}
56

67
export interface IMutation {
@@ -50,6 +51,7 @@ export interface ITrackScopedCallback<T extends object> {
5051
export interface ITrackStateTree<T extends object> {
5152
addTrackingPath(path: string): void
5253
track(cb?: ITrackCallback): ITrackStateTree<T>
54+
stopTracking(): void
5355
trackScope(scope: ITrackScopedCallback<T>, callback?: ITrackCallback): any
5456
canTrack(): boolean
5557
canMutate(): boolean
@@ -90,7 +92,7 @@ export interface IProxyStateTree<T extends object> {
9092
changeTrackStateTree(tree: ITrackStateTree<T>): void
9193
disposeTree(proxy: TTree): void
9294
onMutation(cb: IMutationCallback): void
93-
onRemoveProxy(cb: IRemoveProxyCallback): void
95+
onRemoveProxy(cb: IRemoveProxyCallback): () => void
9496
removeProxy(path: string): void
9597
flush(
9698
tree: IMutationTree<T>,

0 commit comments

Comments
 (0)