How to create strongly typed middleware middleware in TypeScript from Redux type definitions?

I have a TypeScript project that uses React and Redux, and I'm trying to add some middleware features. I started using one of the Redux examples:

// ---- middleware.ts ----
export type MiddlewareFunction = (store: any) => (next: any) => (action: any) => any;

export class MyMiddleWare {
    public static Logger: MiddlewareFunction = store => next => action => {
        // Do stuff
        return next(action);
    }
}

// ---- main.ts ---- 
import * as MyMiddleware from "./middleware";

const createStoreWithMiddleware = Redux.applyMiddleware(MyMiddleWare.Logger)(Redux.createStore);

The above works fine, but since it is TypeScript, I would like to make it strongly typed, ideally using types defined by Redux, so I don't need to invent and maintain my own. So, here are the relevant excerpts from my index.d.ts file for Redux:

// ---- index.d.ts from Redux ----
export interface Action {
    type: any;
}

export interface Dispatch<S> {
    <A extends Action>(action: A): A;
}

export interface MiddlewareAPI<S> {
    dispatch: Dispatch<S>;
    getState(): S;
}

export interface Middleware {
    <S>(api: MiddlewareAPI<S>): (next: Dispatch<S>) => Dispatch<S>;
}

I'm trying to figure out how to cast these types to my Logger method, but I'm not very lucky. It seems to me that something like this should work:

interface MyStore {
    thing: string;
    item: number;
}

interface MyAction extends Action {
    note: string;
}

export class MyMiddleWare {
    public static Logger: Middleware = (api: MiddlewareAPI<MyStore>) => (next: Dispatch<MyStore>) => (action: MyAction) => {
        const currentState: MyStore = api.getState();
        const newNote: string = action.note;
        // Do stuff
        return next(action);
    };
}

but instead I get this error:

TS2322: '(api: MiddlewareAPI) = > (: ) = > (: ) = > ' 'Middleware'.
"api" "api" .
   "MiddlewareAPI" "MiddlewareAPI".
     'S' 'MyStore'.

<S> generic, , , , MyStore, . , api.getState() MyStore. <A> , .

+6
4

MyStore .

export const Logger: Middleware =
  (api: MiddlewareAPI<void>) => 
  (next: Dispatch<void>) => 
  <A extends Action>(action: A) => {
    // Do stuff
   return next(action);
  };

export const Logger: Middleware = api => next => action => {
  // Do stuff
  return next(action);
};

Dev

+4

, :

export type StateType = { thing: string, item: number };

export type ActionType =
    { type: "MY_ACTION", note: string } |
    { type: "PUSH_ACTIVITIY", activity: string };

// Force cast of generic S to my StateType
// tslint:disable-next-line:no-any
function isApi<M>(m: any): m is MiddlewareAPI<StateType> {
    return true;
}

export type MiddlewareFunction =
    (api: MiddlewareAPI<StateType>, next: (action: ActionType) => ActionType, action: ActionType) => ActionType;

export function handleAction(f: MiddlewareFunction): Middleware {
    return <S>(api: MiddlewareAPI<S>) => next => action => {
        if (isApi(api)) {
            // Force cast of generic A to my ActionType
            const _action = (<ActionType>action);
            const _next: (action: ActionType) => ActionType = a => {
                // Force cast my ActionType to generic A
                // tslint:disable-next-line:no-any
                return next(<any>a);
            };
            // Force cast my ActionType to generic A
            // tslint:disable-next-line:no-any
            return f(api, _next, _action) as any;
        } else {
            return next(action);
        }
    };
}

handeAction middlewares:

// Log actions and state.thing before and after action dispatching
export function loggingMiddleware(): Middleware {
    return handleAction((api, next, action) => {
        console.log(" \nBEGIN ACTION DISPATCHING:");
        console.log(`----- Action:    ${JSON.stringify(action)}\n`);
        const oldState = api.getState();

        const retVal = next(action);

        console.log(` \n----- Old thing: ${oldState.thing}`);
        console.log(`----- New thing: ${api.getState().thing)}\n`);
        console.log("END ACTION DISPATCHING\n");

        return retVal;
    });
}

// Another middleware...
export interface DataHub = { ... }:
export function dataHandlingMiddleware(datahub: DataHub): Middleware {
    return handleAction((api, next, action) => {
        switch (action.type) {
            case "PUSH_ACTIVITY": {
                handlePushActivities(action.activity, api, /* outer parameter */ datahub);
                break;
            }
            default:
        }
        return next(action);
    });
}

, , .. (: DataHub), . :

import {
    Store, applyMiddleware, StoreCreator, StoreEnhancer,
    createStore, combineReducers, Middleware, MiddlewareAPI
} from "redux";

const middlewares = [
    dataHandlingMiddleware(datahub),
    loggingMiddleware()];

const rootReducer = combineReducers<StateType>({ ... });
const initialState: StateType = {};

// Trick to enable Redux DevTools with TS: see https://www.npmjs.com/package/redux-ts
const devTool = (f: StoreCreator) => {
    // tslint:disable-next-line:no-any
    return ((window as any).__REDUX_DEVTOOLS_EXTENSION__) ? (window as any).__REDUX_DEVTOOLS_EXTENSION__ : f;
};
const middleware: StoreEnhancer<StateType> = applyMiddleware(...middlewares);
const store: Store<StateType> = middleware(devTool(createStore))(rootReducer, initialState);

, .

0

, !

, , Dispatch<EffectAction>

interface EffectAction extends Action {
  effect<T> (action: T): void
}

const effects: Middleware = (api: MiddlewareAPI<any>) => (next: Dispatch<EffectAction>) => ((action: EffectAction) => {
  if (action.effect instanceof Function) action.effect(action)
  return next(action)
}) as Dispatch<EffectAction>
0

:

, todo , . todo , store(MiddlewareAPI<S>), next(Dispatch<S>), action(Action<S>), custimized. , as Middleware, . , , .

import { MiddlewareAPI, Dispatch, Middleware } from 'redux';
import { Action } from 'redux-actions';

export interface MiddlewareTodoParams<S> {
  store: MiddlewareAPI<S>;
  next: Dispatch<S>;
  action: Action<S>;
  [otherProperty: string]: {};
}

export interface MiddlewareTodo<S> {
  (params: MiddlewareTodoParams<S>): Action<S>;
}

// <S>(api: MiddlewareAPI<S>): (next: Dispatch<S>) => Dispatch<S>;
export const createMiddleware = <S>(
  todo: MiddlewareTodo<S>,
  ...args: {}[]
): Middleware => {
  return ((store: MiddlewareAPI<S>) => {
    return (next: Dispatch<S>) => {
      return action => {
        console.log(store.getState(), action.type);
        return todo({ store, next, action, ...args });
      };
    };
  // Use as Middleware to force the result to be Middleware
  }) as Middleware;
};

- todo. cookie. , XSS .

export type OAUTH2Token = {
  header: {
    alg: string;
    typ: string;
  };
  payload?: {
    sub: string;
    name: string;
    admin: boolean;
  };
};


export const saveToken2Cookie: MiddlewareTodo<OAUTH2Token> = params => {
  const { action, next } = params;
  if (action.type === AUTH_UPDATE_COOKIE && action.payload !== undefined) {
    cookie_set('token', JSON.stringify(action.payload));
  }
  return next(action);
};

, .

const store: Store<{}> = createStore(
  rootReducer,
  // applyMiddleware(thunk, oauth2TokenMiddleware(fetch))
  applyMiddleware(thunk, createMiddleware<OAUTH2Token>(saveToken2Cookie))
);
0

All Articles