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

时间:2017-07-27 00:26:37

标签: typescript redux

我有一个使用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定义的类型,所以我不必重新发明和维护自己的类型。所以,这里是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)=&gt; (下一篇:Dispatch)=&gt; (行动:行动)=&gt; “行动”不能分配给“中间件”类型   参数“api”和“api”的类型不兼容     类型'MiddlewareAPI'不能分配给'MiddlewareAPI'类型       类型“S”不能指定为“MyStore”类型。

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

5 个答案:

答案 0 :(得分:8)

不需要MyStore。

var propertyname = 'house';

var content = document.createElement('div');
var img = document.createElement('img');

if (propertyname == 'house') {
  img.src = 'https://placehold.it/300x100';
}

content.appendChild(img);
document.getElementsByTagName('body')[0].appendChild(content);

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

有一个好的开发

答案 1 :(得分:2)

我有一个类似的解决方案:

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

希望这有帮助。

答案 2 :(得分:2)

这是我的解决方案:

首先是中间件创建者接受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;
};

第二部分是我的待办事项功能的定义。在这个例子中,我将一些令牌写入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 :(得分: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>

答案 4 :(得分:0)

这是一种中间件类型,使您不必注释已管理的函数:

import { Dispatch, MiddlewareAPI, AnyAction } from 'redux'
import { MyStore, MyEvent } from 'src/store'

type Middleware<S, E extends AnyAction> =
  (api: Dispatch<E> extends Dispatch<AnyAction> ? MiddlewareAPI<Dispatch<E>, S> : never) =>
  (next: Dispatch<E>) =>
  (event: E) => ReturnType<Dispatch<E>>

const middleware: Middleware<MyStore, MyEvent> = (api) => (next) => (event) => {
  // ...
}