if ( (typeof preloadedState === 'function' && typeof enhancer === 'function') || (typeof enhancer === 'function' && typeofarguments[3] === 'function') ) { thrownewError( 'It looks like you are passing several store enhancers to ' + 'createStore(). This is not supported. Instead, compose them ' + 'together to a single function. See https://redux.js.org/tutorials/fundamentals/part-4-store#creating-a-store-with-enhancers for an example.' ) }
if (typeof reducer !== 'function') { thrownewError( `Expected the root reducer to be a function. Instead, received: '${kindOf( reducer )}'` ) }
几个内部变量:
1 2 3 4 5
let currentReducer = reducer let currentState = preloadedState as S letcurrentListeners: (() =>void)[] | null = [] let nextListeners = currentListeners let isDispatching = false
内部工具函数
getState
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/** * Reads the state tree managed by the store. * * @returns The current state tree of your application. */ functiongetState(): S { if (isDispatching) { thrownewError( 'You may not call store.getState() while the reducer is executing. ' + 'The reducer has already received the state as an argument. ' + 'Pass it down from the top reducer instead of reading it from the store.' ) }
/** * This makes a shallow copy of currentListeners so we can use * nextListeners as a temporary list while dispatching. * * This prevents any bugs around consumers calling * subscribe/unsubscribe in the middle of a dispatch. */ functionensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } }
/** * Adds a change listener. It will be called any time an action is dispatched, * and some part of the state tree may potentially have changed. You may then * call `getState()` to read the current state tree inside the callback. * * You may call `dispatch()` from a change listener, with the following * caveats: * * 1. The subscriptions are snapshotted just before every `dispatch()` call. * If you subscribe or unsubscribe while the listeners are being invoked, this * will not have any effect on the `dispatch()` that is currently in progress. * However, the next `dispatch()` call, whether nested or not, will use a more * recent snapshot of the subscription list. * * 2. The listener should not expect to see all state changes, as the state * might have been updated multiple times during a nested `dispatch()` before * the listener is called. It is, however, guaranteed that all subscribers * registered before the `dispatch()` started will be called with the latest * state by the time it exits. * * @param listener A callback to be invoked on every dispatch. * @returns A function to remove this change listener. */ functionsubscribe(listener: () => void) { if (typeof listener !== 'function') { thrownewError( `Expected the listener to be a function. Instead, received: '${kindOf( listener )}'` ) }
if (isDispatching) { thrownewError( 'You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api/store#subscribelistener for more details.' ) }
returnfunctionunsubscribe() { if (!isSubscribed) { return }
if (isDispatching) { thrownewError( 'You may not unsubscribe from a store listener while the reducer is executing. ' + 'See https://redux.js.org/api/store#subscribelistener for more details.' ) }
/** * Dispatches an action. It is the only way to trigger a state change. * * The `reducer` function, used to create the store, will be called with the * current state tree and the given `action`. Its return value will * be considered the **next** state of the tree, and the change listeners * will be notified. * * The base implementation only supports plain object actions. If you want to * dispatch a Promise, an Observable, a thunk, or something else, you need to * wrap your store creating function into the corresponding middleware. For * example, see the documentation for the `redux-thunk` package. Even the * middleware will eventually dispatch plain object actions using this method. * * @param action A plain object representing “what changed”. It is * a good idea to keep actions serializable so you can record and replay user * sessions, or use the time travelling `redux-devtools`. An action must have * a `type` property which may not be `undefined`. It is a good idea to use * string constants for action types. * * @returns For convenience, the same action object you dispatched. * * Note that, if you use a custom middleware, it may wrap `dispatch()` to * return something else (for example, a Promise you can await). */ functiondispatch(action: A) { if (!isPlainObject(action)) { thrownewError( `Actions must be plain objects. Instead, the actual type was: '${kindOf( action )}'. You may need to add middleware to your store setup to handle dispatching other values, such as 'redux-thunk' to handle dispatching functions. See https://redux.js.org/tutorials/fundamentals/part-4-store#middleware and https://redux.js.org/tutorials/fundamentals/part-6-async-logic#using-the-redux-thunk-middleware for examples.` ) }
if (typeof action.type === 'undefined') { thrownewError( 'Actions may not have an undefined "type" property. You may have misspelled an action type string constant.' ) }
if (isDispatching) { thrownewError('Reducers may not dispatch actions.') }
/** * Replaces the reducer currently used by the store to calculate the state. * * You might need this if your app implements code splitting and you want to * load some of the reducers dynamically. You might also need this if you * implement a hot reloading mechanism for Redux. * * @param nextReducer The reducer for the store to use instead. * @returns The same store instance with a new reducer in place. */ function replaceReducer<NewState, NewActionsextends A>( nextReducer: Reducer<NewState, NewActions> ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext { if (typeof nextReducer !== 'function') { thrownewError( `Expected the nextReducer to be a function. Instead, received: '${kindOf( nextReducer )}` ) }
// TODO: do this more elegantly ;(currentReducer asunknownasReducer<NewState, NewActions>) = nextReducer
// This action has a similar effect to ActionTypes.INIT. // Any reducers that existed in both the new and old rootReducer // will receive the previous state. This effectively populates // the new state tree with any relevant data from the old one. dispatch({ type: ActionTypes.REPLACE } as A) // change the type of the store by casting it to the new store return store asunknownasStore< ExtendState<NewState, StateExt>, NewActions, StateExt, Ext > & Ext }
/** * Interoperability point for observable/reactive libraries. * @returns A minimal observable of state changes. * For more information, see the observable proposal: * https://github.com/tc39/proposal-observable */ functionobservable() { const outerSubscribe = subscribe return { /** * The minimal observable subscription method. * @param observer Any object that can be used as an observer. * The observer object should have a `next` method. * @returns An object with an `unsubscribe` method that can * be used to unsubscribe the observable from the store, and prevent further * emission of values from the observable. */ subscribe(observer: unknown) { if (typeof observer !== 'object' || observer === null) { thrownewTypeError( `Expected the observer to be an object. Instead, received: '${kindOf( observer )}'` ) }
// When a store is created, an "INIT" action is dispatched so that every // reducer returns their initial state. This effectively populates // the initial state tree. dispatch({ type: ActionTypes.INIT } as A)
const store = { dispatch: dispatch asDispatch<A>, subscribe, getState, replaceReducer, [$$observable]: observable } asunknownasStore<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext return store
所以说,确实如文档所说,store中的 state 和 action 就是一个纯对象,只不过这个纯对象有些规范,你可以不遵循这个规范,但是不遵循这个规范就没法正常运转。
compose
这个文件中只有一个方法,也很简单,就是把一串函数变成一个函数A,A其实就是依次执行这一串函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
exportdefaultfunctioncompose(...funcs: Function[]) { if (funcs.length === 0) { // infer the argument type so it is usable in inference down the line return <T>(arg: T) => arg }
/** * Turns an object whose values are different reducer functions, into a single * reducer function. It will call every child reducer, and gather their results * into a single state object, whose keys correspond to the keys of the passed * reducer functions. * * @template S Combined state object type. * * @param reducers An object whose values correspond to different reducer * functions that need to be combined into one. One handy way to obtain it * is to use ES6 `import * as reducers` syntax. The reducers may never * return undefined for any action. Instead, they should return their * initial state if the state passed to them was undefined, and the current * state for any unrecognized action. * * @returns A reducer function that invokes every reducer inside the passed * object, and builds a state object with the same shape. */ exportdefaultfunctioncombineReducers(reducers: ReducersMapObject) { const reducerKeys = Object.keys(reducers) constfinalReducers: ReducersMapObject = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') { if (typeof reducers[key] === 'undefined') { warning(`No reducer provided for key "${key}"`) } }
// This is used to make sure we don't warn about the same // keys multiple times. letunexpectedKeyCache: { [key: string]: true } if (process.env.NODE_ENV !== 'production') { unexpectedKeyCache = {} }
exportdefaultfunctionapplyMiddleware( ...middlewares: Middleware[] ): StoreEnhancer<any> { return(createStore: StoreEnhancerStoreCreator) => <S, A extendsAnyAction>( reducer: Reducer<S, A>, preloadedState?: PreloadedState<S> ) => { const store = createStore(reducer, preloadedState) letdispatch: Dispatch = () => { thrownewError( 'Dispatching while constructing your middleware is not allowed. ' + 'Other middleware would not be applied to this dispatch.' ) }