Détail du package

ts-action

cartant63.9kMIT11.0.0

TypeScript action creators for Redux

action, ngrx, redux, redux-observable

readme

ts-action

GitHub License NPM version Build status Greenkeeper badge

What is it?

ts-action is a package for declaring Redux action creators with less TypeScript cruft.

Why might you need it?

I wanted a mechanism for declaring and consuming actions that involved writing as little boilerplate as possible. If you, too, want less cruft, you might find this package useful.

Also, if you are using NgRx or redux-observable, you might find the ts-action-operators package useful, too - it includes an RxJS pipeable ofType operator that can be passed a ts-action action creator.

And there is an alternative implementation for the on function in ts-action-immer - which passed an Immer Draft as the reducer's state argument.

For an in-depth look at TypeScript, Redux and ts-action, have a look at: How to Reduce Action Boilerplate.

Install

Install the package using npm:

npm install ts-action --save

TypeScript version 3.0 or later is required.

Usage

Action creators are declared using the action function:

import { action } from "ts-action";
const foo = action("FOO");

Actions are created using the returned action creator function:

store.dispatch(foo());

For actions with payloads, the payload type is specified using the payload function:

import { action, payload } from "ts-action";
const foo = action("FOO", payload<{ foo: number }>());

and the payload value is specified when creating the action:

store.dispatch(foo({ foo: 42 }));

To have the properties added to the action itself - rather than a payload property - use the props function instead. Or, for more control over the parameters accepted by the action creator, pass a creator function.

Action creators have a type property that can be used to narrow an action's TypeScript type in a reducer.

The action types can be combined into a discriminated union and the action can be narrowed to a specific TypeScript type using a switch statement, like this:

import { action, payload, union } from "ts-action";

const foo = action("FOO", payload<{ foo: number }>());
const bar = action("BAR", payload<{ bar: number }>());
const both = union(foo, bar);

interface State { foo?: number; bar?: number; }
const initialState: State = {};

function fooBarReducer(state: State = initialState, action: typeof both.actions): State {
  switch (action.type) {
  case foo.type:
    return { ...state, foo: action.payload.foo };
  case bar.type:
    return { ...state, bar: action.payload.bar };
  default:
    return state;
  }
}

Or, the package's isType function can be used to narrow the action's type using if statements, like this:

import { action, isType, payload } from "ts-action";

const foo = action("FOO", payload<{ foo: number }>());
const bar = action("BAR", payload<{ bar: number }>());

interface State { foo?: number; bar?: number; }
const initialState: State = {};

function fooBarReducer(state: State = initialState, action: Action): State {
  if (isType(action, foo)) {
    return { ...state, foo: action.payload.foo };
  }
  if (isType(action, bar)) {
    return { ...state, bar: action.payload.bar };
  }
  return state;
}

Or, the package's reducer function can be used to create a reducer function, like this:

import { action, on, payload, reducer } from "ts-action";

const foo = action("FOO", payload<{ foo: number }>());
const bar = action("BAR", payload<{ bar: number }>());

interface State { foo?: number; bar?: number; }
const initialState: State = {};

const fooBarReducer = reducer(
  initialState,
  on(foo, (state, { payload }) => ({ ...state, foo: payload.foo })),
  on(bar, (state, { payload }) => ({ ...state, bar: payload.bar }))
);

API

action

function action<T>(type: T)
function action<T>(type: T, config: unknown)
function action<T>(type: T, creator: (...args: any[]) => object)

The action function returns an action creator. Action creators are functions:

const foo = action("FOO");
const fooAction = foo();
console.log(fooAction); // { type: "FOO" }

The type argument passed to action must be a string literal or have a string-literal type. Otherwise, TypeScript will not be able to narrow actions in a discriminated union.

The type option passed to the action function can be obtained using the creator's static type property:

switch (action.type) {
case foo.type:
  return { ...state, foo: action.payload.foo };
default:
  return state;
}

To define propeties, the action function can be passed a config. The config should be created using the empty, payload, props or fsa functions.

empty

function empty()

The empty function constructs the config for the action function. To declare an action without a payload or properties , call it like this:

const foo = action("FOO", empty());
const fooAction = foo();
console.log(fooAction); // { type: "FOO" }

Actions default to being empty; if only a type is passed to the action call, an empty action is created by default:

const foo = action("FOO");
const fooAction = foo();
console.log(fooAction); // { type: "FOO" }

payload

function payload<T>()

The payload function constructs the config for the action function. To declare action properties within a payload property, call it like this:

const foo = action("FOO", payload<{ name: string }>());
const fooAction = foo({ name: "alice" });
console.log(fooAction); // { type: "FOO", payload: { name: "alice" } }

fsa

function fsa<T>()

The fsa function constructs the config for the action function. To declare action properties within a payload property, call it like this:

const foo = action("FOO", fsa<{ name: string }>());
const fooAction = foo({ name: "alice" });
console.log(fooAction); // { type: "FOO", payload: { name: "alice" }, error: false, meta: undefined }

The action creator returns a Flux Standard Action and can also be passed an Error instance:

const fooAction = foo(new Error("Kaboom!"));
console.log(fooAction); // { type: "FOO", payload: Error("Kaboom!"), error: true, meta: undefined }

props

function props<T>()

The props function constructs the config for the action function. To declare action properties at the same level as the type property, call it like this:

const foo = action("FOO", props<{ name: string }>());
const fooAction = foo({ name: "alice" });
console.log(fooAction); // { type: "FOO", name: "alice" }

The props function is similar to the payload function, but with props, the specified properties are added to the action itself - rather than a payload property.

creator

Instead of passing a config to the action function, a creator function can be passed like this:

const foo = action("FOO", (name: string) => ({ name }));
const fooAction = foo("alice");
console.log(fooAction); // { type: "FOO", name: "alice" }

Passing a creator function offers more control over property defaults, etc.

union

The union function can be used to infer a union of actions - for type narrowing using a discriminated union. It's passed action creators and returns a value that can be used with TypeScript's typeof operator, like this:

const both = union(foo, bar);
function reducer(state: any = [], action: typeof both.actions): any {
  switch (action.type) {
  case fFoo.type:
    return ... // Here the action will be narrowed to Foo.
  case bar.type:
    return ... // Here the action will be narrowed to Bar.
  default:
    return state;
  }
}

union can be used if more than three action creators need to be passed to on.

isType

isType is a TypeScript type guard that will return either true or false depending upon whether the passed action is of the appropriate type. TypeScript will narrow the type when used with an if statement.

For example:

if (isType(action, foo)) {
  // Here, TypeScript has narrowed the type, so the action is strongly typed
  // and individual properties can be accessed in a type-safe manner.
}

isType can also be passed multiple action creators:

if (isType(action, foo, bar)) {
  // Here, TypeScript has narrowed the action type to an action created
  // by foo or bar.
}

guard

guard is a higher-order equivalent of isType. That is, it returns a TypeScript type guard that will, in turn, return either true or false depending upon whether the passed action is of the appropriate type. The guard function is useful when dealing with APIs that accept type guards.

For example, Array.prototype.filter accepts a type guard:

const actions = [foo(), bar()];
const filtered = actions.filter(guard(foo));
// TypeScript will have narrowed the type of filtered, so the actions within
// the array are strongly typed and individual properties can be accessed in
// a type-safe manner.

guard can also be passed multiple action creators:

const actions = [foo(), bar(), baz()];
const filtered = actions.filter(guard(foo, bar));

reducer

The reducer function creates a reducer function out of the combined, action-specific reducers declared using the on function.

The on function creates a reducer for a specific, narrowed action and returns an object - containing the created reducer and the types of one or more action creators.

import { action, on, payload, reducer } from "ts-action";

const foo = action("FOO", payload<{ foo: number }>());
const bar = action("BAR", payload<{ bar: number }>());
const fooError = action("FOO_ERROR", payload<{ foo: number, error: {} }>());
const barError = action("BAR_ERROR", payload<{ bar: number, error: {} }>());

interface State { foo?: number; bar?: number; error?: {} }
const initialState: State = {};

const fooBarReducer = reducer(
  initialState,
  on(foo, (state, { payload }) => ({ ...state, foo: payload.foo })),
  on(bar, (state, { payload }) => ({ ...state, bar: payload.bar })),
  on(fooError, barError, (state, { payload }) => ({ ...state, error: payload.error }))
);

If more that three action creators need to be passed to on, you can use union, like this:

on(
  ...union(foo, bar, baz, boo),
  (state, { payload }) => /* whatever */
)

changelog

11.0.0 (2019-10-26)

Breaking changes

  • union now returns an array of metadata describing the passed action creators. Previously, it returned a psuedo value that could be used to obtain the union type. That psuedo value is now a property on the array. So instead of this:

      const both = union(foo, bar);
      type Both = typeof both;

    You will need to do this:

      const both = union(foo, bar);
      type Both = typeof both.actions;
  • Previously, if an action creator was passed to multiple on calls, the reducer associated with the last call was the one that would have been invoked. Now, the reducer associated with the first call will be invoked and a warning will be logged to the console if a creator is passed to more than one on call.

Features

  • union can be used if more than three action creators need to be passed to on, like this:

      on(
        ...union(foo, bar, baz, boo),
        (state, { payload }) => /* whatever */
      )

10.1.0 (2019-06-08)

Changes

  • Prevent type in props and creator return values. (845d444)

10.0.3 (2019-04-28)

Fixes

  • Fix the rest-parameter signature for on. (32f90d1)

10.0.2 (2019-04-24)

Changes

10.0.1 (2019-04-19)

Changes

10.0.0 (2019-04-19)

Breaking changes

  • reducer now takes the intialState as its first parameter, followed by any number of on declarations - using a rest parameter, rather than an array.
  • The types used for the reducer parameters have been simplified, but unless said types have been explicitly referenced, this won't be a breaking change.

9.0.0 (2019-04-06)

Breaking changes

  • Expressing multiple actions using array or object literals is no longer supported. Pass multiple actions as separate arguments instead.

    That is, instead of this:

      union({ foo, bar }); // or union([foo, bar]);

    Do this:

      union(foo, bar);

    This breaking change applies to union, guard, isType and on.

  • Support for class-based action creators has been removed.

8.1.1 (2019-04-04)

Fixes

  • Replace ReturnType with ActionType for TypeScript 3.4 compatibility. (f20ed2e)

8.1.0 (2019-04-04)

Features

  • Support using array literals to represent multiple action types. (f0a98be)

Changes

  • Deprecate using object literals to represent multiple action types.

8.0.0 (2018-11-03)

Breaking changes

  • Action creators are now simple functions - instead of classes.
  • Class-based actions creators are deprecated, but can still be used via the "ts-action/classes" import location.
  • The action creator's static action property has been removed.
  • TypeScript 3.0 is required.

7.0.0 (2018-09-29)

Features

  • Support passing multiple action creators to on. (11e3e73)

Breaking changes

To support passing multiple action creators to on, the return type was changed - which is technically a breaking change. However, if you are using on and reducer in the manner described in the README, this change will not affect you.

  • Changed the return type of on to support multiple actions.
  • Removed the ActionCreator interface.

6.0.3 (2018-07-09)

Fixes

  • reducer: Allow the state parameter in Reducer to be undefined. (b8dab43)

6.0.2 (2018-02-26)

Fixes

6.0.1 (2018-02-04)

Fixes

  • guard: Like isType, the guard function now supports specifying multiple creators. (9658da1)

6.0.0 (2018-02-03)

Breaking Changes

  • isType: To support matching an arbitrary number of types, isType can now be passed either a single action creator or an object literal of action creators, so calls like isType(action, Foo, Bar) should be replaced with calls like isType(action, { Foo, Bar }). Calls that specify a single action creator - like isType(action, Foo) - do not need to be changed. (0bbcf98)

5.0.0 (2018-02-03)

Breaking Changes

  • union: The union function now takes an object literal of action creators, so calls like union(Foo, Bar) should be replaced with calls like union({ Foo, Bar }). (9d453a5)

4.0.0 (2018-01-31)

Breaking Changes

  • action: Removed the overload in which the type could be passed in the options. (742b17d)
  • The package is now distributed with CommonJS, ES5 and ES2015 files (see the Angular Package Format). The ES5 and ES2015 files use ES modules. The entry points in the package.json are:
    • main - CommonJS
    • module - ES5 with ES modules (in the esm5 directory)
    • es2015 - ES2015 (in the esm2015 directory)

3.3.0 (2018-01-29)

Features

  • isType: Add support for passing multiple action creators. (899603f)

3.2.3 (2018-01-27)

Fixes

  • Fix types filename in package.json.

3.2.2 (2017-11-18)

Changes

  • Minor documenation changes.

3.2.1 (2017-11-12)

Bug Fixes

  • reducer: Remove union with undefined from state. (bd748f0)

3.2.0 (2017-11-12)

Features

  • reducer: Add a reducer method for creating reducer functions. (d7e870e)

3.1.3 (2017-11-11)

Changes

  • action: Reset the prototype to that of {} rather than to null. (3337e68)

3.1.2 (2017-11-10)

Changes

  • action: The options are now optional and empty is the default. (c91f3cd)

3.1.1 (2017-11-10)

Documentation

  • Improvemed API documentation.

3.1.0 (2017-11-10)

Feature

  • action: Add a more terse overload that takes the type and options as separate parameters. (083403f)

3.0.1 (2017-11-10)

Changes

  • The distribution now includes .d.ts files rather than .ts files. (b5ebbbf)

3.0.0 (2017-11-09)

Breaking Changes

  • action: Removed the static action property from action creators. Instead, there's a union method for creating discriminated unions from action creators. (22067fb)

2.0.3 (2017-11-09)

Bug Fixes

  • action: Enforce base parameter types. (e3128d2)
  • action: Enforce props parameter type. (1571ee4)

2.0.2 (2017-11-09)

Bug Fixes

  • action: Set prototype to null for reactjs/redux compatibility. (9925f79)

2.0.1 (2017-11-07)

Changes

  • Remove tslib.

2.0.0 (2017-11-06)

Features

  • Add support for inline base classes using base.

Breaking Changes

  • Default payloads and props have been removed.
  • create has been removed.
  • params has been removed.
  • empty must be called when for action creators with no payload or props.
  • Most interfaces have been removed.