1
1
import isPlainObject from 'lodash/isPlainObject'
2
2
import $$observable from 'symbol-observable'
3
3
4
- /**
5
- * These are private action types reserved by Redux.
6
- * For any unknown actions, you must return the current state.
7
- * If the current state is undefined, you must return the initial state.
8
- * Do not reference these action types directly in your code.
9
- */
10
4
export var ActionTypes = {
11
5
INIT : '@@redux/INIT'
12
6
}
13
7
14
- /**
15
- * Creates a Redux store that holds the state tree.
16
- * The only way to change the data in the store is to call `dispatch()` on it.
17
- *
18
- * There should only be a single store in your app. To specify how different
19
- * parts of the state tree respond to actions, you may combine several reducers
20
- * into a single reducer function by using `combineReducers`.
21
- *
22
- * @param {Function } reducer A function that returns the next state tree, given
23
- * the current state tree and the action to handle.
24
- *
25
- * @param {any } [initialState] The initial state. You may optionally specify it
26
- * to hydrate the state from the server in universal apps, or to restore a
27
- * previously serialized user session.
28
- * If you use `combineReducers` to produce the root reducer function, this must be
29
- * an object with the same shape as `combineReducers` keys.
30
- *
31
- * @param {Function } enhancer The store enhancer. You may optionally specify it
32
- * to enhance the store with third-party capabilities such as middleware,
33
- * time travel, persistence, etc. The only store enhancer that ships with Redux
34
- * is `applyMiddleware()`.
35
- *
36
- * @returns {Store } A Redux store that lets you read the state, dispatch actions
37
- * and subscribe to changes.
38
- */
39
- export default function createStore ( reducer , initialState , enhancer ) {
40
- if ( typeof initialState === 'function' && typeof enhancer === 'undefined' ) {
41
- enhancer = initialState
42
- initialState = undefined
8
+ function createStoreBase ( reducer , initialState , onChange ) {
9
+ var currentState = initialState
10
+ var isDispatching = false
11
+
12
+ function getState ( ) {
13
+ return currentState
43
14
}
44
15
45
- if ( typeof enhancer !== 'undefined' ) {
46
- if ( typeof enhancer !== 'function' ) {
47
- throw new Error ( 'Expected the enhancer to be a function.' )
16
+ function dispatch ( action ) {
17
+ if ( ! isPlainObject ( action ) ) {
18
+ throw new Error (
19
+ 'Actions must be plain objects. ' +
20
+ 'Use custom middleware for async actions.'
21
+ )
22
+ }
23
+ if ( typeof action . type === 'undefined' ) {
24
+ throw new Error (
25
+ 'Actions may not have an undefined "type" property. ' +
26
+ 'Have you misspelled a constant?'
27
+ )
28
+ }
29
+ if ( isDispatching ) {
30
+ throw new Error ( 'Reducers may not dispatch actions.' )
31
+ }
32
+
33
+ try {
34
+ isDispatching = true
35
+ currentState = reducer ( currentState , action )
36
+ } finally {
37
+ isDispatching = false
48
38
}
49
39
50
- return enhancer ( createStore ) ( reducer , initialState )
40
+ onChange ( )
41
+ return action
51
42
}
52
43
44
+ return {
45
+ dispatch,
46
+ getState
47
+ }
48
+ }
49
+
50
+ export default function createStore ( reducer , initialState , enhancer ) {
53
51
if ( typeof reducer !== 'function' ) {
54
52
throw new Error ( 'Expected the reducer to be a function.' )
55
53
}
54
+ if ( typeof initialState === 'function' && typeof enhancer === 'undefined' ) {
55
+ enhancer = initialState
56
+ initialState = undefined
57
+ }
58
+ if ( typeof enhancer !== 'undefined' && typeof enhancer !== 'function' ) {
59
+ throw new Error ( 'Expected the enhancer to be a function.' )
60
+ }
56
61
57
- var currentReducer = reducer
58
- var currentState = initialState
62
+ enhancer = enhancer || ( x => x )
63
+ var createFinalStoreBase = enhancer ( createStoreBase )
64
+
65
+ var storeBase
59
66
var currentListeners = [ ]
60
67
var nextListeners = currentListeners
61
- var isDispatching = false
68
+
69
+ function onChange ( ) {
70
+ var listeners = currentListeners = nextListeners
71
+ for ( var i = 0 ; i < listeners . length ; i ++ ) {
72
+ listeners [ i ] ( )
73
+ }
74
+ }
62
75
63
76
function ensureCanMutateNextListeners ( ) {
64
77
if ( nextListeners === currentListeners ) {
65
78
nextListeners = currentListeners . slice ( )
66
79
}
67
80
}
68
81
69
- /**
70
- * Reads the state tree managed by the store.
71
- *
72
- * @returns {any } The current state tree of your application.
73
- */
74
- function getState ( ) {
75
- return currentState
76
- }
77
-
78
- /**
79
- * Adds a change listener. It will be called any time an action is dispatched,
80
- * and some part of the state tree may potentially have changed. You may then
81
- * call `getState()` to read the current state tree inside the callback.
82
- *
83
- * You may call `dispatch()` from a change listener, with the following
84
- * caveats:
85
- *
86
- * 1. The subscriptions are snapshotted just before every `dispatch()` call.
87
- * If you subscribe or unsubscribe while the listeners are being invoked, this
88
- * will not have any effect on the `dispatch()` that is currently in progress.
89
- * However, the next `dispatch()` call, whether nested or not, will use a more
90
- * recent snapshot of the subscription list.
91
- *
92
- * 2. The listener should not expect to see all state changes, as the state
93
- * might have been updated multiple times during a nested `dispatch()` before
94
- * the listener is called. It is, however, guaranteed that all subscribers
95
- * registered before the `dispatch()` started will be called with the latest
96
- * state by the time it exits.
97
- *
98
- * @param {Function } listener A callback to be invoked on every dispatch.
99
- * @returns {Function } A function to remove this change listener.
100
- */
101
82
function subscribe ( listener ) {
102
83
if ( typeof listener !== 'function' ) {
103
84
throw new Error ( 'Expected listener to be a function.' )
104
85
}
105
86
106
87
var isSubscribed = true
107
-
108
88
ensureCanMutateNextListeners ( )
109
89
nextListeners . push ( listener )
110
90
@@ -114,121 +94,43 @@ export default function createStore(reducer, initialState, enhancer) {
114
94
}
115
95
116
96
isSubscribed = false
117
-
118
97
ensureCanMutateNextListeners ( )
119
98
var index = nextListeners . indexOf ( listener )
120
99
nextListeners . splice ( index , 1 )
121
100
}
122
101
}
123
102
124
- /**
125
- * Dispatches an action. It is the only way to trigger a state change.
126
- *
127
- * The `reducer` function, used to create the store, will be called with the
128
- * current state tree and the given `action`. Its return value will
129
- * be considered the **next** state of the tree, and the change listeners
130
- * will be notified.
131
- *
132
- * The base implementation only supports plain object actions. If you want to
133
- * dispatch a Promise, an Observable, a thunk, or something else, you need to
134
- * wrap your store creating function into the corresponding middleware. For
135
- * example, see the documentation for the `redux-thunk` package. Even the
136
- * middleware will eventually dispatch plain object actions using this method.
137
- *
138
- * @param {Object } action A plain object representing “what changed”. It is
139
- * a good idea to keep actions serializable so you can record and replay user
140
- * sessions, or use the time travelling `redux-devtools`. An action must have
141
- * a `type` property which may not be `undefined`. It is a good idea to use
142
- * string constants for action types.
143
- *
144
- * @returns {Object } For convenience, the same action object you dispatched.
145
- *
146
- * Note that, if you use a custom middleware, it may wrap `dispatch()` to
147
- * return something else (for example, a Promise you can await).
148
- */
149
103
function dispatch ( action ) {
150
- if ( ! isPlainObject ( action ) ) {
151
- throw new Error (
152
- 'Actions must be plain objects. ' +
153
- 'Use custom middleware for async actions.'
154
- )
155
- }
156
-
157
- if ( typeof action . type === 'undefined' ) {
158
- throw new Error (
159
- 'Actions may not have an undefined "type" property. ' +
160
- 'Have you misspelled a constant?'
161
- )
162
- }
163
-
164
- if ( isDispatching ) {
165
- throw new Error ( 'Reducers may not dispatch actions.' )
166
- }
167
-
168
- try {
169
- isDispatching = true
170
- currentState = currentReducer ( currentState , action )
171
- } finally {
172
- isDispatching = false
173
- }
174
-
175
- var listeners = currentListeners = nextListeners
176
- for ( var i = 0 ; i < listeners . length ; i ++ ) {
177
- listeners [ i ] ( )
178
- }
104
+ return storeBase . dispatch ( action )
105
+ }
179
106
180
- return action
107
+ function getState ( ) {
108
+ return storeBase . getState ( )
181
109
}
182
110
183
- /**
184
- * Replaces the reducer currently used by the store to calculate the state.
185
- *
186
- * You might need this if your app implements code splitting and you want to
187
- * load some of the reducers dynamically. You might also need this if you
188
- * implement a hot reloading mechanism for Redux.
189
- *
190
- * @param {Function } nextReducer The reducer for the store to use instead.
191
- * @returns {void }
192
- */
193
111
function replaceReducer ( nextReducer ) {
194
112
if ( typeof nextReducer !== 'function' ) {
195
113
throw new Error ( 'Expected the nextReducer to be a function.' )
196
114
}
197
115
198
- currentReducer = nextReducer
116
+ var nextInitialState = storeBase ? getState ( ) : initialState
117
+ storeBase = createFinalStoreBase ( nextReducer , nextInitialState , onChange )
199
118
dispatch ( { type : ActionTypes . INIT } )
200
119
}
201
120
202
- /**
203
- * Interoperability point for observable/reactive libraries.
204
- * @returns {observable } A minimal observable of state changes.
205
- * For more information, see the observable proposal:
206
- * https://github.com/zenparsing/es-observable
207
- */
208
121
function observable ( ) {
209
- var outerSubscribe = subscribe
210
122
return {
211
- /**
212
- * The minimal observable subscription method.
213
- * @param {Object } observer Any object that can be used as an observer.
214
- * The observer object should have a `next` method.
215
- * @returns {subscription } An object with an `unsubscribe` method that can
216
- * be used to unsubscribe the observable from the store, and prevent further
217
- * emission of values from the observable.
218
- */
219
123
subscribe ( observer ) {
220
124
if ( typeof observer !== 'object' ) {
221
125
throw new TypeError ( 'Expected the observer to be an object.' )
222
126
}
223
-
224
127
function observeState ( ) {
225
128
if ( observer . next ) {
226
129
observer . next ( getState ( ) )
227
130
}
228
131
}
229
-
230
132
observeState ( )
231
- var unsubscribe = outerSubscribe ( observeState )
133
+ var unsubscribe = subscribe ( observeState )
232
134
return { unsubscribe }
233
135
} ,
234
136
@@ -238,15 +140,12 @@ export default function createStore(reducer, initialState, enhancer) {
238
140
}
239
141
}
240
142
241
- // When a store is created, an "INIT" action is dispatched so that every
242
- // reducer returns their initial state. This effectively populates
243
- // the initial state tree.
244
- dispatch ( { type : ActionTypes . INIT } )
143
+ replaceReducer ( reducer )
245
144
246
145
return {
247
146
dispatch,
248
- subscribe,
249
147
getState,
148
+ subscribe,
250
149
replaceReducer,
251
150
[ $$observable ] : observable
252
151
}
0 commit comments