Skip to content

Commit 180e93e

Browse files
author
jedmao
committed
Improve TypeScript types
1 parent 066fa81 commit 180e93e

15 files changed

+167
-117
lines changed

package-lock.json

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@
8181
"glob": "^7.1.4",
8282
"jest": "^24.8.0",
8383
"prettier": "^1.18.2",
84+
"redux-thunk": "^2.3.0",
8485
"rimraf": "^2.6.3",
8586
"rollup": "^1.16.7",
8687
"rollup-plugin-babel": "^4.3.3",

src/applyMiddleware.ts

+15-9
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import compose from './compose'
2-
import { Middleware, MiddlewareAPI } from './types/middleware'
3-
import { AnyAction } from './types/actions'
4-
import { StoreEnhancer, StoreCreator, Dispatch } from './types/store'
5-
import { Reducer } from './types/reducers'
2+
import {
3+
Middleware,
4+
StoreEnhancer,
5+
Dispatch,
6+
MiddlewareAPI,
7+
StoreEnhancerStoreCreator
8+
} from '..'
69

710
/**
811
* Creates a store enhancer that applies middleware to the dispatch method
@@ -17,8 +20,11 @@ import { Reducer } from './types/reducers'
1720
* Note that each middleware will be given the `dispatch` and `getState` functions
1821
* as named arguments.
1922
*
20-
* @param {...Function} middlewares The middleware chain to be applied.
21-
* @returns {Function} A store enhancer applying the middleware.
23+
* @param middlewares The middleware chain to be applied.
24+
* @returns A store enhancer applying the middleware.
25+
*
26+
* @template Ext Dispatch signature added by a middleware.
27+
* @template S The type of the state supported by a middleware.
2228
*/
2329
export default function applyMiddleware(): StoreEnhancer
2430
export default function applyMiddleware<Ext1, S>(
@@ -51,9 +57,9 @@ export default function applyMiddleware<Ext, S = any>(
5157
): StoreEnhancer<{ dispatch: Ext }>
5258
export default function applyMiddleware(
5359
...middlewares: Middleware[]
54-
): StoreEnhancer<any> {
55-
return (createStore: StoreCreator) => <S, A extends AnyAction>(
56-
reducer: Reducer<S, A>,
60+
): StoreEnhancer {
61+
return (createStore: StoreEnhancerStoreCreator<any>) => (
62+
reducer,
5763
...args: any[]
5864
) => {
5965
const store = createStore(reducer, ...args)

src/compose.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ type Func3<T1, T2, T3, R> = (a1: T1, a2: T2, a3: T3, ...args: any[]) => R
99
* resulting composite function.
1010
*
1111
* @param funcs The functions to compose.
12-
* @returns R function obtained by composing the argument functions from right
12+
* @returns A function obtained by composing the argument functions from right
1313
* to left. For example, `compose(f, g, h)` is identical to doing
1414
* `(...args) => f(g(h(...args)))`.
1515
*/

src/createStore.ts

+16-20
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,21 @@ import isPlainObject from './utils/isPlainObject'
2121
* parts of the state tree respond to actions, you may combine several reducers
2222
* into a single reducer function by using `combineReducers`.
2323
*
24-
* @param {Function} reducer A function that returns the next state tree, given
24+
* @param reducer A function that returns the next state tree, given
2525
* the current state tree and the action to handle.
2626
*
27-
* @param {any} [preloadedState] The initial state. You may optionally specify it
27+
* @param preloadedState The initial state. You may optionally specify it
2828
* to hydrate the state from the server in universal apps, or to restore a
2929
* previously serialized user session.
3030
* If you use `combineReducers` to produce the root reducer function, this must be
3131
* an object with the same shape as `combineReducers` keys.
3232
*
33-
* @param {Function} [enhancer] The store enhancer. You may optionally specify it
33+
* @param enhancer The store enhancer. You may optionally specify it
3434
* to enhance the store with third-party capabilities such as middleware,
3535
* time travel, persistence, etc. The only store enhancer that ships with Redux
3636
* is `applyMiddleware()`.
3737
*
38-
* @returns {Store} A Redux store that lets you read the state, dispatch actions
38+
* @returns A Redux store that lets you read the state, dispatch actions
3939
* and subscribe to changes.
4040
*/
4141
export default function createStore<
@@ -97,7 +97,7 @@ export default function createStore<
9797
throw new Error('Expected the reducer to be a function.')
9898
}
9999

100-
let currentReducer = reducer
100+
let currentReducer: Reducer<any, any> = reducer
101101
let currentState = preloadedState as S
102102
let currentListeners: (() => void)[] | null = []
103103
let nextListeners = currentListeners
@@ -119,7 +119,7 @@ export default function createStore<
119119
/**
120120
* Reads the state tree managed by the store.
121121
*
122-
* @returns {any} The current state tree of your application.
122+
* @returns The current state tree of your application.
123123
*/
124124
function getState(): S {
125125
if (isDispatching) {
@@ -153,8 +153,8 @@ export default function createStore<
153153
* registered before the `dispatch()` started will be called with the latest
154154
* state by the time it exits.
155155
*
156-
* @param {Function} listener A callback to be invoked on every dispatch.
157-
* @returns {Function} A function to remove this change listener.
156+
* @param listener A callback to be invoked on every dispatch.
157+
* @returns A function to remove this change listener.
158158
*/
159159
function subscribe(listener: () => void) {
160160
if (typeof listener !== 'function') {
@@ -210,13 +210,13 @@ export default function createStore<
210210
* example, see the documentation for the `redux-thunk` package. Even the
211211
* middleware will eventually dispatch plain object actions using this method.
212212
*
213-
* @param {Object} action A plain object representing “what changed”. It is
213+
* @param action A plain object representing “what changed”. It is
214214
* a good idea to keep actions serializable so you can record and replay user
215215
* sessions, or use the time travelling `redux-devtools`. An action must have
216216
* a `type` property which may not be `undefined`. It is a good idea to use
217217
* string constants for action types.
218218
*
219-
* @returns {Object} For convenience, the same action object you dispatched.
219+
* @returns For convenience, the same action object you dispatched.
220220
*
221221
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
222222
* return something else (for example, a Promise you can await).
@@ -263,8 +263,8 @@ export default function createStore<
263263
* load some of the reducers dynamically. You might also need this if you
264264
* implement a hot reloading mechanism for Redux.
265265
*
266-
* @param {Function} nextReducer The reducer for the store to use instead.
267-
* @returns {Store} The same store instance with a new reducer in place.
266+
* @param nextReducer The reducer for the store to use instead.
267+
* @returns The same store instance with a new reducer in place.
268268
*/
269269
function replaceReducer<NewState, NewActions extends A>(
270270
nextReducer: Reducer<NewState, NewActions>
@@ -273,11 +273,7 @@ export default function createStore<
273273
throw new Error('Expected the nextReducer to be a function.')
274274
}
275275

276-
// TODO: do this more elegantly
277-
;((currentReducer as unknown) as Reducer<
278-
NewState,
279-
NewActions
280-
>) = nextReducer
276+
currentReducer = nextReducer
281277

282278
// This action has a similiar effect to ActionTypes.INIT.
283279
// Any reducers that existed in both the new and old rootReducer
@@ -296,7 +292,7 @@ export default function createStore<
296292

297293
/**
298294
* Interoperability point for observable/reactive libraries.
299-
* @returns {observable} A minimal observable of state changes.
295+
* @returns A minimal observable of state changes.
300296
* For more information, see the observable proposal:
301297
* https://github.com/tc39/proposal-observable
302298
*/
@@ -305,9 +301,9 @@ export default function createStore<
305301
return {
306302
/**
307303
* The minimal observable subscription method.
308-
* @param {Object} observer Any object that can be used as an observer.
304+
* @param observer Any object that can be used as an observer.
309305
* The observer object should have a `next` method.
310-
* @returns {subscription} An object with an `unsubscribe` method that can
306+
* @returns An object with an `unsubscribe` method that can
311307
* be used to unsubscribe the observable from the store, and prevent further
312308
* emission of values from the observable.
313309
*/

src/types/middleware.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface MiddlewareAPI<D extends Dispatch = Dispatch, S = any> {
2121
* installed.
2222
*/
2323
export interface Middleware<
24-
_DispatchExt = {}, // TODO: remove unused component
24+
_DispatchExt = {}, // TODO: remove unused component (breaking change)
2525
S = any,
2626
D extends Dispatch = Dispatch
2727
> {

src/utils/isPlainObject.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
/**
2-
* @param {any} obj The object to inspect.
3-
* @returns {boolean} True if the argument appears to be a plain object.
2+
* @param obj The object to inspect.
3+
* @returns True if the argument appears to be a plain object.
44
*/
5-
export default function isPlainObject(obj: unknown) {
5+
export default function isPlainObject(obj: any): boolean {
66
if (typeof obj !== 'object' || obj === null) return false
77

88
let proto = obj

src/utils/warning.ts

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
/**
22
* Prints a warning in the console if it exists.
33
*
4-
* @param {String} message The warning message.
5-
* @returns {void}
4+
* @param message The warning message.
65
*/
7-
export default function warning(message: string) {
6+
export default function warning(message: string): void {
87
/* eslint-disable no-console */
98
if (typeof console !== 'undefined' && typeof console.error === 'function') {
109
console.error(message)

test/applyMiddleware.spec.ts

+30-34
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
import { createStore, applyMiddleware, Middleware, AnyAction, Action } from '..'
1+
import {
2+
createStore,
3+
applyMiddleware,
4+
Middleware,
5+
AnyAction,
6+
Action,
7+
Store
8+
} from '..'
29
import * as reducers from './helpers/reducers'
310
import { addTodo, addTodoAsync, addTodoIfEmpty } from './helpers/actionCreators'
411
import { thunk } from './helpers/middleware'
512

613
describe('applyMiddleware', () => {
714
it('warns when dispatching during middleware setup', () => {
8-
function dispatchingMiddleware(store) {
15+
function dispatchingMiddleware(store: Store) {
916
store.dispatch(addTodo('Dont dispatch in middleware setup'))
1017
return next => action => next(action)
1118
}
@@ -40,8 +47,8 @@ describe('applyMiddleware', () => {
4047
])
4148
})
4249

43-
it('passes recursive dispatches through the middleware chain', () => {
44-
function test(spyOnMethods) {
50+
it('passes recursive dispatches through the middleware chain', async () => {
51+
function test(spyOnMethods: jest.Mock) {
4552
return () => next => action => {
4653
spyOnMethods(action)
4754
return next(action)
@@ -51,16 +58,11 @@ describe('applyMiddleware', () => {
5158
const spy = jest.fn()
5259
const store = applyMiddleware(test(spy), thunk)(createStore)(reducers.todos)
5360

54-
// the typing for redux-thunk is super complex, so we will use an as unknown hack
55-
const dispatchedValue = (store.dispatch(
56-
addTodoAsync('Use Redux')
57-
) as unknown) as Promise<void>
58-
return dispatchedValue.then(() => {
59-
expect(spy.mock.calls.length).toEqual(2)
60-
})
61+
await store.dispatch(addTodoAsync('Use Redux'))
62+
expect(spy.mock.calls.length).toEqual(2)
6163
})
6264

63-
it('works with thunk middleware', done => {
65+
it('works with thunk middleware', async () => {
6466
const store = applyMiddleware(thunk)(createStore)(reducers.todos)
6567

6668
store.dispatch(addTodoIfEmpty('Hello'))
@@ -91,27 +93,21 @@ describe('applyMiddleware', () => {
9193
}
9294
])
9395

94-
// the typing for redux-thunk is super complex, so we will use an "as unknown" hack
95-
const dispatchedValue = (store.dispatch(
96-
addTodoAsync('Maybe')
97-
) as unknown) as Promise<void>
98-
dispatchedValue.then(() => {
99-
expect(store.getState()).toEqual([
100-
{
101-
id: 1,
102-
text: 'Hello'
103-
},
104-
{
105-
id: 2,
106-
text: 'World'
107-
},
108-
{
109-
id: 3,
110-
text: 'Maybe'
111-
}
112-
])
113-
done()
114-
})
96+
await store.dispatch(addTodoAsync('Maybe'))
97+
expect(store.getState()).toEqual([
98+
{
99+
id: 1,
100+
text: 'Hello'
101+
},
102+
{
103+
id: 2,
104+
text: 'World'
105+
},
106+
{
107+
id: 3,
108+
text: 'Maybe'
109+
}
110+
])
115111
})
116112

117113
it('passes through all arguments of dispatch calls from within middleware', () => {
@@ -144,7 +140,7 @@ describe('applyMiddleware', () => {
144140
applyMiddleware(multiArgMiddleware, dummyMiddleware)
145141
)
146142

147-
store.dispatch(spy)
143+
store.dispatch(spy as any)
148144
expect(spy.mock.calls[0]).toEqual(testCallArgs)
149145
})
150146
})

test/bindActionCreators.spec.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { bindActionCreators, createStore, ActionCreator } from '..'
1+
import { bindActionCreators, createStore, ActionCreator, Store } from '..'
22
import { todos } from './helpers/reducers'
33
import * as actionCreators from './helpers/actionCreators'
44

55
describe('bindActionCreators', () => {
6-
let store
7-
let actionCreatorFunctions
6+
let store: Store<typeof todos>
7+
let actionCreatorFunctions: Partial<typeof actionCreators>
88

99
beforeEach(() => {
1010
store = createStore(todos)

test/createStore.spec.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,7 @@ describe('createStore', () => {
505505

506506
it('throws if action type is missing', () => {
507507
const store = createStore(reducers.todos)
508+
// @ts-ignore
508509
expect(() => store.dispatch({})).toThrow(
509510
/Actions may not have an undefined "type" property/
510511
)
@@ -519,10 +520,10 @@ describe('createStore', () => {
519520

520521
it('does not throw if action type is falsy', () => {
521522
const store = createStore(reducers.todos)
522-
expect(() => store.dispatch({ type: false })).not.toThrow()
523-
expect(() => store.dispatch({ type: 0 })).not.toThrow()
523+
expect(() => store.dispatch({ type: false } as any)).not.toThrow()
524+
expect(() => store.dispatch({ type: 0 } as any)).not.toThrow()
524525
expect(() => store.dispatch({ type: null })).not.toThrow()
525-
expect(() => store.dispatch({ type: '' })).not.toThrow()
526+
expect(() => store.dispatch({ type: '' } as any)).not.toThrow()
526527
})
527528

528529
it('accepts enhancer as the third argument', () => {

0 commit comments

Comments
 (0)