Created
January 22, 2020 17:11
-
-
Save r3dm1ke/ed6453237945a888bf12642f586a51b6 to your computer and use it in GitHub Desktop.
createStore from Redux (simplified)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import $$observable from './utils/symbol-observable' | |
| import { | |
| Store, | |
| PreloadedState, | |
| StoreEnhancer, | |
| Dispatch, | |
| Observer, | |
| ExtendState | |
| } from './types/store' | |
| import { Action } from './types/actions' | |
| import { Reducer } from './types/reducers' | |
| import ActionTypes from './utils/actionTypes' | |
| import isPlainObject from './utils/isPlainObject' | |
| export default function createStore< | |
| S, | |
| A extends Action, | |
| Ext = {}, | |
| StateExt = never | |
| >( | |
| reducer: Reducer<S, A>, | |
| preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>, | |
| enhancer?: StoreEnhancer<Ext, StateExt> | |
| ): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext { | |
| if ( | |
| (typeof preloadedState === 'function' && typeof enhancer === 'function') || | |
| (typeof enhancer === 'function' && typeof arguments[3] === 'function') | |
| ) { | |
| throw new Error( | |
| 'It looks like you are passing several store enhancers to ' + | |
| 'createStore(). This is not supported. Instead, compose them ' + | |
| 'together to a single function.' | |
| ) | |
| } | |
| if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { | |
| enhancer = preloadedState as StoreEnhancer<Ext, StateExt> | |
| preloadedState = undefined | |
| } | |
| if (typeof enhancer !== 'undefined') { | |
| if (typeof enhancer !== 'function') { | |
| throw new Error('Expected the enhancer to be a function.') | |
| } | |
| return enhancer(createStore)(reducer, preloadedState as PreloadedState< | |
| S | |
| >) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext | |
| } | |
| if (typeof reducer !== 'function') { | |
| throw new Error('Expected the reducer to be a function.') | |
| } | |
| let currentReducer = reducer | |
| let currentState = preloadedState as S | |
| let currentListeners: (() => void)[] | null = [] | |
| let nextListeners = currentListeners | |
| let isDispatching = false | |
| /** | |
| * 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. | |
| */ | |
| function ensureCanMutateNextListeners() { | |
| if (nextListeners === currentListeners) { | |
| nextListeners = currentListeners.slice() | |
| } | |
| } | |
| /** | |
| * Reads the state tree managed by the store. | |
| * | |
| * @returns The current state tree of your application. | |
| */ | |
| function getState(): S { | |
| if (isDispatching) { | |
| throw new Error( | |
| '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.' | |
| ) | |
| } | |
| return currentState as S | |
| } | |
| /** | |
| * 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. | |
| */ | |
| function subscribe(listener: () => void) { | |
| if (typeof listener !== 'function') { | |
| throw new Error('Expected the listener to be a function.') | |
| } | |
| if (isDispatching) { | |
| throw new Error( | |
| '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-reference/store#subscribelistener for more details.' | |
| ) | |
| } | |
| let isSubscribed = true | |
| ensureCanMutateNextListeners() | |
| nextListeners.push(listener) | |
| return function unsubscribe() { | |
| if (!isSubscribed) { | |
| return | |
| } | |
| if (isDispatching) { | |
| throw new Error( | |
| 'You may not unsubscribe from a store listener while the reducer is executing. ' + | |
| 'See https://redux.js.org/api-reference/store#subscribelistener for more details.' | |
| ) | |
| } | |
| isSubscribed = false | |
| ensureCanMutateNextListeners() | |
| const index = nextListeners.indexOf(listener) | |
| nextListeners.splice(index, 1) | |
| currentListeners = null | |
| } | |
| } | |
| /** | |
| * 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). | |
| */ | |
| function dispatch(action: A) { | |
| if (!isPlainObject(action)) { | |
| throw new Error( | |
| 'Actions must be plain objects. ' + | |
| 'Use custom middleware for async actions.' | |
| ) | |
| } | |
| if (typeof action.type === 'undefined') { | |
| throw new Error( | |
| 'Actions may not have an undefined "type" property. ' + | |
| 'Have you misspelled a constant?' | |
| ) | |
| } | |
| if (isDispatching) { | |
| throw new Error('Reducers may not dispatch actions.') | |
| } | |
| try { | |
| isDispatching = true | |
| currentState = currentReducer(currentState, action) | |
| } finally { | |
| isDispatching = false | |
| } | |
| const listeners = (currentListeners = nextListeners) | |
| for (let i = 0; i < listeners.length; i++) { | |
| const listener = listeners[i] | |
| listener() | |
| } | |
| return action | |
| } | |
| // 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 as Dispatch<A>, | |
| subscribe, | |
| getState, | |
| } as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext | |
| return store | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment