Skip to content

Commit fd15e1a

Browse files
committed
WIP: rewrite Reactor to be stateless
1 parent 23ffb02 commit fd15e1a

File tree

6 files changed

+516
-137
lines changed

6 files changed

+516
-137
lines changed

src/reactor-state.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
var logging = require('./logging')
2+
var Map = require('immutable').Map
3+
var extend = require('./utils').extend
4+
var toJS = require('./immutable-helpers').toJS
5+
var toImmutable = require('./immutable-helpers').toImmutable
6+
7+
/**
8+
* Singular state wrapper for all reactor state
9+
*/
10+
class ReactorState {
11+
constructor(config) {
12+
config = config || {}
13+
this.debug = !!config.debug
14+
/**
15+
* The state for the whole cluster
16+
*/
17+
this.dispatchId = 0
18+
this.state = Immutable.Map({})
19+
this.stores = Immutable.Map({})
20+
21+
this.__evaluator = new Evaluator()
22+
}
23+
24+
registerStores() {
25+
each(stores, (store, id) => {
26+
if (this.stores.get(id)) {
27+
/* eslint-disable no-console */
28+
console.warn('Store already defined for id = ' + id)
29+
/* eslint-enable no-console */
30+
}
31+
32+
var initialState = store.getInitialState()
33+
34+
if (this.debug && !isImmutableValue(initialState)) {
35+
throw new Error('Store getInitialState() must return an immutable value, did you forget to call toImmutable')
36+
}
37+
38+
this.stores = this.stores.set(id, store)
39+
this.state = this.state.set(id, initialState)
40+
})
41+
42+
this.notify()
43+
}
44+
45+
handleDispatch(actionType, payload) {
46+
var prevState = this.state
47+
this.state = this.__handleAction(this.state, actionType, payload)
48+
if (prevState === this.state) {
49+
this.notify()
50+
}
51+
}
52+
53+
loadState(state) {
54+
var prevState = this.stat
55+
var stateToLoad = toImmutable({}).withMutations(stateToLoad => {
56+
each(state, (serializedStoreState, storeId) => {
57+
var store = this.stores.get(storeId)
58+
if (store) {
59+
var storeState = store.deserialize(serializedStoreState)
60+
if (storeState !== undefined) {
61+
stateToLoad.set(storeId, storeState)
62+
}
63+
}
64+
})
65+
})
66+
67+
this.state = this.state.merge(stateToLoad)
68+
if (prevState !== this.state) {
69+
this.notify()
70+
}
71+
}
72+
73+
/**
74+
* Returns a plain object representing the application state
75+
* @return {Object}
76+
*/
77+
serialize() {
78+
var serialized = {}
79+
this.stores.forEach((store, id) => {
80+
var storeState = this.state.get(id)
81+
var serializedState = store.serialize(storeState)
82+
if (serializedState !== undefined) {
83+
serialized[id] = serializedState
84+
}
85+
})
86+
return serialized
87+
}
88+
89+
reset() {
90+
var debug = this.debug
91+
var prevState = this.state
92+
93+
this.state = Immutable.Map().withMutations(state => {
94+
this.__stores.forEach((store, id) => {
95+
var storeState = prevState.get(id)
96+
var resetStoreState = store.handleReset(storeState)
97+
if (debug && resetStoreState === undefined) {
98+
throw new Error('Store handleReset() must return a value, did you forget a return statement')
99+
}
100+
if (debug && !isImmutableValue(resetStoreState)) {
101+
throw new Error('Store reset state must be an immutable value, did you forget to call toImmutable')
102+
}
103+
state.set(id, resetStoreState)
104+
})
105+
})
106+
107+
this.notify();
108+
}
109+
110+
notify() {
111+
this.dispatchId++
112+
}
113+
114+
/**
115+
* Reduces the current state to the new state given actionType / message
116+
* @param {string} actionType
117+
* @param {object|undefined} payload
118+
* @return {Immutable.Map}
119+
*/
120+
__handleAction(state, actionType, payload) {
121+
}
122+
123+
124+
}

src/reactor.js

Lines changed: 41 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
var Immutable = require('immutable')
2-
var logging = require('./logging')
3-
var ChangeObserver = require('./change-observer')
2+
var createReactMixin = require('./create-react-mixin')
3+
var ReactorState = require('./reactor/reactor-state')
4+
var fns = require('./reactor/fns')
5+
var evaluate = require('./reactor/evaluate')
46
var Getter = require('./getter')
57
var KeyPath = require('./key-path')
6-
var Evaluator = require('./evaluator')
7-
var createReactMixin = require('./create-react-mixin')
88

99
// helper fns
1010
var toJS = require('./immutable-helpers').toJS
11-
var toImmutable = require('./immutable-helpers').toImmutable
12-
var isImmutableValue = require('./immutable-helpers').isImmutableValue
13-
var each = require('./utils').each
1411

1512

1613
/**
@@ -29,24 +26,13 @@ class Reactor {
2926
}
3027
config = config || {}
3128

32-
this.debug = !!config.debug
29+
var initialReactorState = new ReactorState({
30+
debug: config.debug
31+
})
32+
this.prevReactorState = initialReactorState
33+
this.reactorState = initialReactorState
3334

3435
this.ReactMixin = createReactMixin(this)
35-
/**
36-
* The state for the whole cluster
37-
*/
38-
this.state = Immutable.Map({})
39-
/**
40-
* Holds a map of id => store instance
41-
*/
42-
this.__stores = Immutable.Map({})
43-
44-
this.__evaluator = new Evaluator()
45-
/**
46-
* Change observer interface to observe certain keypaths
47-
* Created after __initialize so it starts with initialState
48-
*/
49-
this.__changeObserver = new ChangeObserver(this.state, this.__evaluator)
5036

5137
// keep track of the depth of batch nesting
5238
this.__batchDepth = 0
@@ -63,7 +49,9 @@ class Reactor {
6349
* @return {*}
6450
*/
6551
evaluate(keyPathOrGetter) {
66-
return this.__evaluator.evaluate(this.state, keyPathOrGetter)
52+
var evaluateResult = evaluate(this.reactorState, keyPathOrGetter)
53+
this.__changed(evaluateResult.reactorState)
54+
return evaluateResult.result
6755
}
6856

6957
/**
@@ -98,7 +86,10 @@ class Reactor {
9886
} else if (KeyPath.isKeyPath(getter)) {
9987
getter = Getter.fromKeyPath(getter)
10088
}
101-
return this.__changeObserver.onChange(getter, handler)
89+
var observeResult = fns.addObserver(this.reactorState, getter, handler)
90+
this.__changed(observeResult.reactorState)
91+
92+
return observeResult.unwatchFn
10293
}
10394

10495

@@ -116,10 +107,8 @@ class Reactor {
116107
this.__isDispatching = true
117108
}
118109

119-
var prevState = this.state
120-
121110
try {
122-
this.state = this.__handleAction(prevState, actionType, payload)
111+
this.__changed(fns.dispatch(this.reactorState, actionType, payload))
123112
} catch (e) {
124113
this.__isDispatching = false
125114
throw e
@@ -128,15 +117,11 @@ class Reactor {
128117

129118
if (this.__batchDepth > 0) {
130119
this.__batchDispatchCount++
131-
} else {
132-
if (this.state !== prevState) {
133-
try {
134-
this.__notify()
135-
} catch (e) {
136-
this.__isDispatching = false
137-
throw e
138-
}
139-
}
120+
}
121+
122+
try {
123+
this.__notify()
124+
} finally {
140125
this.__isDispatching = false
141126
}
142127
}
@@ -162,30 +147,16 @@ class Reactor {
162147
/* eslint-enable no-console */
163148
var stores = {}
164149
stores[id] = store
165-
this.registerStores(stores)
150+
this.batch(() => {
151+
this.registerStores(stores)
152+
})
166153
}
167154

168155
/**
169156
* @param {Store[]} stores
170157
*/
171158
registerStores(stores) {
172-
each(stores, (store, id) => {
173-
if (this.__stores.get(id)) {
174-
/* eslint-disable no-console */
175-
console.warn('Store already defined for id = ' + id)
176-
/* eslint-enable no-console */
177-
}
178-
179-
var initialState = store.getInitialState()
180-
181-
if (this.debug && !isImmutableValue(initialState)) {
182-
throw new Error('Store getInitialState() must return an immutable value, did you forget to call toImmutable')
183-
}
184-
185-
this.__stores = this.__stores.set(id, store)
186-
this.state = this.state.set(id, initialState)
187-
})
188-
159+
this.__changed(fns.registerStores(this.reactorState, stores))
189160
this.__notify()
190161
}
191162

@@ -194,112 +165,45 @@ class Reactor {
194165
* @return {Object}
195166
*/
196167
serialize() {
197-
var serialized = {}
198-
this.__stores.forEach((store, id) => {
199-
var storeState = this.state.get(id)
200-
var serializedState = store.serialize(storeState)
201-
if (serializedState !== undefined) {
202-
serialized[id] = serializedState
203-
}
204-
})
205-
return serialized
168+
return this.reactorState.serialize()
206169
}
207170

208171
/**
209172
* @param {Object} state
210173
*/
211174
loadState(state) {
212-
var stateToLoad = toImmutable({}).withMutations(stateToLoad => {
213-
each(state, (serializedStoreState, storeId) => {
214-
var store = this.__stores.get(storeId)
215-
if (store) {
216-
var storeState = store.deserialize(serializedStoreState)
217-
if (storeState !== undefined) {
218-
stateToLoad.set(storeId, storeState)
219-
}
220-
}
221-
})
222-
})
223-
224-
this.state = this.state.merge(stateToLoad)
175+
this.__changed(fns.loadState(this.reactorState, state))
225176
this.__notify()
226177
}
227178

228179
/**
229180
* Resets the state of a reactor and returns back to initial state
230181
*/
231182
reset() {
232-
var debug = this.debug
233-
var prevState = this.state
234-
235-
this.state = Immutable.Map().withMutations(state => {
236-
this.__stores.forEach((store, id) => {
237-
var storeState = prevState.get(id)
238-
var resetStoreState = store.handleReset(storeState)
239-
if (debug && resetStoreState === undefined) {
240-
throw new Error('Store handleReset() must return a value, did you forget a return statement')
241-
}
242-
if (debug && !isImmutableValue(resetStoreState)) {
243-
throw new Error('Store reset state must be an immutable value, did you forget to call toImmutable')
244-
}
245-
state.set(id, resetStoreState)
246-
})
247-
})
248-
249-
this.__evaluator.reset()
250-
this.__changeObserver.reset(this.state)
183+
var newState = fns.reset(this.reactorState)
184+
this.reactorState = newState
185+
this.prevReactorState = newState
251186
}
252187

253188
/**
254189
* Notifies all change observers with the current state
255190
* @private
256191
*/
257192
__notify() {
258-
this.__changeObserver.notifyObservers(this.state)
193+
if (this.__batchDepth <= 0) {
194+
// side-effects of notify all observers
195+
var nextReactorState = fns.notify(this.prevReactorState, this.reactorState)
196+
197+
this.prevReactorState = nextReactorState
198+
this.reactorState = nextReactorState
199+
}
259200
}
260201

261202
/**
262-
* Reduces the current state to the new state given actionType / message
263-
* @param {string} actionType
264-
* @param {object|undefined} payload
265-
* @return {Immutable.Map}
203+
* @param {ReactorState} reactorState
266204
*/
267-
__handleAction(state, actionType, payload) {
268-
return state.withMutations(state => {
269-
if (this.debug) {
270-
logging.dispatchStart(actionType, payload)
271-
}
272-
273-
// let each store handle the message
274-
this.__stores.forEach((store, id) => {
275-
var currState = state.get(id)
276-
var newState
277-
278-
try {
279-
newState = store.handle(currState, actionType, payload)
280-
} catch(e) {
281-
// ensure console.group is properly closed
282-
logging.dispatchError(e.message)
283-
throw e
284-
}
285-
286-
if (this.debug && newState === undefined) {
287-
var errorMsg = 'Store handler must return a value, did you forget a return statement'
288-
logging.dispatchError(errorMsg)
289-
throw new Error(errorMsg)
290-
}
291-
292-
state.set(id, newState)
293-
294-
if (this.debug) {
295-
logging.storeHandled(id, currState, newState)
296-
}
297-
})
298-
299-
if (this.debug) {
300-
logging.dispatchEnd(state)
301-
}
302-
})
205+
__changed(reactorState) {
206+
this.reactorState = reactorState
303207
}
304208

305209
__batchStart() {

0 commit comments

Comments
 (0)