Typescript and Redux. Best tips.

Introduction

Hello everybody!

Today I want to talk about quite popular technologies. Typescript and Redux. Both helps to develop fault tolerant applications. There is a lot of approaches to write typings of state and actions. I formed own, that could save your time.

State

Each state in Redux is immutable. So let's use Readonly modifier. It makes all properties readonly. You can't mutate property in reducer.

export type State = Readonly<{
  value: number;
}>

But what about arrays. For example:

export type State = Readonly<{
  list: number[];
}>

You still can change it. Let's fix it, TypeScript includes special modifier ReadonlyArray .

export type State = Readonly<{
  list: ReadonlyArray<number>;
}>

Now you can't add or remove items. You have to create new array for changes.

Actions

I use enums for Redux actions. Naming convention is simple: @namespace/effect .

enum Action {
  ValueChanged = '@counter/ValueChanged',
}

Action creators

Little magic starts.

The first thing, we use const assertions . The const assertion allowed TypeScript to take the most specific type of the expression.

The second thing, we extract return types of action creators by type inference .

const actions = {
  setValue(value: number) {
    return {
      payload: value,
      type: Action.ValueChanged,
    } as const;
  },
}

type InferValueTypes<T> = T extends { [key: string]: infer U } ? U : never;

type Actions = ReturnType<InferValueTypes<typeof actions>>;

Reducers

Inside reducer we just use things described before.

const DEFAULT_STATE: State = 0;

function reducer(state = DEFAULT_STATE, action: Actions): State {
  if (action.type === Action.ValueChanged) {
    return action.payload;
  }

  return state;
}

Now, for all of your critical changes inside action creators, TypeScript throws error inside reducer. You will have to change your code for correct handlers.

Module

Each module exports object like this:

export const Module = {
  actions,
  defaultState: DEFAULT_STATE,
  reducer,
}

You can also describe your saga inside module, if you use redux-saga .

Configure store

Describe the whole state of application, all actions and store.

import { Store } from 'redux';

type AppState = ModuleOneState | ModuleTwoState;
type AppActions = ModuleOneActions | ModuleTwoActions;

type Store = Store<AppState, AppActions>;

Hooks

If you use hooks from react-redux , it would be helpful too.

By default you need to describe typings each time, when you use this hooks. Better to make it one time.

export function useAppDispatch() {
  return useDispatch<Dispatch<AppActions>>();
}

export function useAppSelector<Selected>(
  selector: (state: AppState) => Selected,
  equalityFn?: (left: Selected, right: Selected) => boolean,
) {
  return useSelector<AppState, Selected>(selector, equalityFn);
}

Now you can't dispatch invalid action.

The end

I hope all of this things will make your live easier.

I will glad for your comments and questions.

My twitter .

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章