web-dev-qa-db-fra.com

Comment créez-vous un middleware redux fortement typé dans TypeScript à partir des définitions de type de Redux?

J'ai un projet TypeScript qui utilise React et Redux et j'essaie d'ajouter des fonctions middleware. J'ai commencé par implémenter un des échantillons de Redux comme ceci:

// ---- 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);

Ce qui précède fonctionne très bien, mais comme il s’agit de TypeScript, j’aimerais le rendre fortement typé, idéalement en utilisant les types définis par Redux afin de ne pas avoir à réinventer et à maintenir le mien. Alors, voici les extraits pertinents de mon fichier index.d.ts pour 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>;
}

J'essaie de comprendre comment intégrer ces types dans ma méthode Logger, mais je n'ai pas beaucoup de chance. Il me semble que quelque chose comme cela devrait fonctionner:

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);
    };
}

mais à la place je reçois cette erreur:

Erreur TS2322: Type '(api: MiddlewareAPI) => (suivant: Dispatch) => (action: Action) => Action' n'est pas assignable au type 'Middleware'.
Les types de paramètres 'api' et 'api' sont incompatibles.
Le type 'MiddlewareAPI' n'est pas assignable au type 'MiddlewareAPI'.
Le type 'S' n'est pas assignable au type 'MyStore'.

Je vois le générique <S> déclaré dans la définition de type, mais j'ai essayé beaucoup de combinaisons différentes et je n'arrive pas à comprendre comment le spécifier en tant que MyStore afin qu'il soit reconnu comme type générique dans le reste des déclarations. Par exemple, selon la déclaration, api.getState () devrait renvoyer un objet MyStore. La même pensée s’applique bien sûr au type d’action <A>.

12
Bernard Hymmen

MyStore n'est pas requis.

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

ou

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

Avoir un gentil dev

8
Jaro

J'ai une solution qui va comme ceci:

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);
        }
    };
}

Avec la fonction handeAction, je peux maintenant définir les 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);
    });
}

Veuillez noter que les middlewares peuvent également nécessiter des paramètres supplémentaires tels que des services, etc. (ici: DataHub), qui sont transmis lors de l'installation .

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);

J'espère que cela t'aides.

2
Martin Backschat

Voici ma solution:

Le premier est le créateur de middleware qui accepte une fonction de tâche en tant qu'entrée, qui est exécutée en tant que logique centrale pour le middleware. La fonction todo accepte un objet qui encapsule store(MiddlewareAPI<S>), next(Dispatch<S>), action(Action<S>) ainsi que tous les autres paramètres personnalisés . Veuillez noter que j'utilise as Middleware pour forcer le créateur de middleware à renvoyer un middleware. C'est la magie que j'utilise pour me débarrasser de mes ennuis.

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;
};

La deuxième partie est la définition de ma fonction todo. Dans cet exemple, j'écris un jeton dans le cookie. C'est juste un POC pour le middleware, donc je ne me soucie pas du tout du risque XSS dans mes codes.

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);
};

Enfin, voici à quoi ressemble la configuration de mon magasin.

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

Je viens de traverser le même problème que vous!

Résolu en mettant la dernière fonction entre parenthèses, puis en forçant son type à être 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