Skip to content

Commit 6a03145

Browse files
author
jedmao
committed
Improve TypeScript types
1 parent 85024d4 commit 6a03145

11 files changed

+150
-124
lines changed

package-lock.json

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@
8282
"prettier": "^1.18.2",
8383
"rimraf": "^3.0.0",
8484
"rollup": "^1.20.3",
85+
"redux-thunk": "^2.3.0",
8586
"rollup-plugin-babel": "^4.3.3",
8687
"rollup-plugin-node-resolve": "^5.2.0",
8788
"rollup-plugin-replace": "^2.2.0",

src/applyMiddleware.ts

Lines changed: 18 additions & 38 deletions
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
@@ -23,40 +26,17 @@ import { Reducer } from './types/reducers'
2326
* @template Ext Dispatch signature added by a middleware.
2427
* @template S The type of the state supported by a middleware.
2528
*/
26-
export default function applyMiddleware(): StoreEnhancer
27-
export default function applyMiddleware<Ext1, S>(
28-
middleware1: Middleware<Ext1, S, any>
29-
): StoreEnhancer<{ dispatch: Ext1 }>
30-
export default function applyMiddleware<Ext1, Ext2, S>(
31-
middleware1: Middleware<Ext1, S, any>,
32-
middleware2: Middleware<Ext2, S, any>
33-
): StoreEnhancer<{ dispatch: Ext1 & Ext2 }>
34-
export default function applyMiddleware<Ext1, Ext2, Ext3, S>(
35-
middleware1: Middleware<Ext1, S, any>,
36-
middleware2: Middleware<Ext2, S, any>,
37-
middleware3: Middleware<Ext3, S, any>
38-
): StoreEnhancer<{ dispatch: Ext1 & Ext2 & Ext3 }>
39-
export default function applyMiddleware<Ext1, Ext2, Ext3, Ext4, S>(
40-
middleware1: Middleware<Ext1, S, any>,
41-
middleware2: Middleware<Ext2, S, any>,
42-
middleware3: Middleware<Ext3, S, any>,
43-
middleware4: Middleware<Ext4, S, any>
44-
): StoreEnhancer<{ dispatch: Ext1 & Ext2 & Ext3 & Ext4 }>
45-
export default function applyMiddleware<Ext1, Ext2, Ext3, Ext4, Ext5, S>(
46-
middleware1: Middleware<Ext1, S, any>,
47-
middleware2: Middleware<Ext2, S, any>,
48-
middleware3: Middleware<Ext3, S, any>,
49-
middleware4: Middleware<Ext4, S, any>,
50-
middleware5: Middleware<Ext5, S, any>
51-
): StoreEnhancer<{ dispatch: Ext1 & Ext2 & Ext3 & Ext4 & Ext5 }>
52-
export default function applyMiddleware<Ext, S = any>(
53-
...middlewares: Middleware<any, S, any>[]
54-
): StoreEnhancer<{ dispatch: Ext }>
55-
export default function applyMiddleware(
56-
...middlewares: Middleware[]
57-
): StoreEnhancer<any> {
58-
return (createStore: StoreCreator) => <S, A extends AnyAction>(
59-
reducer: Reducer<S, A>,
29+
export default function applyMiddleware<
30+
S = any,
31+
M extends Middleware = Middleware
32+
>(
33+
...middlewares: M[]
34+
): StoreEnhancer<
35+
M extends Middleware<any, any, infer D> ? { dispatch: D } : never,
36+
S
37+
> {
38+
return (createStore: StoreEnhancerStoreCreator<any>) => (
39+
reducer,
6040
...args: any[]
6141
) => {
6242
const store = createStore(reducer, ...args)

src/createStore.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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
@@ -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

test/applyMiddleware.spec.ts

Lines changed: 31 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
import { createStore, applyMiddleware, Middleware, AnyAction, Action } from '..'
1+
import {
2+
createStore,
3+
applyMiddleware,
4+
Middleware,
5+
AnyAction,
6+
Action,
7+
Store
8+
} from '../src'
9+
import { rootActionTypes } from './helpers/actionTypes'
210
import * as reducers from './helpers/reducers'
311
import { addTodo, addTodoAsync, addTodoIfEmpty } from './helpers/actionCreators'
412
import { thunk } from './helpers/middleware'
513

614
describe('applyMiddleware', () => {
715
it('warns when dispatching during middleware setup', () => {
8-
function dispatchingMiddleware(store) {
16+
function dispatchingMiddleware(store: Store<any, rootActionTypes>) {
917
store.dispatch(addTodo('Dont dispatch in middleware setup'))
1018
return next => action => next(action)
1119
}
@@ -40,8 +48,8 @@ describe('applyMiddleware', () => {
4048
])
4149
})
4250

43-
it('passes recursive dispatches through the middleware chain', () => {
44-
function test(spyOnMethods) {
51+
it('passes recursive dispatches through the middleware chain', async () => {
52+
function test(spyOnMethods: jest.Mock) {
4553
return () => next => action => {
4654
spyOnMethods(action)
4755
return next(action)
@@ -51,16 +59,11 @@ describe('applyMiddleware', () => {
5159
const spy = jest.fn()
5260
const store = applyMiddleware(test(spy), thunk)(createStore)(reducers.todos)
5361

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-
})
62+
await store.dispatch(addTodoAsync('Use Redux'))
63+
expect(spy.mock.calls.length).toEqual(2)
6164
})
6265

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

6669
store.dispatch(addTodoIfEmpty('Hello'))
@@ -91,27 +94,21 @@ describe('applyMiddleware', () => {
9194
}
9295
])
9396

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-
})
97+
await store.dispatch(addTodoAsync('Maybe'))
98+
expect(store.getState()).toEqual([
99+
{
100+
id: 1,
101+
text: 'Hello'
102+
},
103+
{
104+
id: 2,
105+
text: 'World'
106+
},
107+
{
108+
id: 3,
109+
text: 'Maybe'
110+
}
111+
])
115112
})
116113

117114
it('passes through all arguments of dispatch calls from within middleware', () => {
@@ -144,7 +141,7 @@ describe('applyMiddleware', () => {
144141
applyMiddleware(multiArgMiddleware, dummyMiddleware)
145142
)
146143

147-
store.dispatch(spy)
144+
store.dispatch(spy as any)
148145
expect(spy.mock.calls[0]).toEqual(testCallArgs)
149146
})
150147
})

test/bindActionCreators.spec.ts

Lines changed: 3 additions & 3 deletions
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

Lines changed: 4 additions & 3 deletions
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', () => {

test/helpers/actionCreators.ts

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,89 @@
1+
import { ThunkAction } from 'redux-thunk'
2+
13
import {
24
ADD_TODO,
5+
AddTodo,
36
DISPATCH_IN_MIDDLE,
7+
DispatchInMiddle,
48
GET_STATE_IN_MIDDLE,
9+
GetStateInMiddle,
510
SUBSCRIBE_IN_MIDDLE,
11+
SubscribeInMiddle,
612
UNSUBSCRIBE_IN_MIDDLE,
13+
UnsubscribeInMiddle,
714
THROW_ERROR,
8-
UNKNOWN_ACTION
15+
ThrowError,
16+
UNKNOWN_ACTION,
17+
UnknownAction
918
} from './actionTypes'
10-
import { Action, AnyAction, Dispatch } from '../..'
1119

12-
export function addTodo(text: string): AnyAction {
20+
export function addTodo(text: string): AddTodo {
1321
return { type: ADD_TODO, text }
1422
}
1523

16-
export function addTodoAsync(text: string) {
17-
return (dispatch: Dispatch): Promise<void> =>
18-
new Promise(resolve =>
24+
export function addTodoAsync(text: string): ThunkAction<any, any, {}, AddTodo> {
25+
return dispatch =>
26+
new Promise<void>(resolve =>
1927
setImmediate(() => {
2028
dispatch(addTodo(text))
2129
resolve()
2230
})
2331
)
2432
}
2533

26-
export function addTodoIfEmpty(text: string) {
27-
return (dispatch: Dispatch, getState: () => any) => {
34+
export function addTodoIfEmpty(
35+
text: string
36+
): ThunkAction<any, any, {}, AddTodo> {
37+
return (dispatch, getState) => {
2838
if (!getState().length) {
2939
dispatch(addTodo(text))
3040
}
3141
}
3242
}
3343

34-
export function dispatchInMiddle(boundDispatchFn: () => void): AnyAction {
44+
export function dispatchInMiddle(
45+
boundDispatchFn: () => void
46+
): DispatchInMiddle {
3547
return {
3648
type: DISPATCH_IN_MIDDLE,
3749
boundDispatchFn
3850
}
3951
}
4052

41-
export function getStateInMiddle(boundGetStateFn: () => void): AnyAction {
53+
export function getStateInMiddle(
54+
boundGetStateFn: () => void
55+
): GetStateInMiddle {
4256
return {
4357
type: GET_STATE_IN_MIDDLE,
4458
boundGetStateFn
4559
}
4660
}
4761

48-
export function subscribeInMiddle(boundSubscribeFn: () => void): AnyAction {
62+
export function subscribeInMiddle(
63+
boundSubscribeFn: () => void
64+
): SubscribeInMiddle {
4965
return {
5066
type: SUBSCRIBE_IN_MIDDLE,
5167
boundSubscribeFn
5268
}
5369
}
5470

55-
export function unsubscribeInMiddle(boundUnsubscribeFn: () => void): AnyAction {
71+
export function unsubscribeInMiddle(
72+
boundUnsubscribeFn: () => void
73+
): UnsubscribeInMiddle {
5674
return {
5775
type: UNSUBSCRIBE_IN_MIDDLE,
5876
boundUnsubscribeFn
5977
}
6078
}
6179

62-
export function throwError(): Action {
80+
export function throwError(): ThrowError {
6381
return {
6482
type: THROW_ERROR
6583
}
6684
}
6785

68-
export function unknownAction(): Action {
86+
export function unknownAction(): UnknownAction {
6987
return {
7088
type: UNKNOWN_ACTION
7189
}

test/helpers/actionTypes.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,42 @@
1+
import { Action } from '../..'
2+
13
export const ADD_TODO = 'ADD_TODO'
4+
export interface AddTodo extends Action<typeof ADD_TODO> {
5+
text: string
6+
}
7+
28
export const DISPATCH_IN_MIDDLE = 'DISPATCH_IN_MIDDLE'
9+
export interface DispatchInMiddle extends Action<typeof DISPATCH_IN_MIDDLE> {
10+
boundDispatchFn: () => void
11+
}
12+
313
export const GET_STATE_IN_MIDDLE = 'GET_STATE_IN_MIDDLE'
14+
export interface GetStateInMiddle extends Action<typeof GET_STATE_IN_MIDDLE> {
15+
boundGetStateFn: () => void
16+
}
17+
418
export const SUBSCRIBE_IN_MIDDLE = 'SUBSCRIBE_IN_MIDDLE'
19+
export interface SubscribeInMiddle extends Action<typeof SUBSCRIBE_IN_MIDDLE> {
20+
boundSubscribeFn: () => void
21+
}
22+
523
export const UNSUBSCRIBE_IN_MIDDLE = 'UNSUBSCRIBE_IN_MIDDLE'
24+
export interface UnsubscribeInMiddle
25+
extends Action<typeof UNSUBSCRIBE_IN_MIDDLE> {
26+
boundUnsubscribeFn: () => void
27+
}
28+
629
export const THROW_ERROR = 'THROW_ERROR'
30+
export type ThrowError = Action<typeof THROW_ERROR>
31+
732
export const UNKNOWN_ACTION = 'UNKNOWN_ACTION'
33+
export type UnknownAction = Action<typeof UNKNOWN_ACTION>
34+
35+
export type rootActionTypes =
36+
| AddTodo
37+
| DispatchInMiddle
38+
| GetStateInMiddle
39+
| SubscribeInMiddle
40+
| UnsubscribeInMiddle
41+
| ThrowError
42+
| UnknownAction

0 commit comments

Comments
 (0)