javascript - การเก็บรักษาเจนเนอริกในประเภทแมปของ TypeScript
ฉันมีคลาสหนึ่ง ซึ่งเมธอดของอินสแตนซ์เป็นตัวจัดการ แต่ละตัวแทนการดำเนินการหนึ่ง รับการอ้างอิงเป็นอินพุตและกำหนดผลลัพธ์ให้กับพารามิเตอร์ตัวที่สอง วัตถุพร็อกซี่ถูกสร้างโดยไลบรารีของบุคคลที่สามเพื่อให้สามารถเรียกตัวจัดการโดยตรงได้
type InputRef<T> = {
current: T,
};
type OutputRef<T> = {
current?: T,
};
class Original {
increment(input: InputRef<number>, output: OutputRef<number>) {
const { current: inValue } = input;
output.current = inValue + 1;
}
}
type Mapper<Fn> = Fn extends (input: InputRef<infer U>, output: OutputRef<infer V>) => unknown ? (input: U) => V : never;
type MyProxyGeneratedByThirdPartyJs = { [FnName in keyof Original]: Mapper<Original[FnName]> };
declare const proxy: MyProxyGeneratedByThirdPartyJs;
const result = proxy.increment(3); // 4
อย่างไรก็ตาม ตัวแม็ปไม่ทำงานเมื่อฮันเดิลเลอร์เกี่ยวข้องกับประเภททั่วไป เช่น,
class Original {
toBox<T>(input: InputRef<T>, output: OutputRef<{ boxed: T }>) {
const { current: inValue } = input;
output.current = { boxed: inValue };
}
}
โดยใช้วิธีเดียวกันข้างต้น ประเภทของ proxy จะเกี่ยวข้องกับ unknown เท่านั้น และข้อมูลเจเนอริก T จะหายไป
ในอุดมคติ ฉันต้องการให้ proxy เป็นชนิด
{
toBox<T>(input: T): { boxed: T },
}
แทนที่จะ
{
toBox(input: unknown): { boxed: unknown },
}
มีวิธีใดบ้างในการทำให้สิ่งนี้สำเร็จ?
คำตอบ #1
สิ่งนี้ยังไม่สามารถทำได้ใน TypeScript ในการที่จะบอกสิ่งที่คุณกำลังทำกับฟังก์ชันทั่วไปในระดับประเภท คุณจะต้องใช้ higher kinded types ตามที่ร้องขอใน microsoft/TypeScript#1213 แต่สิ่งเหล่านี้ไม่ได้รับการสนับสนุนโดยตรง การกำหนดประเภทเงื่อนไขของ Mapper<Fn> จะอนุมาน U เป็นประเภทอินพุตและ V เป็นประเภทเอาต์พุต แต่ตัวคอมไพเลอร์ไม่มีความสามารถในการเห็นหรือแสดงความสัมพันธ์ระดับสูงระหว่างพวกมันเมื่อ Fn เป็นแบบทั่วไป
มีการสนับสนุน บางส่วน สำหรับการเปลี่ยนประเภทฟังก์ชันทั่วไปไปเป็นประเภทฟังก์ชันทั่วไปอื่น แต่สิ่งนี้เกิดขึ้นเฉพาะในสถานการณ์ที่เฉพาะเจาะจงมาก การเปลี่ยนแปลงต้องเกิดขึ้นอย่างน้อยบางส่วนในระดับค่า (ต้องมี ค่าฟังก์ชัน ที่เปลี่ยนประเภทฟังก์ชันทั่วไปหนึ่งไปเป็นอีกประเภทหนึ่งเมื่อถูกเรียก ใช่เพียง ประเภท ของฟังก์ชันนั้น) ดังนั้นจึงจะมีการสร้างโค้ด JS ออกมา และการเปลี่ยนประเภทนี้ทำงานได้เพียงฟังก์ชันเดียวในแต่ละครั้ง ดังนั้นวัตถุที่มีฟังก์ชันหลายตัวไม่สามารถถูกแมปได้พร้อมกันโดยไม่ทำให้ค่าเจเนอริกสูญหาย
ถึงอย่างนั้น เพื่อแสดงให้เห็นว่ามีความสามารถบางส่วนในการทำสิ่งนี้ นี่คือวิธีที่ใครบางคนอาจจะลองทำ:
function proxySingleFunction<U, V>(
f: (input: InputRef<U>, output: OutputRef<V>) => any
): (input: U) => V {
return function (input: U) {
const o: OutputRef<V> = {};
f({ current: input }, o);
const ret = o.current;
if (ret === undefined) throw new Error("OH NO");
return ret;
}
}
ประเภทของ proxySingleFunction() คือ
// function proxySingleFunction<U, V>(
// f: (input: InputRef<U>, output: OutputRef<V>) => any
// ): (input: U) => V
ซึ่งดูคล้ายกับสิ่งที่คุณกำลังทำกับ Mapper<Fn> จากนั้น หากคุณ เรียก proxySingleFunction() มันจะสร้างผลลัพธ์ของประเภทที่เกี่ยวข้อง:
const increment = proxySingleFunction(Original.prototype.increment);
// const increment: (input: number) => number
const toBox = proxySingleFunction(Original.prototype.toBox);
// const toBox: <T>(input: T) => { boxed: T; }
คุณสามารถเห็นได้ว่า toBox เป็นแบบทั่วไป ตามที่ต้องการ จากนั้นคุณสามารถรวมฟังก์ชันเอาต์พุตเหล่านี้ไว้ในวัตถุ proxy เดียวและใช้งานได้:
const proxy = {
increment, toBox
}
console.log(proxy.increment(1).toFixed(1)) // "2.0"
console.log(proxy.toBox("a").boxed.toUpperCase()) // "A"
นั่นก็ดีและมันใช้ได้ แต่สิ่งนี้ต้องให้คุณสร้าง JavaScript สำหรับแต่ละเมธอดที่คุณต้องการแปลง หากคุณมีออบเจ็กต์ที่ถูกแปลงเช่นนั้นจากบุคคลที่สามอยู่แล้วและเพียงต้องการแสดงประเภทของมัน สิ่งที่ใกล้เคียงที่สุดที่คุณทำได้คือการหลอกตัวคอมไพเลอร์ผ่านการระบุประเภทเพื่อให้คิดว่าคุณกำลังทำการแปลงอยู่ ทั้งที่จริง ๆ แล้วคุณไม่ได้ทำ:
// pretend this function exists
declare const psf: <U, V>(
f: (input: InputRef<U>, output: OutputRef<V>) => any
) => (input: U) => V;
// pretend you're using it to create a proxy object
const myProxyType = (true as false) || {
increment: psf(Original.prototype.increment),
toBox: psf(Original.prototype.toBox)
}
// get the pretend type of that object
type MyProxyGeneratedByThirdPartyJs = typeof myProxyType;
/* type MyProxyGeneratedByThirdPartyJs = {
increment: (input: number) => number;
toBox: <T>(input: T) => { boxed: T; };
} */
ฉันไม่เห็นว่านี่เป็นชัยชนะครั้งใหญ่เหนือการเขียนประเภทเหล่านี้ด้วยมือในตอนแรก ดังนั้นฉันก็ไม่แน่ใจว่าฉันจะแนะนำมัน มันเป็นเพียงสิ่งที่ใกล้เคียงที่สุดที่ฉันสามารถจินตนาการได้กับสิ่งที่คุณต้องการกับภาษาตามที่มันเป็นอยู่ในปัจจุบัน
ลิงก์ Playground ไปยังโค้ด
