【问题标题】:How do you create strongly typed redux middleware in TypeScript from Redux's type definitions?如何根据 Redux 的类型定义在 TypeScript 中创建强类型的 redux 中间件?
【发布时间】:2018-01-02 12:19:53
【问题描述】:

我有一个使用 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);

上面的工作很好,但由于这是 TypeScript,我想让它成为强类型,理想情况下使用 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>;
}

我正试图弄清楚如何将这些类型带入我的 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);
    };
}

但是我得到了这个错误:

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

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

【问题讨论】:

  • 您有没有找到好的解决方案?使用类而不是函数?

标签: typescript redux


【解决方案1】:

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

开发愉快

【讨论】:

  • 这些都不能回答这个问题(我将更新以澄清),因为对 api.getState() 的调用不会返回强类型对象。所有这一切的重点是让编译器遵循定义的类型声明,这样您就不必进行任何额外的转换。对于第一个,您必须这样做: const currentState: MyStore = api.getState() as any as MyStore;对于第二个,您仍然必须这样做: const currentState: MyStore = api.getState() as MyStore;
  • @BernardHymmen 你有没有想过这个问题?
  • @NSjonas - 很抱歉没有早点回复。我的一位同事指出,类型定义使得直接创建强类型中间件在结构上是不可能的。您能做的最好的事情就是像 Martin Backschat 建议的那样实施某种解决方法。这基本上就是我最终做的事情,尽管当时我不知道如何使用“is”创建类型保护,所以我的解决方案不如 Martin 的解决方案好。 FWIW:看起来人们一直在解决这个问题 (github.com/reactjs/redux/pull/2563),但我还没有尝试过这些更改。
  • 不确定自 2017 年 11 月以来类型定义是否发生了变化,但(现在?)可以从 store.getState() 中获取类型化对象。 MiddlewareAPI 接口如下所示: interface MiddlewareAPI { dispatch: D getState(): S } 因此,将应用程序的状态作为第二个泛型传递将导致来自 store.getState( )。
【解决方案2】:

这是我的解决方案:

First 是中间件创建者,它接受 todo 函数作为输入,该函数作为中间件的核心逻辑运行。 todo 函数接受一个对象,该对象封装了store(MiddlewareAPI&lt;S&gt;)next(Dispatch&lt;S&gt;)action(Action&lt;S&gt;) 以及您自定义的任何其他参数。 请注意,我使用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。它只是中间件的 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);
};

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

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

【讨论】:

    【解决方案3】:

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

    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 函数,我现在可以定义中间件:

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

    希望这会有所帮助。

    【讨论】:

      【解决方案4】:

      这是一种中间件类型,可以让您不必对 curried 函数进行注释:

      import type { Dispatch, AnyAction } from 'redux'
      
      export interface MiddlewareAPI<S, E extends AnyAction> {
        dispatch: Dispatch<E>
        getState(): S
      }
      
      export type Middleware<S, E extends AnyAction> =
        (api: MiddlewareAPI<S, E>) =>
        (next: Dispatch<E>) =>
        (event: E) => ReturnType<Dispatch<E>>
      
      const middleware: Middleware<MyStore, MyEvent> = (api) => (next) => (event) => {
        // ...
      }
      

      【讨论】:

      • 这给了我:Type 'Dispatch&lt;E&gt;' does not satisfy the constraint 'Dispatch&lt;AnyAction&gt;'. Type 'AnyAction' is not assignable to type 'E'. 'AnyAction' is assignable to the constraint of type 'E', but 'E' could be instantiated with a different subtype of constraint 'AnyAction'.ts(2344)
      • @Tom,谢谢,我已经修好了。以前的版本可能停止了打字稿更新。
      【解决方案5】:

      我刚遇到和你一样的问题!

      通过将最后一个函数放在括号之间然后强制它的类型为Dispatch&lt;EffectAction&gt;来解决它

      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>
      

      【讨论】:

        猜你喜欢
        • 2018-08-31
        • 1970-01-01
        • 1970-01-01
        • 2016-10-09
        • 2019-10-25
        • 2021-10-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多