Skip to content

Commit

Permalink
convert createStore to typescript (#3533)
Browse files Browse the repository at this point in the history
  • Loading branch information
cellog authored and timdorr committed Aug 29, 2019
1 parent b0e8219 commit 9b70f1c
Showing 1 changed file with 50 additions and 20 deletions.
70 changes: 50 additions & 20 deletions src/createStore.js → src/createStore.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
import $$observable from 'symbol-observable'

import {
Store,
Action,
Reducer,
PreloadedState,
StoreEnhancer,
Dispatch,
Observer
} from '..'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'

Expand Down Expand Up @@ -28,7 +37,20 @@ import isPlainObject from './utils/isPlainObject'
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
*/
export default function createStore(reducer, preloadedState, enhancer) {
export default function createStore<S, A extends Action, Ext, StateExt>(
reducer: Reducer<S, A>,
enhancer?: StoreEnhancer<Ext, StateExt>
): Store<S & StateExt, A> & Ext
export default function createStore<S, A extends Action, Ext, StateExt>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S>,
enhancer?: StoreEnhancer<Ext>
): Store<S & StateExt, A> & Ext
export default function createStore<S, A extends Action, Ext, StateExt>(
reducer: Reducer<S, A>,
preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
enhancer?: StoreEnhancer<Ext>
) {
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
Expand All @@ -41,7 +63,7 @@ export default function createStore(reducer, preloadedState, enhancer) {
}

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
enhancer = preloadedState as StoreEnhancer<Ext, StateExt>
preloadedState = undefined
}

Expand All @@ -50,16 +72,16 @@ export default function createStore(reducer, preloadedState, enhancer) {
throw new Error('Expected the enhancer to be a function.')
}

return enhancer(createStore)(reducer, preloadedState)
return enhancer(createStore)(reducer, preloadedState as PreloadedState<S>)
}

if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}

let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let currentState = preloadedState as S
let currentListeners: (() => void)[] | null = []
let nextListeners = currentListeners
let isDispatching = false

Expand All @@ -81,7 +103,7 @@ export default function createStore(reducer, preloadedState, enhancer) {
*
* @returns {any} The current state tree of your application.
*/
function getState() {
function getState(): S {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
Expand All @@ -90,7 +112,7 @@ export default function createStore(reducer, preloadedState, enhancer) {
)
}

return currentState
return currentState as S
}

/**
Expand All @@ -116,7 +138,7 @@ export default function createStore(reducer, preloadedState, enhancer) {
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
function subscribe(listener: () => void) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
Expand Down Expand Up @@ -181,7 +203,7 @@ export default function createStore(reducer, preloadedState, enhancer) {
* 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) {
function dispatch(action: A) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
Expand Down Expand Up @@ -226,19 +248,26 @@ export default function createStore(reducer, preloadedState, enhancer) {
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {Store} The same store instance with a new reducer in place.
*/
function replaceReducer(nextReducer) {
function replaceReducer<NewState, NewActions extends A>(
nextReducer: Reducer<NewState, NewActions>
): Store<NewState, NewActions> {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}

currentReducer = nextReducer
// TODO: do this more elegantly
;((currentReducer as unknown) as Reducer<
NewState,
NewActions
>) = nextReducer

// This action has a similiar 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 })
return store
dispatch({ type: ActionTypes.REPLACE } as A)
// change the type of the store by casting it to the new store
return (store as unknown) as Store<NewState, NewActions>
}

/**
Expand All @@ -258,14 +287,15 @@ export default function createStore(reducer, preloadedState, enhancer) {
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe(observer) {
subscribe(observer: unknown) {
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}

function observeState() {
if (observer.next) {
observer.next(getState())
const observerAsObserver = observer as Observer<S>
if (observerAsObserver.next) {
observerAsObserver.next(getState())
}
}

Expand All @@ -283,14 +313,14 @@ export default function createStore(reducer, preloadedState, enhancer) {
// 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 })
dispatch({ type: ActionTypes.INIT } as A)

const store = {
dispatch,
const store: Store<S, A> = ({
dispatch: dispatch as Dispatch<A>,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
} as unknown) as Store<S, A>
return store
}

0 comments on commit 9b70f1c

Please sign in to comment.