如何从 Redux 的类型定义在 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:中间件 API(=>(下一个:调度(=>(操作:操作(=> 操作"不可分配给类型"中间件"。
参数"api"和"api"的类型不兼容。类型"中间件API
"不能分配给类型"中间件API"。
类型"S"不能分配给类型"我的商店"。

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

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

有一个不错的开发

这是我的解决方案:

首先是中间件创建者,它接受待办事项函数作为输入,该函数作为中间件的核心逻辑运行。todo 函数接受一个封装store(MiddlewareAPI<S>)next(Dispatch<S>)action(Action<S>)以及任何其他托管参数的对象。 请注意,我使用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))
);

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

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

希望这有帮助。

这是一个中间件类型,它使您不必注释柯里函数:

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) => {
// ...
}

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

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

最新更新