Skip to content

Commit f30ed16

Browse files
committed
Uses type-narrowing in reducers
Type-safety in reducer switch statements can be improved by explicitly declaring the action set and allowing TypeScript to narrow down the contents of each action based on the `type` constant. See (e.g.) reduxjs/redux#290
1 parent c75b9f2 commit f30ed16

File tree

3 files changed

+21
-31
lines changed

3 files changed

+21
-31
lines changed

src/actions/index.ts

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,17 @@
1-
export interface Action<T, P> {
2-
type: T
3-
payload?: P
1+
export type Action = {
2+
type: 'INCREMENT_COUNTER',
3+
delta: number,
4+
} | {
5+
type: 'DECREMENT_COUNTER',
6+
delta: number,
47
}
58

6-
type CounterActionTypes = 'INCREMENT_COUNTER' | 'DECREMENT_COUNTER'
7-
8-
type IncrementCounterPayload = { delta: number }
9-
10-
export type IncrementCounter = Action<CounterActionTypes, IncrementCounterPayload>
11-
12-
export const incrementCounter = (delta: number): IncrementCounter => ({
9+
export const incrementCounter = (delta: number): Action => ({
1310
type: 'INCREMENT_COUNTER',
14-
payload: { delta },
11+
delta,
1512
})
1613

17-
type DecrementCounterPayload = { delta: number }
18-
19-
export type DecrementCounter = Action<CounterActionTypes, DecrementCounterPayload>
20-
21-
export const decrementCounter = (delta: number): DecrementCounter => ({
22-
type: 'INCREMENT_COUNTER',
23-
payload: { delta },
14+
export const decrementCounter = (delta: number): Action => ({
15+
type: 'DECREMENT_COUNTER',
16+
delta,
2417
})

src/components/counter.tsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import { connect } from 'react-redux'
55
import { incrementCounter } from '../actions'
66
import { Store } from '../reducers'
77

8-
interface OwnProps {
8+
type OwnProps = {
99
label: string
1010
}
1111

12-
interface ConnectedState {
12+
type ConnectedState = {
1313
counter: { value: number }
1414
}
1515

16-
interface ConnectedDispatch {
16+
type ConnectedDispatch = {
1717
increment: (n: number) => void
1818
}
1919

@@ -22,14 +22,15 @@ const mapStateToProps = (state: Store.All, ownProps: OwnProps): ConnectedState =
2222
})
2323

2424
const mapDispatchToProps = (dispatch: redux.Dispatch<Store.All>): ConnectedDispatch => ({
25-
increment: (n: number) => {
25+
increment: (n: number): void => {
2626
dispatch(incrementCounter(n))
2727
},
2828
})
2929

3030
class CounterComponent extends React.Component<ConnectedState & ConnectedDispatch & OwnProps, {}> {
3131

32-
_onClickIncrement = () => {
32+
_onClickIncrement = (e: Event) => {
33+
e.preventDefault()
3334
this.props.increment(1)
3435
}
3536

src/reducers/index.ts

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { combineReducers } from 'redux'
22

3-
import {
4-
IncrementCounter,
5-
DecrementCounter,
6-
} from '../actions'
3+
import { Action } from '../actions'
74

85
export namespace Store {
96

@@ -18,12 +15,11 @@ const initialState: Store.Counter = {
1815
value: 0,
1916
}
2017

21-
function counter (state: Store.Counter = initialState, action: IncrementCounter | DecrementCounter): Store.Counter {
18+
function counter (state: Store.Counter = initialState, action: Action): Store.Counter {
2219
const { value } = state
2320
switch (action.type) {
24-
case 'INCREMENT_COUNTER': // not checked, pending Microsoft/TypeScript#9407
25-
const { payload } = action as IncrementCounter
26-
const newValue = value + payload.delta
21+
case 'INCREMENT_COUNTER':
22+
const newValue = value + action.delta
2723
return { value: newValue }
2824
}
2925

0 commit comments

Comments
 (0)