你如何从 Redux 的类型定义在 TypeScript 中创建强类型的 redux 中间件?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/45339448/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 04:45:40  来源:igfitidea点击:

How do you create strongly typed redux middleware in TypeScript from Redux's type definitions?

typescriptredux

提问by Bernard Hymmen

I have a TypeScript project that uses React and Redux and I'm trying to add some middleware functions. I got started by implementing one from Redux's samples like so:

我有一个使用 React 和 Redux 的 TypeScript 项目,我正在尝试添加一些中间件功能。我首先从 Redux 的示例中实现一个,如下所示:

// ---- 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 just fine but since this is TypeScript I'd like to make it strongly-typed, ideally using the types defined by Redux so I don't have to reinvent and maintain my own. So, here are the relevant excerpts from my index.d.ts file for Redux:

以上工作得很好,但由于这是 TypeScript,我想使它成为强类型,理想情况下使用 Redux 定义的类型,这样我就不必重新发明和维护自己的类型。因此,以下是 Redux 的 index.d.ts 文件的相关摘录:

// ---- 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 bring those types into my Logger method but I'm not having much luck. It seems to me that something like this ought to work:

我试图弄清楚如何将这些类型带入我的 Logger 方法中,但我运气不佳。在我看来,这样的事情应该有效:

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:

但是我收到了这个错误:

Error TS2322: Type '(api: MiddlewareAPI) => (next: Dispatch) => (action: Action) => Action' is not assignable to type 'Middleware'.
Types of parameters 'api' and 'api' are incompatible.
Type 'MiddlewareAPI' is not assignable to type 'MiddlewareAPI'.
Type 'S' is not assignable to type 'MyStore'.

错误 TS2322:类型 '(api: MiddlewareAPI) => (next: Dispatch) => (action: Action) => Action' 不可分配给类型 'Middleware'。
参数“api”和“api”的类型不兼容。
“MiddlewareAPI”类型不能分配给“MiddlewareAPI”类型。
类型“S”不可分配给类型“MyStore”。

I see the <S> generic declared in the type definition, but I've tried a lot of different combinations and I can't seem to figure out how to specify it as MyStore so that it is recognized as the generic type in the rest of the declarations. For example, according to the declaration api.getState() should return a MyStore object. Same thinking applies to the action type <A>, of course.

我看到在类型定义中声明了 <S> 泛型,但是我尝试了很多不同的组合,我似乎无法弄清楚如何将其指定为 MyStore 以便在其余部分中将其识别为泛型类型的声明。例如,根据声明 api.getState() 应该返回一个 MyStore 对象。当然,同样的想法也适用于操作类型 <A>。

回答by Jaro

MyStore is not required.

MyStore 不是必需的。

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

or

或者

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

Have a Nice Dev

有一个不错的开发

回答by u8155716

Here is my solution:

这是我的解决方案:

First is the middleware creator that accepts a todo function as input which is run as core logic for the middleware. The todo function accepts an object that encapsulates store(MiddlewareAPI<S>), next(Dispatch<S>), action(Action<S>)as well as any other your custimized parameters. Please be aware that I use as Middlewareto force the middleware creator to return a Middleware. This is the magic I use to get rid of the trouble.

首先是接受 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;
};

Second part is the definition of my todo function. In this example I write some token into cookie. It is just a POC for Middleware so I don't care about the XSS risk in my codes at all.

第二部分是我的 todo 函数的定义。在这个例子中,我将一些令牌写入 cookie。它只是中间件的 POC,所以我根本不关心代码中的 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);
};

Lastly, here is what it looks of my store configruation.

最后,这是我的商店配置的外观。

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

回答by Martin Backschat

I have a solution that goes like this:

我有一个像这样的解决方案:

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

With the handeActionfunction I can now define middlewares:

使用该handeAction函数,我现在可以定义中间件:

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

Please note that the middlewares can also require additional parameters like services etc (here: DataHub), that are passed in during setup. The store setup looks like this:

请注意,中间件还可能需要在设置期间传入的其他参数,如服务等(此处: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);

Hope this helps.

希望这可以帮助。

回答by Bernardo Sunderhus

I just Passed through the same problem as you!

我刚刚和你遇到了同样的问题!

Solved it by putting the last function between parenthesis and then forcing it's type to be Dispatch<EffectAction>

通过将最后一个函数放在括号之间然后强制它的类型来解决它 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>