1
1
import isPlainObject from 'lodash/isPlainObject'
2
+ import warning from './utils/warning'
2
3
import $$observable from 'symbol-observable'
3
4
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
5
export var ActionTypes = {
11
6
INIT : '@@redux/INIT'
12
7
}
13
8
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
9
+ function createBasicStore ( reducer , initialState , onChange ) {
10
+ var currentState = initialState
11
+ var isDispatching = false
12
+
13
+ function getState ( ) {
14
+ return currentState
43
15
}
44
16
45
- if ( typeof enhancer !== 'undefined' ) {
46
- if ( typeof enhancer !== 'function' ) {
47
- throw new Error ( 'Expected the enhancer to be a function.' )
17
+ function dispatch ( action ) {
18
+ if ( ! isPlainObject ( action ) ) {
19
+ throw new Error (
20
+ 'Actions must be plain objects. ' +
21
+ 'Use custom middleware for async actions.'
22
+ )
23
+ }
24
+ if ( typeof action . type === 'undefined' ) {
25
+ throw new Error (
26
+ 'Actions may not have an undefined "type" property. ' +
27
+ 'Have you misspelled a constant?'
28
+ )
29
+ }
30
+ if ( isDispatching ) {
31
+ throw new Error ( 'Reducers may not dispatch actions.' )
48
32
}
33
+ try {
34
+ isDispatching = true
35
+ currentState = reducer ( currentState , action )
36
+ } finally {
37
+ isDispatching = false
38
+ }
39
+ onChange ( )
40
+ return action
41
+ }
49
42
50
- return enhancer ( createStore ) ( reducer , initialState )
43
+ return {
44
+ dispatch,
45
+ getState,
51
46
}
47
+ }
52
48
49
+ export default function createEnhancedStore ( reducer , initialState , enhancer ) {
53
50
if ( typeof reducer !== 'function' ) {
54
51
throw new Error ( 'Expected the reducer to be a function.' )
55
52
}
53
+ if ( typeof initialState === 'function' && typeof enhancer === 'undefined' ) {
54
+ enhancer = initialState
55
+ initialState = undefined
56
+ }
57
+ var createStore = createBasicStore
58
+ if ( typeof enhancer !== 'undefined' ) {
59
+ if ( typeof enhancer !== 'function' ) {
60
+ throw new Error ( 'Expected the enhancer to be a function.' )
61
+ }
62
+ createStore = enhancer ( createBasicStore )
63
+ }
56
64
57
- var currentReducer = reducer
58
- var currentState = initialState
65
+ var store
59
66
var currentListeners = [ ]
60
67
var nextListeners = currentListeners
61
- var isDispatching = false
62
68
63
69
function ensureCanMutateNextListeners ( ) {
64
70
if ( nextListeners === currentListeners ) {
65
71
nextListeners = currentListeners . slice ( )
66
72
}
67
73
}
68
-
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
74
function subscribe ( listener ) {
102
75
if ( typeof listener !== 'function' ) {
103
76
throw new Error ( 'Expected listener to be a function.' )
104
77
}
105
-
106
78
var isSubscribed = true
107
-
108
79
ensureCanMutateNextListeners ( )
109
80
nextListeners . push ( listener )
110
-
111
81
return function unsubscribe ( ) {
112
82
if ( ! isSubscribed ) {
113
83
return
114
84
}
115
-
116
85
isSubscribed = false
117
-
118
86
ensureCanMutateNextListeners ( )
119
87
var index = nextListeners . indexOf ( listener )
120
88
nextListeners . splice ( index , 1 )
121
89
}
122
90
}
123
91
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
92
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
- }
93
+ return store . dispatch ( action )
94
+ }
174
95
96
+ function onChange ( ) {
175
97
var listeners = currentListeners = nextListeners
176
98
for ( var i = 0 ; i < listeners . length ; i ++ ) {
177
99
listeners [ i ] ( )
178
100
}
179
-
180
- return action
181
101
}
182
102
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
- function replaceReducer ( nextReducer ) {
194
- if ( typeof nextReducer !== 'function' ) {
195
- throw new Error ( 'Expected the nextReducer to be a function.' )
196
- }
197
-
198
- currentReducer = nextReducer
199
- dispatch ( { type : ActionTypes . INIT } )
103
+ function getState ( ) {
104
+ return store . getState ( )
200
105
}
201
106
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
107
function observable ( ) {
209
- var outerSubscribe = subscribe
210
108
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
109
subscribe ( observer ) {
220
110
if ( typeof observer !== 'object' ) {
221
111
throw new TypeError ( 'Expected the observer to be an object.' )
222
112
}
223
-
224
113
function observeState ( ) {
225
114
if ( observer . next ) {
226
115
observer . next ( getState ( ) )
227
116
}
228
117
}
229
-
230
118
observeState ( )
231
- var unsubscribe = outerSubscribe ( observeState )
119
+ var unsubscribe = subscribe ( observeState )
232
120
return { unsubscribe }
233
121
} ,
234
122
@@ -238,16 +126,22 @@ export default function createStore(reducer, initialState, enhancer) {
238
126
}
239
127
}
240
128
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 } )
129
+ function replaceReducer ( nextReducer ) {
130
+ if ( typeof nextReducer !== 'function' ) {
131
+ throw new Error ( 'Expected the nextReducer to be a function.' )
132
+ }
133
+
134
+ store = createStore ( nextReducer , store ? getState ( ) : initialState , onChange )
135
+ dispatch ( { type : ActionTypes . INIT } )
136
+ }
137
+
138
+ replaceReducer ( reducer )
245
139
246
140
return {
247
141
dispatch,
248
- subscribe,
249
142
getState,
143
+ subscribe,
250
144
replaceReducer,
251
145
[ $$observable ] : observable
252
146
}
253
- }
147
+ }
0 commit comments