tsdux
Type-safe Redux Utils for TypeScript!
Table of Contents
How To Install
npm install --save redux tsdux
If you use redux-observable or use RxJS with redux, you also might have interest in tsdux-observable which includes some basic utilities to use TSdux with Observable
s.
npm install --save rxjs tsdux-observable
How To Use
import { action, payload, union } from 'tsdux';
interface MySectionState {
log?: string;
id: number;
}
export const MyAction = action('myapp/mysection/MY_ACTION', props<{ error?: string }>());
export const YourAction = action('myapp/mysection/YOUR_ACTION', payload<{ id: number }>());
const Action = union([
MyAction,
YourAction,
]);
type Action = typeof Action;
const initialState: MySectionState = {
id: 0,
};
export default function reducer(state: MySectionState = initialState, action: ActionType): MySectionState {
switch(action.type) {
case MyAction.type:
return {
...state,
log: action.error,
};
case YourAction.type:
return {
...state,
id: action.payload.id,
};
default:
return state;
}
}
import { MyAction, YourAction } from './mysection';
import { store } from './store';
store.dispatch(MyAction('abcd'));
store.dispatch(YourAction({ id: 5 }));
Prior Arts
This library is highly inspired by ts-action.
However, it uses Object.setPrototypeOf
in its code, and it is really, really bad to performace (see MDN) and have some runtime overheads on the ActionCtor
instances.
This library is intended to be more fast, and be more lighter (therefore, have less APIs...) than ts-action (At least this does not use Object.setPrototypeOf
!). If you find any codes those hurt performance, please report an issue.
API
API Types
Types used in this library.
TypeOnlyAction
Action without any extra properties.
type TypeOnlyAction<T extends string> = {
type: T;
}
PropsAction
Action with extra properties.
type PropsAction<T extends string, P extends object = {}> = {
type: T;
} & P;
PayloadAction
Action with payload
property. This action is easier to create than PropsAction
, although this also has its own cons.
type PayloadAction<T extends string, P = {}> = {
type: T;
payload: P;
}
TypeOnlyActionCreator
ActionCreator made by action
function. This ActionCreator creates TypeOnlyAction
.
interface TypeOnlyActionCreator<T extends string> {
(): TypeOnlyAction<T>;
type: T;
action: TypeOnlyAction<T>;
create(): TypeOnlyAction<T>;
}
PropsActionCreator
This ActionCreator creates PropsAction
.
interface PropsActionCreator<T extends string, P extends object = {}> {
(props: P): PropsAction<T, P>;
type: T;
action: PropsAction<T, P>;
create(props: P): PropsAction<T, P>;
}
PayloadActionCreator
This ActionCreator creates PayloadAction
.
interface PayloadActionCreator<T extends string, P = {}> {
(payload: P): PayloadAction<T, P>;
type: T;
action: PayloadAction<T, P>;
create(payload: P): PayloadAction<T, P>;
}
Action util functions
Functions for defining an action (or rather an action creator). Defined action creators are used to define reducer.
action
function action<T extends string>(type: T): TypeOnlyActionCreator<T>;
function action<T extends string, P extends object = {}>(type: T, p: PropsOpt<P>): PropsActionCreator<T, P>;
function action<T extends string, P = {}>(type: T, p: PayloadOpt<P>): PayloadActionCreator<T, P>;
action
is for creating an ActionCreator
. ActionCreator
is object used for this library. You can create action by calling ActionCreator
itself or calling actionCreator.create
method.
const FixNote = action('FixNote');
const fixNote0 = FixNote.create();
const fixNote1 = FixNote();
console.log(fixNote0);
console.log(fixNote1);
switch (action.type) {
case FixNote.type:
default:
return state;
}
However, if you cannot define payload, this action is useless. You can define it using payload
function.
payload
function payload<P = {}>(): PayloadOpt<P>
payload
is for creating a payload
argument (second argument) of action
function.
const AddNote = action('AddNote', payload<string>());
const addNote = AddNote('This is a new note');
console.log(addNote);
props
function props<P extends object = {}>(): PropsOpt<Omit<P, 'type'>>
props
is for creating a props
argument (second argument) of action
function.
The difference between props
and payload
is that props
injects additional data to action as property, where payload
set additional data to payload
of action. To get more clear understanding, see following example.
const ActionFromProps = action('Example0', props<{ x: number }>());
const ActionFromPayload = action('Example1', payload<{ x: number }>());
console.log(ActionFromProps({ x: 5 }));
console.log(ActionFromPayload({ x: 5 }));
const OnlyForPayload = action('Example2', payload<string>());
const ThisDoesNotWork = action('Example3', props<string>());
console.log(OnlyForPayload('abc'));
const AddError = action('AddError', props<{ error?: string }>());
const addError0 = AddError({});
const addError1 = AddError({ error: 'New error' });
console.log(addError0);
console.log(addError1);
Action type util functions
Functions to define union type (or to do other type-related things).
union
function union<AC extends ActionCreator<string, any>>(arg: Array<AC>): AC['action'];
To define the type including all actions, you need to use this function. This union type is useful when you define a reducer without subreducer
and reducer
functions.
const NoteActions = union([FixNote, AddNote]);
function reducer(state: State = { notes: [] }, action: NoteActions) {
switch(action.type) {
case FixNote.type:
case AddNote.type:
}
}
isType
function isType<AC extends ActionCreator<string, any>>(
action: AnyAction, actionCreators: AC | Array<AC>,
): action is AC['action']
To check an action is specific type or not, you can use this function.
function reducer(state: State = { notes: [] }, action: NoteActions) {
if (isType(action, FixNote)) {
} eles if (isType(action, AddNote)) {
}
}
Reducer util functions
Functions for define redux reducer.
subreducer
function subreducer<S, T extends string, P extends object>(
action: ActionCreator<T, P>, handler: Subreducer<S, T, P>['handler'],
): Subreducer<S, T, P>
Function to define a reducer for specific action.
const fixNoteReducer = subreducer(FixNote, function (state) {
return {
...state,
});
});
const addNoteReducer = subreducer(AddNote, function (state, payload) {
return {
...state,
notes: [...state.notes, payload],
});
});
These Subreducer
s can be merged using reducer
function.
reducer
function reducer<S, SR extends Subreducer<S, string, any>>(
initialState: S, subreducers: Array<SR>,
): Reducer<S>
Function to define a reducer by merging Subreducer
cases.
export reducer({ notes: [] }, [fixNoteReducer, addNoteReducer]);
Code above is similar with
export function reducer(state = { notes: [] }, action) {
switch (action.type) {
case FixNote.type:
case AddNote.type:
default:
return state;
}
}
Author
Junyoung Clare Jang @Ailrun