Skip to content

Convert all the things to Typescript #3520

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions src/applyMiddleware.js → src/applyMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
import compose from './compose'
import {
Middleware,
StoreEnhancer,
StoreCreator,
Dispatch,
AnyAction
} from '..'

/**
* Creates a store enhancer that applies middleware to the dispatch method
Expand All @@ -16,10 +23,12 @@ import compose from './compose'
* @param {...Function} middlewares The middleware chain to be applied.
* @returns {Function} A store enhancer applying the middleware.
*/
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
export default function applyMiddleware(
...middlewares: Middleware[]
): StoreEnhancer {
return (createStore: StoreCreator) => (reducer: any, ...args) => {
const store = createStore(reducer, ...args)
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
Expand All @@ -28,7 +37,8 @@ export default function applyMiddleware(...middlewares) {

const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
dispatch: (action: AnyAction, ...args: any[]) =>
(dispatch as any)(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
Expand Down
24 changes: 16 additions & 8 deletions src/bindActionCreators.js → src/bindActionCreators.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
function bindActionCreator(actionCreator, dispatch) {
return function() {
return dispatch(actionCreator.apply(this, arguments))
import { AnyAction, ActionCreator, Dispatch, ActionCreatorsMapObject } from '..'

function bindActionCreator<A extends AnyAction = AnyAction>(
actionCreator: ActionCreator<A>,
dispatch: Dispatch
) {
return function(this: any) {
return dispatch(actionCreator.apply(this, (arguments as unknown) as any[]))
}
}

Expand All @@ -13,19 +18,22 @@ function bindActionCreator(actionCreator, dispatch) {
* For convenience, you can also pass an action creator as the first argument,
* and get a dispatch wrapped function in return.
*
* @param {Function|Object} actionCreators An object whose values are action
* @param actionCreators An object whose values are action
* creator functions. One handy way to obtain it is to use ES6 `import * as`
* syntax. You may also pass a single function.
*
* @param {Function} dispatch The `dispatch` function available on your Redux
* @param dispatch The `dispatch` function available on your Redux
* store.
*
* @returns {Function|Object} The object mimicking the original object, but with
* @returns The object mimicking the original object, but with
* every action creator wrapped into the `dispatch` call. If you passed a
* function as `actionCreators`, the return value will also be a single
* function.
*/
export default function bindActionCreators(actionCreators, dispatch) {
export default function bindActionCreators(
actionCreators: ActionCreator<any> | ActionCreatorsMapObject,
dispatch: Dispatch
) {
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
Expand All @@ -39,7 +47,7 @@ export default function bindActionCreators(actionCreators, dispatch) {
)
}

const boundActionCreators = {}
const boundActionCreators: ActionCreatorsMapObject = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
Expand Down
27 changes: 14 additions & 13 deletions src/combineReducers.js → src/combineReducers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { AnyAction, Action, ReducersMapObject } from '..'
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'

function getUndefinedStateErrorMessage(key, action) {
function getUndefinedStateErrorMessage(key: string, action: Action) {
const actionType = action && action.type
const actionDescription =
(actionType && `action "${String(actionType)}"`) || 'an action'
Expand All @@ -15,10 +16,10 @@ function getUndefinedStateErrorMessage(key, action) {
}

function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
inputState: object,
reducers: ReducersMapObject,
action: Action,
unexpectedKeyCache: { [key: string]: true }
) {
const reducerKeys = Object.keys(reducers)
const argumentName =
Expand All @@ -36,7 +37,7 @@ function getUnexpectedStateShapeWarningMessage(
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "` +
{}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
({} as any).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
`". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
)
Expand All @@ -62,7 +63,7 @@ function getUnexpectedStateShapeWarningMessage(
}
}

function assertReducerShape(reducers) {
function assertReducerShape(reducers: ReducersMapObject) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT })
Expand Down Expand Up @@ -110,9 +111,9 @@ function assertReducerShape(reducers) {
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
export default function combineReducers(reducers) {
export default function combineReducers(reducers: ReducersMapObject) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
const finalReducers: ReducersMapObject = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]

Expand All @@ -130,19 +131,19 @@ export default function combineReducers(reducers) {

// This is used to make sure we don't warn about the same
// keys multiple times.
let unexpectedKeyCache
let unexpectedKeyCache: { [key: string]: true }
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}

let shapeAssertionError
let shapeAssertionError: Error
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}

return function combination(state = {}, action) {
return function combination(state: any = {}, action: AnyAction) {
if (shapeAssertionError) {
throw shapeAssertionError
}
Expand All @@ -160,7 +161,7 @@ export default function combineReducers(reducers) {
}

let hasChanged = false
const nextState = {}
const nextState: any = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
Expand Down
6 changes: 3 additions & 3 deletions src/compose.js → src/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
* (...args) => f(g(h(...args))).
*/

export default function compose(...funcs) {
export default function compose(...funcs: Function[]) {
if (funcs.length === 0) {
return arg => arg
return (arg: any) => arg
}

if (funcs.length === 1) {
return funcs[0]
}

return funcs.reduce((a, b) => (...args) => a(b(...args)))
return funcs.reduce((a, b) => (...args: any) => a(b(...args)))
}
72 changes: 52 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,7 +72,7 @@ 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') {
Expand All @@ -59,7 +81,7 @@ export default function createStore(reducer, preloadedState, enhancer) {

let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
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 All @@ -202,7 +224,10 @@ export default function createStore(reducer, preloadedState, enhancer) {

try {
isDispatching = true
currentState = currentReducer(currentState, action)
;(currentState as S) = currentReducer(
currentState as (S | undefined),
action
)
} finally {
isDispatching = false
}
Expand All @@ -226,19 +251,25 @@ 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 = S, NewActions extends A = A>(
nextReducer: Reducer<NewState, NewActions>
): Store<NewState, NewActions> {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}

currentReducer = nextReducer
;((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
const newStore = (store as unknown) as Store<NewState, NewActions>
dispatch({ type: ActionTypes.REPLACE } as A)
return newStore
}

/**
Expand All @@ -258,14 +289,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 +315,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
}
Loading