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

typescript - อนุมานพารามิเตอร์ของฟังก์ชันโอเวอร์โหลด


เป็นไปได้หรือไม่ที่จะ สรุป พารามิเตอร์ของฟังก์ชันโอเวอร์โหลด?

ตัวอย่างเช่น:

type MyFunction = {
  (key: "one", params: { first: string }): void;
  (key: "two", params: { second: string }): void;
}

type MyFunctionParams<T extends string> = MyFunction extends (
  key: T,
  params: infer P,
) => void
  ? P
  : never;

// should be `{ first: string }`
type OneParams = MyFunctionParams<"one"> // never

สนามเด็กเล่น Typescript

คำตอบ #1

ถ้า คุณควบคุมคำนิยามของ MyFunction ผมจะทำตามวิธีที่ยอดเยี่ยมของ caTS ในการเข้าหามันจากมุมมองอีกทางหนึ่ง (บางครั้งมันน่าทึ่งมากว่าถ้าคุณเปลี่ยนจุดเริ่มต้นของคุณ คุณสามารถทำให้บางสิ่งง่ายขึ้นหรือแม้กระทั่งทำให้บางสิ่งเป็นไปได้ซึ่งเดิมทีไม่สามารถทำได้ โดยมักไม่มีผลลบใด ๆ)

อย่างไรก็ตาม หากคุณต้องใช้ประเภท MyFunction ที่มีอยู่แล้ว ดังนั้น: มีสองวิธีแก้ปัญหาที่เป็นไปได้สำหรับคุณ วิธีหนึ่งทั่วไปและอีกวิธีหนึ่งที่เน้นการใช้งานอย่างสมเหตุสมผล ทั้งสองวิธีพูดตรง ๆ แล้วก็ไม่ค่อยน่าพอใจเท่าที่เราทุกคนคงต้องการ:

คำตอบทั่วไป

น่าเสียดายที่มันยากมากและยืดยาว — แต่สามารถทำได้ — ในการเข้าถึงข้อมูลพารามิเตอร์ (หรือข้อมูลชนิดการคืนค่า) ของประเภทฟังก์ชันที่มีการโอเวอร์โหลด ตามที่อธิบายในคำตอบของคำถามนี้ มันเป็นเพียงข้อจำกัดของ Parameters และ ReturnType ที่พวกมันไม่สามารถจัดการกับการโอเวอร์โหลดในแบบที่เราต้องการ คำถามของคุณเป็นตัวอย่างที่ดีของเรื่องนั้น นี่คือตัวอย่างที่ง่ายกว่าหนึ่งตัว (playground):

type MyFunction = {
    (key: "one", params: { first: string }): void;
    (key: "two", params: { second: string }): void;
};

type MyFunctionKey = Parameters<MyFunction>[0];
//   ^? type MyFunctionKey = "two"
// Huh?!? I think most of us would expect `"one" | "two"`, but
// that's just not the way it is. (There's usually a good reason
// for these limitations in the general case, but...)

แต่ก็ เป็นไปได้ ในกรอบความเหมาะสม โดยที่ "ความเหมาะสม" ในกรณีนี้หมายถึงจำนวนสูงสุดของการโอเวอร์โหลดของฟังก์ชันที่สมเหตุสมผล ให้เรากำหนดหกเป็นจำนวนสูงสุดของการโอเวอร์โหลด

ดังที่คุณเห็นจากคำตอบของทิเชียนต่อคำถามนั้น เป็นไปได้ที่จะทราบว่าฟังก์ชันมีการโอเวอร์โหลดกี่แบบ และสามารถสรุปรายการพารามิเตอร์ของพวกมัน ตัวอย่างเช่น:

type Whatever<F> =
    F extends {
        (...args: infer Params1): any);
        (...args: infer Params2): any);
    }
        ? // This branch is used if F has **exactly** two overloads
        : // Otherwise this one is
;

เราสามารถใช้กลไกนั้นในการจัดการ 2, 3, 4, 5...จนถึงขีดจำกัดที่สมเหตุสมผลที่คุณต้องการของชุดการโอเวอร์โหลด ดังนั้นนี่คือส่วนแรกของปริศนาของเรา MyFunctonParams ที่กระจายตามจำนวนโอเวอร์โหลดที่มีไปยังประเภทผู้ช่วยพารามิเตอร์:

type MyFunctionParams<KeyType> =
    MyFunction extends {
        (...args: infer P1): any;
        (...args: infer P2): any;
    }
        ? ParamsHelper<KeyType, P1, P2>
        : MyFunction extends {
              (...args: infer P1): any;
              (...args: infer P2): any;
              (...args: infer P3): any;
          }
        ? ParamsHelper<KeyType, P1, P2, P3>
        : MyFunction extends {
              (...args: infer P1): any;
              (...args: infer P2): any;
              (...args: infer P3): any;
              (...args: infer P4): any;
          }
        ? ParamsHelper<KeyType, P1, P2, P3, P4>
        : MyFunction extends {
              (...args: infer P1): any;
              (...args: infer P2): any;
              (...args: infer P3): any;
              (...args: infer P4): any;
              (...args: infer P5): any;
          }
        ? ParamsHelper<KeyType, P1, P2, P3, P4, P5>
        : MyFunction extends {
              (...args: infer P1): any;
              (...args: infer P2): any;
              (...args: infer P3): any;
              (...args: infer P4): any;
              (...args: infer P5): any;
              (...args: infer P6): any;
          }
        ? ParamsHelper<KeyType, P1, P2, P3, P4, P5, P6>
        : never;

งานของ ParamsHelper คือการจับคู่ KeyType ที่เรามอบให้กับองค์ประกอบตัวแรกของทูเพิลพารามิเตอร์แต่ละตัวที่เราส่งให้มัน (key ของคุณ) และให้ประเภทขององค์ประกอบที่สอง (พารามิเตอร์ของคุณ params)

type ParamsHelper<
    KeyType,
    P1 extends any[],
    P2 extends any[] = [never, never],
    P3 extends any[] = [never, never],
    P4 extends any[] = [never, never],
    P5 extends any[] = [never, never],
    P6 extends any[] = [never, never]
> = KeyType extends P1[0]
    ? P1[1]
    : KeyType extends P2[0]
    ? P2[1]
    : KeyType extends P3[0]
    ? P3[1]
    : KeyType extends P4[0]
    ? P4[1]
    : KeyType extends P5[0]
    ? P5[1]
    : KeyType extends P6[0]
    ? P6[1]
    : never;

เท่านี้ก็เสร็จแล้ว! ตอนนี้เราได้ผลลัพธ์ที่ต้องการแล้ว ตราบใดที่ MyFunction ไม่มีการโอเวอร์โหลดมากกว่าหกครั้ง (หรือจำกัดตามที่คุณตัดสินใจตั้ง):

type OneParams = MyFunctionParams<"one">;
//   ^? type OneParams = { first: string; }

type TwoParams = MyFunctionParams<"two">;
//   ^? type TwoParams = { second: string; }

ตัวอย่างสนามเด็กเล่น

ในตัวอย่างนั้น MyFunctionParams ถูกเชื่อมโยงกับ MyFunction แต่เราสามารถสร้างเวอร์ชันทั่วไปได้ถ้าเราต้องการ โดยที่เราส่งประเภทฟังก์ชันเข้าไป ดูเหมือนจะเกินไปสำหรับตัวอย่างของคุณ แต่ตรงนี้ก็มีให้ดูแล้ว

โซลูชันที่มุ่งเป้าหมายแบบปฏิบัติได้

ถ้าเรา เพียงต้องการ จัดการ MyFunction เราอาจใช้วิธีที่สั้นและมุ่งเน้นมากขึ้น เช่นนี้:

type MyFunction1 = {
    (key: "one", params: { first: string }): void;
};
type MyFunction2 = {
    (key: "two", params: { second: string }): void;
};
type MyFunction = MyFunction1 & MyFunction2;

type MyFunctionParams<KeyType> =
    KeyType extends Parameters<MyFunction1>[0]
    ? Parameters<MyFunction1>[1]
        : KeyType extends Parameters<MyFunction2>[0]
        ? Parameters<MyFunction2>[1]
            : never;

type OneParams = MyFunctionParams<"one">;
//   ^? type OneParams = { first: string; }

type TwoParams = MyFunctionParams<"two">;
//   ^? type TwoParams = { second: string; }

ลิงก์สนามเด็กเล่น

ไม่ค่อยน่าพอใจเท่าไหร่ และแน่นอนว่า ทุกครั้งที่คุณเพิ่มการโอเวอร์โหลดใหม่ คุณต้องแก้ไขชนิด MyFunctionParams ให้รองรับมัน แต่บางครั้งอะไรที่เรียบง่ายและตรงจุดก็อาจดีกว่า ขึ้นอยู่กับสถานการณ์ของคุณ

คำตอบ #2

มาทำสิ่งที่ตรงข้ามที่นี่กัน... การใช้ประเภททั่วไป และแผนที่ของคีย์ไปยังพารามิเตอร์ เราสามารถได้ประเภท MyFunction เดิมกลับมาได้ดังนี้:

type PMap = {
    one: { first: string };
    two: { second: string };
};

type UnionToIntersection<U> = 
  (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never

type MyF<T extends keyof PMap> = (key: T, params: PMap[T]) => void;

type MyFunction = UnionToIntersection<keyof PMap extends infer U extends keyof PMap ? U extends U ? MyF<U> : never : never>;

และนี่หมายความว่าคุณจะสามารถได้สิ่งที่คุณต้องการเช่นนี้:

type MyFunctionParams<T extends keyof PMap> = Parameters<MyF<T>>[1];

ค่อนข้างยาวแต่ขยายได้และทำงานได้ดี

สนามเด็กเล่น

บทความที่เกี่ยวข้อง