@@ -3,14 +3,16 @@ import { EventEmitter } from 'betsy'
33import ProxyStateTree from 'proxy-state-tree'
44import Devtools , { Message , safeValue } from './Devtools'
55import Action , { IValueAction , INoValueAction } from './Action'
6+ import Reaction , { ReactionConfig } from './reaction'
67export { default as namespaces , Namespace } from './namespaces'
78export { default as derived } from './derived'
89export { default as computed } from './computed'
910
10- type Configuration < State , Providers , Actions > = {
11+ type Configuration < State , Providers , Actions , Reactions > = {
1112 state ?: State
1213 providers ?: Providers
1314 actions ?: Actions
15+ reactions ?: Reactions
1416}
1517
1618type Options = {
@@ -33,12 +35,32 @@ export interface Events {
3335 cacheKeysCount : number
3436 cacheKeyIndex : number
3537 }
38+ 'reaction:add' : {
39+ path : string
40+ statePath : string
41+ updateCount : number
42+ }
43+ 'reaction:update' : {
44+ path : string
45+ statePath : string
46+ updateCount : number
47+ }
48+ 'reaction:remove' : {
49+ path : string
50+ statePath : string
51+ updateCount : number
52+ }
3653}
3754
3855export type ActionsCallback < Providers , State > = (
3956 action : IAction < State , Providers & { state : State } >
4057) => any
4158
59+ export type ReactionsCallback < State , Providers > = (
60+ reaction : ReactionConfig < State , Providers > ,
61+ action : IAction < State , Providers & { state : State } >
62+ ) => any
63+
4264export type TContext < State , Providers = { } > = Providers & {
4365 state : State
4466}
@@ -52,31 +74,49 @@ export interface IAction<State, Context> {
5274export default class App <
5375 State extends object ,
5476 Providers extends object ,
77+ Reactions extends
78+ | ReactionsCallback < State , Providers >
79+ | {
80+ [ namespace : string ] : ReactionsCallback < State , any >
81+ } ,
5582 Actions extends
83+ | ActionsCallback < Providers , State >
5684 | {
5785 [ namespace : string ] : ActionsCallback < { } , { } >
5886 }
59- | ActionsCallback < Providers , State >
6087> {
6188 private proxyStateTree : ProxyStateTree
89+ private eventHub : EventEmitter < Events >
6290 devtools : Devtools
63- actions : Actions extends {
64- [ namespace : string ] : ActionsCallback < { } , { } >
65- }
66- ? { [ Namespace in keyof Actions ] : ReturnType < Actions [ Namespace ] > }
67- : Actions extends ActionsCallback < Providers , State >
68- ? ReturnType < Actions >
91+ actions : Actions extends ActionsCallback < Providers , State >
92+ ? ReturnType < Actions >
93+ : Actions extends {
94+ [ namespace : string ] : ActionsCallback < { } , { } >
95+ }
96+ ? { [ Namespace in keyof Actions ] : ReturnType < Actions [ Namespace ] > }
6997 : any
7098 state : State
7199 constructor (
72- configuration : Configuration < State , Providers , Actions > ,
100+ configuration : Configuration < State , Providers , Actions , Reactions > ,
73101 options : Options = { }
74102 ) {
103+ /*
104+ Set up an eventHub to trigger information from derived, computed and reactions
105+ */
75106 const eventHub = new EventEmitter < Events > ( )
107+
108+ /*
109+ Create the proxy state tree instance with the state and a wrapper to expose
110+ the eventHub
111+ */
76112 const proxyStateTree = new ProxyStateTree ( configuration . state || { } , {
77113 dynamicWrapper : ( proxyStateTree , path , func ) =>
78114 func ( eventHub , proxyStateTree , path ) ,
79115 } )
116+
117+ /*
118+ The action chain with the context configuration
119+ */
80120 const actionChain = new ActionChain (
81121 Object . assign (
82122 {
@@ -88,12 +128,30 @@ export default class App<
88128 providerExceptions : [ 'state' ] ,
89129 }
90130 )
131+
132+ /*
133+ The action factory function
134+ */
91135 const action = function < InitialValue > ( ) : [ InitialValue ] extends [ void ]
92136 ? INoValueAction < State , Providers & { state : State } , InitialValue >
93137 : IValueAction < State , Providers & { state : State } , InitialValue > {
94138 return new Action ( proxyStateTree , actionChain ) as any
95139 }
96140
141+ if ( options . devtools && typeof window !== 'undefined' ) {
142+ this . initializeDevtools (
143+ options . devtools ,
144+ actionChain ,
145+ eventHub ,
146+ proxyStateTree
147+ )
148+ }
149+
150+ this . initializeReactions ( configuration , eventHub , proxyStateTree , action )
151+
152+ /*
153+ Identify when the state tree should flush out changes
154+ */
97155 actionChain . on ( 'operator:async' , ( ) => {
98156 if ( this . devtools ) {
99157 this . devtools . send ( {
@@ -113,29 +171,16 @@ export default class App<
113171 proxyStateTree . flush ( )
114172 } )
115173
174+ /*
175+ Expose the created actions
176+ */
177+ this . actions = this . getActions ( configuration , action )
178+
116179 this . state = proxyStateTree . get ( )
117- this . actions =
118- typeof configuration . actions === 'function'
119- ? ( configuration . actions as ActionsCallback < Providers , State > ) (
120- action as IAction < State , Providers & { state : State } >
121- )
122- : ( Object . keys ( configuration . actions || { } ) . reduce (
123- ( aggr , namespace ) =>
124- Object . assign ( aggr , {
125- [ namespace ] : configuration . actions [ namespace ] ( action as IAction <
126- State ,
127- Providers & { state : State }
128- > ) ,
129- } ) ,
130- { }
131- ) as any )
132180 this . proxyStateTree = proxyStateTree
133-
134- if ( options . devtools && typeof window !== 'undefined' ) {
135- this . initializeDevtools ( options . devtools , actionChain , eventHub )
136- }
181+ this . eventHub = eventHub
137182 }
138- private initializeDevtools ( host , actionChain , eventHub ) {
183+ private initializeDevtools ( host , actionChain , eventHub , proxyStateTree ) {
139184 const devtools = new Devtools ( )
140185 devtools . connect (
141186 host ,
@@ -146,7 +191,7 @@ export default class App<
146191 devtools . send ( {
147192 type : 'init' ,
148193 data : {
149- state : this . proxyStateTree . get ( ) ,
194+ state : proxyStateTree . get ( ) ,
150195 } ,
151196 } )
152197 actionChain . on ( 'action:start' , ( data ) =>
@@ -200,8 +245,87 @@ export default class App<
200245 data,
201246 } )
202247 )
248+ eventHub . on ( 'reaction:add' , ( data ) =>
249+ devtools . send ( {
250+ type : 'reaction:add' ,
251+ data,
252+ } )
253+ )
254+ eventHub . on ( 'reaction:update' , ( data ) =>
255+ devtools . send ( {
256+ type : 'reaction:update' ,
257+ data,
258+ } )
259+ )
260+ eventHub . on ( 'reaction:remove' , ( data ) =>
261+ devtools . send ( {
262+ type : 'reaction:remove' ,
263+ data,
264+ } )
265+ )
203266 this . devtools = devtools
204267 }
268+ private initializeReactions ( configuration , eventHub , proxyStateTree , action ) {
269+ if ( typeof configuration . reactions === 'function' ) {
270+ const reactions = ( configuration . reactions as any ) (
271+ ( stateCb , action ) => [ stateCb , action ] ,
272+ action
273+ )
274+ Object . keys ( reactions ) . forEach ( ( key ) => {
275+ const reaction = new Reaction ( eventHub , proxyStateTree , key )
276+ reaction . create ( reactions [ key ] [ 0 ] , reactions [ key ] [ 1 ] )
277+ } )
278+ } else {
279+ const reactions = Object . keys ( configuration . reactions || { } ) . reduce (
280+ ( aggr , namespace ) =>
281+ Object . assign (
282+ aggr ,
283+ configuration . reactions [ namespace ]
284+ ? {
285+ [ namespace ] : ( configuration . reactions [ namespace ] as any ) (
286+ ( stateCb , action ) => [ stateCb , action ] ,
287+ action
288+ ) ,
289+ }
290+ : { }
291+ ) ,
292+ { }
293+ )
294+ Object . keys ( reactions ) . forEach ( ( namespace ) => {
295+ Object . keys ( reactions [ namespace ] ) . forEach ( ( key ) => {
296+ const reaction = new Reaction (
297+ eventHub ,
298+ proxyStateTree ,
299+ namespace + '.' + key
300+ )
301+ reaction . create (
302+ reactions [ namespace ] [ key ] [ 0 ] ,
303+ reactions [ namespace ] [ key ] [ 1 ]
304+ )
305+ } )
306+ } )
307+ }
308+ }
309+ private getActions ( configuration , action ) {
310+ return typeof configuration . actions === 'function'
311+ ? ( configuration . actions as ActionsCallback < Providers , State > ) (
312+ action as IAction < State , Providers & { state : State } >
313+ )
314+ : ( Object . keys ( configuration . actions || { } ) . reduce (
315+ ( aggr , namespace ) =>
316+ Object . assign (
317+ aggr ,
318+ configuration . actions [ namespace ]
319+ ? {
320+ [ namespace ] : configuration . actions [ namespace ] (
321+ action as IAction < State , Providers & { state : State } >
322+ ) ,
323+ }
324+ : { }
325+ ) ,
326+ { }
327+ ) as any )
328+ }
205329 trackState ( ) {
206330 return this . proxyStateTree . startPathsTracking ( )
207331 }
@@ -211,4 +335,24 @@ export default class App<
211335 addMutationListener ( paths , cb ) {
212336 return this . proxyStateTree . addMutationListener ( paths , cb )
213337 }
338+ createReactionFactory ( ) {
339+ const reactions = [ ]
340+ const instance = this
341+ return {
342+ add ( name : string , stateCb : ( state : State ) => any , cb : Function ) {
343+ const reaction = new Reaction (
344+ instance . eventHub ,
345+ instance . proxyStateTree ,
346+ 'view.' + name
347+ )
348+ reaction . create ( stateCb , cb )
349+
350+ reactions . push ( reaction )
351+ } ,
352+ dispose ( ) {
353+ reactions . forEach ( ( reaction ) => reaction . destroy ( ) )
354+ reactions . length = 0
355+ } ,
356+ }
357+ }
214358}
0 commit comments