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];
ค่อนข้างยาวแต่ขยายได้และทำงานได้ดี
สนามเด็กเล่น
