我使用typescript的 ReturnType 功能找到了关于类型安全 redux 的很棒的解决方案,这是 2.8 版的新功能。
动作/types.ts
type FunctionType = (...args: any[]) => any;
type ActionCreatorsMapObject = { [actionCreator: string]: FunctionType };
export type ActionUnion<A extends ActionCreatorsMapObject> = ReturnType<A[keyof A]>;
模型/用户.ts
export interface User {
id: number;
username: string;
name: string;
}
动作/index.ts
import { User } from '../models/user';
interface Action<T extends string> {
type: T;
}
interface ActionWithPayload<T extends string, P> extends Action<T> {
payload: P;
}
export function createAction<T extends string>(type: T): Action<T>;
export function createAction<T extends string, P>(type: T, payload: P): ActionWithPayload<T, P>;
export function createAction<T extends string, P>(type: T, payload?: P) {
return payload === undefined ? { type } : { type, payload };
}
export enum ActionTypes {
SIMPLE_ACTION = 'SIMPLE_ACTION',
ASYNC_ACTION = 'ASYNC_ACTION
}
export const Actions = {
simpleAction: (value: string) => createAction(ActionTypes.SIMPLE_ACTION, value)
asyncAction: () => {
const token = localStorage.getItem('token');
const request = axios.get('/', {
headers: {
'Authorization': token
}
});
return createAction(ActionTypes.ASYNC_ACTION, request as AxiosPromise<User>);
},
anotherAction: (value: number) => blahblah...
};
export type Actions = ActionUnion<typeof Actions>;
在进入 reducer 之前,我使用的是redux-promise包,它是 redux 的中间件来处理异步调度。简单地说,如果 payload 是 Promise,那么redux-promise将解析该 Promise 并将 payload 值更改为 Promise 的结果。
这是问题所在。我想在写reducer 代码的时候使用ActionUnion类型。但是打字稿不知道承诺已经被中间件解决了。非异步操作效果很好,因为中间件不会更改有效负载。
user_reducer.ts
import { User } from '../models/user';
import * as fromActions from '../actions';
interface UserState {
loginUser: User | null;
someValue: string;
}
const initialState: UserState = {
loginUser: null,
someValue: ''
};
export default (state: UserState = initialState, action: fromActions.Actions) => {
switch (action.type) {
case fromActions.ActionTypes.SIMPLE_ACTION: {
// typescript knows that action.payload is string. It works well.
const data = action.payload;
...
}
case fromActions.ActionTypes.ASYNC_ACTION: {
// typescript knows that action.payload is still promise.
// Therefore typescript said, action.payload.data is wrong.
const username = action.payload.data.username;
...
}
...
}
};
这不是错误。这很明显,因为我定义了action: fromActions.Action,所以打字稿会认为“哦,action 参数的类型是 asyncAction 函数的 ReturnValue 并且它有 promise 对象作为有效负载值。”。
我认为有两种解决方案。
1.旧式
在 2.8 之前,我们通常会定义每个 ActionCreator 的接口并将它们联合起来。
export type Actions = ActionCreator1 | ActionCreator2 | ActionCreator3 | ...;
它可以解决中间件问题,但是如果我更改动作创建函数的返回值,那么我必须手动更改匹配的接口。(这就是为什么新的ReturnValue功能很棒的原因。)
2.重新定义Action类型
而不是使用,
export type Actions = ActionUnion<typeof Actions>;
让我们定义具有承诺作为有效负载值的 Action 类型。比方说 ActionWithPromisePayload。下面是伪代码。我不擅长打字。(T_T)
// Let's define two new types.
type ActionWithPromisePayload<...>;
type ActionWithPromiseResolvedPayload<...>;
ActionWithPromisePayload用于检查操作对象的有效负载是否具有承诺。 ActionWithPromiseResolvedPayload用于重新定义动作类型,因为承诺由中间件解决。
然后,使用2.8 中新增的条件类型重新定义类型Actions 。
如果Action 有promise 对象作为payload,那么它的真实类型不是action 的ReturnValue,而是resolved 的类型。下面是伪代码。
export type Actions =
ActionUnion<typeof Actions> extends ActionWithPromisePayload<..> ? ActionWithPromiseResolvedPayload<..> : ActionUnion<typeof Actions>;
问题有点乱,但关键问题是,我怎样才能很好地定义减速器的动作参数类型并与中间件一起工作?
如果有更好的方法,那么不要关心我的解决方案。谢谢。
参考。https://medium.com/@martin_hotell/improved-redux-type-safety-with-typescript-2-8-2c11a8062575