forked from angular-redux/store
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added tassign, docs about typing reducers
Fixes angular-redux#216
- Loading branch information
Seth Davenport
committed
Sep 5, 2016
1 parent
a059179
commit ad2b723
Showing
5 changed files
with
139 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
# Strongly Typed Reducers | ||
|
||
It's good practice in typescript to be as specific about your types as possible. | ||
This helps you catch errors at compile-time instead of build time. | ||
|
||
Reducers are no exception to this rule. However it's not always obvious how to | ||
make this happen in practice. | ||
|
||
## Reducer Typing Best Practices | ||
|
||
### Define an Interface for your State | ||
|
||
It's important to strongly type the data in your store, and this is done by | ||
defining types for the `state` arguments to your reducers`: | ||
|
||
```typescript | ||
export type TFoo: string; | ||
|
||
// Being explicit about the state argument and type ensures that all your | ||
// reducer's cases return the correct type. | ||
export const fooReducer = (state: TFoo, action): TFoo => { | ||
// ... | ||
}; | ||
|
||
export interface IBar { | ||
a: number; | ||
b: string; | ||
} | ||
|
||
export const barReducer = (state: IBar, action): IBar => { | ||
// ... | ||
}; | ||
``` | ||
|
||
Since most applications are composed of several reducers, you should compose | ||
a global 'AppState' by composing the reducer types: | ||
|
||
```typescript | ||
export interface IAppState { | ||
foo: TFoo; | ||
bar: IBar; | ||
} | ||
|
||
export const rootReducer = combineReducers({ | ||
foo: fooReducer, | ||
bar: barReducer | ||
}); | ||
``` | ||
|
||
### Consider Using types from `redux` | ||
|
||
Redux ships with a good set of official typings; consider using them. In | ||
particular, be aware of the 'Action' and 'Reducer' types: | ||
|
||
```typescript | ||
import { Action, Reducer } from 'redux'; | ||
|
||
export const fooReducer: Reducer<TFoo> = (state: TFoo, action: Action): TFoo => { | ||
// ... | ||
}; | ||
``` | ||
|
||
Note that we supply the state type as a generic type parameter to `Reducer`. | ||
|
||
### Consider using 'Flux Standard Actions' (FSAs) | ||
|
||
[FSA](https://github.com/acdlite/flux-standard-action/blob/master/src/index.js) | ||
is a widely-used convention for defining the shape of actions. You can import | ||
in into your project and use it: | ||
|
||
```sh | ||
npm install --save flux-standard-action @types/flux-standard-action | ||
``` | ||
|
||
Flux standard actions take 'payload', and 'error' parameters in addition to the | ||
basic `type`. Payloads in particular help you strengthen your reducers even | ||
further: | ||
|
||
```typescript | ||
import { Reducer } from 'redux'; | ||
import { Action } from 'flux-standard-action'; | ||
|
||
// Here we're saying that the action's payload must have type TFoo. | ||
// If you need more flexibility in payload types, you can use a union and | ||
// typeguards: Action<TFoo : IBar> etc. | ||
export const fooReducer: Reducer<TFoo> = (state: TFoo, action: Action<TFoo>): TFoo => { | ||
// ... | ||
}; | ||
``` | ||
|
||
### Use a Typed Wrapper around Object.assign | ||
|
||
In the Babel world, reducers often use `Object.assign` or property spread to | ||
maintain immutability. This works in Typescript too, but it's not typesafe: | ||
|
||
```typescript | ||
export const barReducer: Reducer<IBar> = (state: IBar, action: Action<number>): IBar => { | ||
switch(action.type) { | ||
case A_HAS_CHANGED: | ||
return Object.assign({}, state, { | ||
a: action.payload, | ||
zzz: 'test' | ||
}); | ||
// ... | ||
} | ||
}; | ||
``` | ||
|
||
Ideally, we'd like this code to fail because `zzz` is not a property of the state. | ||
However, the built-in type definitions for `Object.assign` return an intersection | ||
type, making this legal. This makes sense for general usage of `Object.assign`, | ||
but it's not what we want in a reducer. | ||
|
||
Instead, we've provided a type-corrected immutable assignment function, `tassign`, | ||
that will catch this type of error: | ||
|
||
```typescript | ||
import { tassign } from 'ng2-redux'; | ||
|
||
export const barReducer: Reducer<IBar> = (state: IBar, action: Action<number>): IBar => { | ||
switch(action.type) { | ||
case A_HAS_CHANGED: | ||
return tassign(state, { | ||
a: action.payload, | ||
zzz: 'test' // Error: zzz is not a property of IBar | ||
}); | ||
// ... | ||
} | ||
}; | ||
``` | ||
|
||
Following these tips to strengthen your reducer typings will go a long way | ||
towards more robust code. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export { NgRedux } from './components/ng-redux'; | ||
export { DevToolsExtension } from './components/dev-tools'; | ||
export { select } from './decorators/select'; | ||
export { tassign } from './utils/tassign'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function tassign<T extends U, U>(target: T, ...source: U[]): T { | ||
return Object.assign({}, target, ...source); | ||
} |