Refine the actions in TypeScript context.
- Gain type-safe without extra pain, which includes:
- Uniting actions together for narrowing the type later in 'reducer' or anywhere
- Enforcing the
type
to be string literal.
- Simple to understand, easy to use
- No magic.
ActionCreator
could be implemented in 4 lines of pretty-printed code. - ActionCreator-like API, easy to create an action.
- FSA is no longer important in TypeScript context.
- Firstly,
meta
anderror
have rarely usecases. - And, TypeScript makes you trust that the
payload
is type-safe without checkingerror
. - It's better to nest actions rather than to mark
meta
anderror
besidespayload
.
- Firstly,
- No magic.
- Reduce boilerplate code without the one-to-one mapping assumption
- Unlike some redux utilities,
the-actions
doesn't change the way to create a 'reducer'.
- Unlike some redux utilities,
- Use
type
no more than ONCE- What's the point of using the
type
anywhere? No point at all! - Once specified, never to use anymore.
- Or there could be a babel-plugin to generate
type
.
- What's the point of using the
- Non-exclusive introducing
the-actions
doesn't reject to work with other-system actions if they do too.
- More general usage
the-actions
can be integrated into redux (reducer), redux-saga (take*), react hooks (useReducer), and anything need to mark a type for channeling, which exceeds the react & redux ecosystem.
// create an action creator
import { ActionCreator } from "the-actions";
const setText = ActionCreator<{ text: string }>();
// create an action
const setTextAction = setText({ text: "hello, world" });
// usage in redux reducer
const initState = { text: "" };
const reducer = (state = initState, action: unknown) => {
if (setText.match(action)) {
return { ...state, text: action.payload.text };
}
return state;
};
// usage in react-redux connect
import { bindActionCreators, Dispatch } from "redux";
const mapDispatchToProps = (dispatch: Dispatch) =>
bindActionCreators(
{
setText,
},
dispatch,
);
// usage in redux-saga
import { call, takeLatest } from "redux-saga/effects";
function* saga() {
yield takeLatest(setText.match, sampleListener);
}
function* sampleListener(action: ReturnType<typeof setText>) {
yield call(console.log, action.payload.text);
}
// usage in react useReducer hook
import React, { useReducer } from "react";
const InputComponent = () => {
const [state, dispatch] = useReducer(reducer, initState);
return (
<input
value={state.text}
onChange={(e: React.ChangeEvent<HtmlInputElement>) => {
dispatch(setText(e.target.value));
}}
/>
);
};
Yes, there's many action utilities published. And some of them are designed for typescript.
up to now, there're many in npm, includes typesafe-actions
, typescript-fsa
, ts-action
, typed-redux-actions
, create-action-ts
, redux-action-class
...
Take the most typical for examples,
It's almost what I want!
The core mechanisms are the same except the-actions
is not fully FSA-compliant. In fact, the-actions
loaded meta
and error
off to be simple.
By the way, it's a amazing coincidence that typescript-fsa
and the-actions
took the same word "match" to name the type predicate.
Another difference is that the-actions
doesn't treat the "Async Action Creators" as first-class concept. In the-actions
, "Async Action Creators" is a sample of "Action Creator Group", which is a composition of action creators.
Further more, when creating an action creator in typescript-fsa
, you MUST pass a type
for identify the action creator. It's no problem but lose the chance to entirely avoid specifying a unique string.
The core mechanisms to be type-safe are really different.
The way of typesafe-actions
is troublesome.
-
type
MUST BE string literal (Ability Limited)It stopped writing prefix (namespace) for the action type.
-
Actions SHOULD BE united first (Boilerplate Code)
That is, you, the app developper, should define the universal set of action for every reducer. And then, typescript helps narrowing the scope from the universal set.
So, typesafe-actions
paid too much for type-safe.