ค้นหาเว็บไซต์

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 ไว้ได้

แก้ไข: เปลี่ยนโค้ดให้ใช้ประเภทเงื่อนไขแบบแจกแจงเพื่อทำให้การบำรุงรักษาง่ายขึ้น