typescript - ตรวจสอบประเภทของอาเรย์ที่มีทูเพิล
ฉันกำลังพยายามเขียนและตรวจสอบการพิมพ์ของอาเรย์ของทูเพิลที่จับคู่ตัวสร้างการกระทำกับรีดิวเซอร์ของมัน และจำเป็นต้องตรวจสอบว่า:
- ว่า Payload ของ action เหมือนกับ Payload ของ reducer
- ตัวลดทั้งหมดยอมรับสถานะเดียวกัน
แนวคิดคือการเขียนมันในลักษณะที่ไม่ขึ้นกับไลบรารีใด ๆ และโดยไม่ต้องบำรุงรักษารายการ payload ที่ถูกต้อง
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],
];
สนามเด็กเล่น TypeScript
ความพยายามของฉัน
ดังนั้นจึงชัดเจนสำหรับฉันว่าฉันต้องการบริบททั่วไปสำหรับแต่ละรายการ ฉันได้ลอง:
เรียกใช้ฟังก์ชันสำหรับแต่ละรายการตามลำดับ
สิ่งนี้ใช้ได้ แต่ฉันขอแสดงความชอบที่จะอธิบายวิธีแก้ปัญหาโดยใช้โครงสร้างข้อมูลเท่านั้น
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>>>;
คำตอบ #1
คุณสามารถทำสิ่งนั้นได้จริง ๆ กับประเภทที่คุณได้กำหนดไว้แล้ว ใช้ประเภทของคุณ ActionReducerPair ในสหภาพ (ActionReducerPair<State, A> | ActionReducerPair<State, B>)[] เพื่อกำหนดคู่ที่ถูกต้องทั้งหมด
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,
];
const reducers: (ActionReducerPair<State, A> | ActionReducerPair<State, B>)[] =
[
[actionA, reducerA], // okay
[actionB, reducerB], // okay
[actionB, reducerA], // error!
[actionA, reducerB], // error!
];
สนามเด็กเล่น TypeScript
คำตอบ #2
ประเภทตามเงื่อนไข
ฉันจะแก้ปัญหานี้โดยใช้ประเภทเงื่อนไขเชิงแจกแจงบนอาร์เรย์:
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 PossiblePayloads = A | B;
type ReducerArray<State, T extends PossiblePayloads> = T extends PossiblePayloads ? ActionReducerPair<State, T>[] : never;
const reducers: ReducerArray<State, PossiblePayloads> = [
// These are OK
[actionA, reducerA],
[actionB, reducerB],
// These now give an error
[actionB, reducerA],
[actionA, reducerB],
];
โดยพื้นฐานแล้ว ประเภท ReducerArray จะอนุญาตให้ TypeScript ตรวจสอบประเภท Payload กับประเภท ActionReducerPair ที่สอดคล้องกัน
อย่างชัดเจน วิธีแก้ปัญหานี้จะใช้ได้ก็ต่อเมื่อคุณสามารถรักษาประเภท ReducerArray ไว้ได้
แก้ไข: เปลี่ยนโค้ดให้ใช้ประเภทเงื่อนไขแบบแจกแจงเพื่อทำให้การบำรุงรักษาง่ายขึ้น
