javascript - วิธีจัดการกับ TypeScript ที่ไม่พิจารณาพารามิเตอร์ทางเลือกที่ยังไม่ได้กำหนดค่าเป็น undefined
นี่คือการจำลองฟังก์ชันของฉันแบบง่ายขึ้น:
const nullify = <T extends string | undefined>(
value?: T,
): T extends string ? string : null => {
if (value === undefined) return null as any;
return value as any;
};
สิ่งที่ฉันคาดหวังจากการระบุประเภทฟังก์ชันเหล่านี้คือการพิจารณาการขาด value เป็น undefined แต่ TypeScript ไม่อนุญาตสิ่งนี้ และกลับคืนค่าชุดรวม string | null วิธีเดียวที่จะได้รับ null จากฟังก์ชันนี้คือการส่งค่า undefined โดยตรง
const result1 = nullify('string'); // result1 is string, as expected
const result2 = nullify(undefined); // result2 is null, as expected
const result3 = nullify(); // result3 is (string | null), but should be just null
มีวิธีแบบสำนวนในการจัดการกับเรื่องนี้ไหม?
คำตอบ #1
ปัญหาคือเมื่อคุณไม่ส่งอาร์กิวเมนต์ value เข้ามา ก็จะไม่มีค่าใดที่คอมไพเลอร์สามารถอนุมาน T ได้ และการอนุมานก็ล้มเหลว เมื่อการอนุมานล้มเหลว มันโดยทั่วไปจะกลับไปใช้ข้อจำกัดของมัน ซึ่งในกรณีของคุณคือ string | undefined หากคุณต้องการเปลี่ยนสิ่งที่มันจะกลับไปใช้ คุณสามารถตั้งค่าพารามิเตอร์เจนเนอริก default โดยใช้ไวยากรณ์ =:
const nullify = <T extends string | undefined = undefined>(
// default generic ----> ^^^^^^^^^^^
value?: T,
): T extends string ? string : null => {
if (value === undefined) return null as any;
return value as any;
};
และคุณจะเห็นความแตกต่างก็ต่อเมื่อคุณละเว้น value:
const result1 = nullify('string'); // string, as expected
const result2 = nullify(undefined); // null, as expected
const result3 = nullify(); // null, as expected 👍
เย้!
มีข้อเสียตรงที่ทางเทคนิคผู้โทรสามารถระบุค่าใด ๆ ของ T ที่พวกเขาต้องการ ดังนั้นไม่มีอะไรหยุดผู้โทรที่ใจไม่อยู่กับร่องกับรอยจากการทำสิ่งนี้:
const oops = nullify<string>(); // string, oops
// ---------------> ^^^^^^^^ <--- manually specify string
เนื่องจาก string ถูกระบุด้วยตนเองสำหรับ T และพารามิเตอร์ value เป็นทางเลือก จึงไม่มีข้อผิดพลาดจากคอมไพเลอร์ และตอนนี้คุณมีสถานการณ์ที่คอมไพเลอร์คิดผิดว่า ค่าของ null เป็นประเภท string
นี่น่าจะไม่เกิดขึ้นบ่อยนัก ดังนั้นคุณสามารถปล่อยไว้เฉย ๆ และไม่ต้องกังวลเกี่ยวกับมัน TypeScript จริง ๆ แล้วไม่ถูกต้องในหลาย ๆ จุด ดังนั้นคุณก็จะไม่ได้ทำให้สถานการณ์แย่ลงไปมากที่นี่
อย่างไรก็ตาม หากคุณใส่ใจในเรื่องเหล่านี้ คุณก็จำเป็นต้องปรับโครงสร้างใหม่ วิธีหนึ่งคือทำให้พารามิเตอร์ประเภททั่วไปของคุณสอดคล้องกับประเภททูเพิลของพารามิเตอร์ที่เหลือ:
const nullify = <T extends [string] | [undefined?]>(
...[value]: T
): T extends [string] ? string : null => {
if (value === undefined) return null as any;
return value as any;
};
const result1 = nullify('string'); // string, as expected
const result2 = nullify(undefined); // null, as expected
const result3 = nullify(); // null, as expected
ตอนนี้คุณไม่สามารถส่งสิ่งที่ไม่ถูกต้องได้อย่างง่ายดาย เนื่องจากสถานการณ์เดียวที่ยอมรับได้ที่ value จะหายไปหรือเป็น undefined คือเมื่อ T ขยาย [undefined?]:
const okayNow = nullify<[string]>(); // error, as expected
วิธีดั้งเดิมในการทำสิ่งประเภทนี้คือการใช้การโอเวอร์โหลดแทน (หรือควบคู่กับ) เจเนอริก แต่แทนที่จะเจาะลึกเรื่องนี้ ผมจะชี้ผู้ที่สนใจไปที่ How to give a default value to a generic parameter? สำหรับข้อมูลเพิ่มเติม
ลิงก์ Playground ไปยังโค้ด
คำตอบ #2
ลองตรวจสอบว่าค่าคือสตริงแทน
const nullify = <T extends string | undefined>(
value?: T,
): T extends string ? string : null => {
if (typeof value === 'string') return value as any;
return null as any;
};