我正在尝试编写和类型检查一个元组数组,将动作创建器与其减速器配对,并且需要验证:
这个想法是以与库无关的方式编写它。
type A = string;
type B = number;
type State = { a: A; b: B };
type Action<Payload> = { type: string; payload: Payload };
type ActionFn<Payload> = (payload: Payload) => Action<Payload>;
const actionA: ActionFn<A> = (payload: A) => ({ type: 'A', payload });
const actionB: ActionFn<B> = (payload: B) => ({ type: 'B', payload });
const reducerA = (state: State, a: A): State => ({ ...state, a });
const reducerB = (state: State, b: B): State => ({ ...state, b });
type ActionReducerPair<State, Payload> = [
ActionFn<Payload>,
(state: State, payload: Payload) => State
];
type Reducer<State, Payload> = (state: State, payload: Payload) => State;
const reducers = [
// These are OK
[actionA, reducerA],
[actionB, reducerB],
// These should report an error
// @ts-expect-error
[actionB, reducerA],
// @ts-expect-error
[actionA, reducerB],
];
所以我很清楚,我需要为每个条目提供一个通用上下文。我试过了:
按顺序为每个条目调用一个函数
这是可行的,但是我更愿意仅使用数据结构来表达解决方案。
type Pair<State, Payload> = [
ActionFn<Payload>,
(state: State, payload: Payload) => State
];
const pair = <State, Payload>(
actionCreator: ActionFn<Payload>,
reducerFn: (state: State, payload: Payload) => State
): Pair<State, Payload> => [actionCreator, reducerFn];
const reducerPairs2 = [
// These are OK
pair(actionA, reducerA), // ✅
pair(actionB, reducerB), // ✅
// These should report an error
// @ts-expect-error
pair(actionB, reducerA), // ✅
// @ts-expect-error
pair(actionA, reducerB), // ✅
];
使用类型助手强制执行类型
这不太管用,要么全部通过,要么全部失败。
type ValidPair<MaybeAction, MaybeReducer> =
MaybeAction extends ActionFn<infer Payload>
? MaybeReducer extends Reducer<infer State, Payload>
? ActionReducerPair<State, Payload>
: 'reducer does not satisfy Reducer<S, P>'
: 'action does not satisfy ActionFn<P>';
const reducers = [
// These are OK
[actionA, reducerA], // ✅
[actionB, reducerB], // ✅
// These should give an error
// @ts-expect-error // 💥 no error reported
[actionB, reducerA],
// @ts-expect-error // 💥 no error reported
[actionA, reducerB],
] satisfies Array<ValidPair<ActionFn<any>, Reducer<State, any>>>;
我会在数组上使用条件类型来解决这个问题:
type Reducer<State, Payload> = (state: State, payload: Payload) => State;
const reducerA: Reducer<State, A> = (state: State, a: A): State => ({ ...state, a });
const reducerB: Reducer<State, B> = (state: State, b: B): State => ({ ...state, b });
type ActionReducerPair<State, Payload> = [
ActionFn<Payload>,
Reducer<State, Payload>
];
type ReducerArray<State, T> = T extends A ? ActionReducerPair<State, A>[]
: T extends B ? ActionReducerPair<State, B>[]
: never;
const reducers: ReducerArray<State, A | B> = [
// These are OK
[actionA, reducerA],
[actionB, reducerB],
// These now give an error
[actionB, reducerA],
[actionA, reducerB],
];
本质上,
ReducerArray
类型将允许打字稿将Payload
类型检查为相应的ActionReducerPair
类型。
显然,只有当你能够维持
ReducerArray
类型时,这个解决方案才有效。