immer-reducer
Advanced tools
Comparing version 0.1.0 to 0.1.1
import { Draft } from "immer"; | ||
/** get function arguments as tuple type */ | ||
declare type ArgumentsType<T> = T extends (...args: infer V) => any ? V : never; | ||
/** Get union of function property names */ | ||
declare type FunctionPropertyNames<T> = { | ||
[K in keyof T]: T[K] extends Function ? K : never; | ||
}[keyof T]; | ||
/** Pick only methods from object */ | ||
declare type Methods<T> = Pick<T, FunctionPropertyNames<T>>; | ||
/** flatten functions in an object to their return values */ | ||
declare type FlattenToReturnTypes<T extends { | ||
@@ -12,9 +16,13 @@ [key: string]: () => any; | ||
}; | ||
/** get union of object value types */ | ||
declare type ObjectValueTypes<T> = T[keyof T]; | ||
/** get union of object method return types */ | ||
declare type ReturnTypeUnion<T extends { | ||
[key: string]: () => any; | ||
}> = ObjectValueTypes<FlattenToReturnTypes<T>>; | ||
/** type constrant for the ImmerReducer class */ | ||
export interface ImmerReducerClass { | ||
new (...args: any[]): ImmerReducer<any>; | ||
} | ||
/** get state type from a ImmerReducer subclass */ | ||
declare type ImmerReducerState<T> = T extends { | ||
@@ -25,2 +33,7 @@ prototype: { | ||
} ? V : never; | ||
/** generate reducer function type form the ImmerReducer class */ | ||
interface ImmerReducerFunction<T extends ImmerReducerClass> { | ||
(state: ImmerReducerState<T> | undefined, action: ReturnTypeUnion<ActionCreators<T>>): ImmerReducerState<T> | undefined; | ||
} | ||
/** generate ActionCreators types from the ImmerReducer class */ | ||
export declare type ActionCreators<ClassActions extends ImmerReducerClass> = { | ||
@@ -32,2 +45,3 @@ [K in keyof Methods<InstanceType<ClassActions>>]: (...args: ArgumentsType<InstanceType<ClassActions>[K]>) => { | ||
}; | ||
/** The actual ImmerReducer class */ | ||
export declare class ImmerReducer<T> { | ||
@@ -39,6 +53,3 @@ readonly state: T; | ||
export declare function createActionCreators<T extends ImmerReducerClass>(immerReducerClass: T): ActionCreators<T>; | ||
interface ImmerReducerFunction<T extends ImmerReducerClass> { | ||
(state: ImmerReducerState<T> | undefined, action: ReturnTypeUnion<ActionCreators<T>>): ImmerReducerState<T> | undefined; | ||
} | ||
export declare function createReducerFunction<T extends ImmerReducerClass>(immerReducerClass: T): ImmerReducerFunction<T>; | ||
export {}; |
@@ -8,2 +8,3 @@ "use strict"; | ||
var PREFIX = "IMMER_REDUCER"; | ||
/** The actual ImmerReducer class */ | ||
var ImmerReducer = /** @class */ (function () { | ||
@@ -41,3 +42,3 @@ function ImmerReducer(draftState, state) { | ||
}); | ||
return actionCreators; | ||
return actionCreators; // typed in the function signature | ||
} | ||
@@ -44,0 +45,0 @@ exports.createActionCreators = createActionCreators; |
{ | ||
"name": "immer-reducer", | ||
"version": "0.1.0", | ||
"version": "0.1.1", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "lib/immer-reducer.js", |
@@ -5,4 +5,6 @@ # Immer Reducer | ||
Create Redux reducers using [Immer](https://github.com/mweststrate/immer)! [Typescript friendly](#100-type-safety-with-typescript). | ||
Create Redux reducers using [Immer](https://github.com/mweststrate/immer)! | ||
Typescript [friendly](#100-type-safety-with-typescript) too. | ||
## Install | ||
@@ -17,8 +19,11 @@ | ||
```js | ||
import {ImmerReducer, createActionCreators} from "immer-reducer"; | ||
import {ImmerReducer} from "immer-reducer"; | ||
export class MyImmerReducer extends ImmerReducer { | ||
// each method becomes a reducer | ||
// The class represents the classic switch-case reducer | ||
class MyImmerReducer extends ImmerReducer { | ||
// each method becomes an Action Creator | ||
setFirstName(firstName) { | ||
// State update are simple as assigning a value to the state property thanks to Immer | ||
// State updates are simple as assigning a value to | ||
// the draftState property thanks to Immer | ||
this.draftState.firstName = firstName; | ||
@@ -31,3 +36,3 @@ } | ||
// You can combine reducers too | ||
// You can combine methods to a single Action Creator | ||
setName(firstName, lastName) { | ||
@@ -45,7 +50,7 @@ this.setFirstName(firstName); | ||
export const ActionCreators = createActionCreators(MyImmerReducer); | ||
export const reducerFunction = createReducerFunction(MyImmerReducer); | ||
const ActionCreators = createActionCreators(MyImmerReducer); | ||
const reducerFunction = createReducerFunction(MyImmerReducer); | ||
``` | ||
and create a Redux store | ||
and create the Redux store | ||
@@ -86,6 +91,6 @@ ```js | ||
So the method names becomes Redux Action Types and the method arguments | ||
becomes the action payload. The reducer function will then match these | ||
So the method names become the Redux Action Types and the method arguments | ||
become the action payloads. The reducer function will then match these | ||
actions against the class and calls the approciate methods with the payload | ||
spread to the arguments. But do note that the action format is not part of | ||
array spread to the arguments. But do note that the action format is not part of | ||
the public API so don't write any code relying on it. The actions are handled | ||
@@ -101,3 +106,6 @@ by the generated reducer function. | ||
specifically Typescript usage in mind because I was unable to find any other | ||
libraries that make Redux usage both boilerplate free and 100% type safe. | ||
libraries that make Redux usage both boilerplate free and 100% type safe. To | ||
be honest it was no easy feat. Pretty advanced Typescript sorcery was | ||
required and so this library requires Typescript 3.1 or later. But the end | ||
results is really simple for the end user. | ||
@@ -108,9 +116,12 @@ The Typescript usage does not differ that much from the Javascript usage. | ||
```ts | ||
const initialState = { | ||
firstName: "", | ||
lastName: "", | ||
}; | ||
interface State { | ||
// The state can be defined as read only | ||
readonly firstName: string; | ||
readonly lastName: string; | ||
} | ||
class MyImmerReducer extends ImmerReducer<typeof initialState> { | ||
class MyImmerReducer extends ImmerReducer<State> { | ||
setFirstName(firstName: string) { | ||
// draftState has the State type but the readonly | ||
// flags are removed here to allow type safe mutation | ||
this.draftState.firstName = firstName; | ||
@@ -125,3 +136,3 @@ } | ||
The generated ActionsTypes object now respects the types used in the class | ||
The generated `ActionsTypes` object now respects the types used in the class | ||
@@ -141,2 +152,7 @@ ```ts | ||
const initialState: State = { | ||
firstName: "", | ||
lastName: "", | ||
}; | ||
reducer(initialState, ActionCreators.setFirstName("Charlie")); // OK | ||
@@ -147,4 +163,6 @@ reducer(initialState, {type: "WAT"}); // Type error | ||
## Type Safe connect()? | ||
If you enjoy this then also checkout | ||
[redux-render-prop](https://github.com/epeli/redux-render-prop) for type safe | ||
`connect()` alternative. |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
11589
114
159