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

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;
};