ใน TypeScript ชนิดเงื่อนไข ฉันจะทดสอบการมีอยู่ของฟังก์ชัน/พารามิเตอร์ทั่วไปได้อย่างไร?
ฉันกำลังพยายามเขียนฟังก์ชันง่าย ๆ ใน TypeScript คล้ายกับตัวอย่าง JavaScript ต่อไปนี้ (กรณีการใช้งานจริงของฉันซับซ้อน ดังนั้นฉันจึงลดปัญหาให้เป็นกรณีการใช้งานที่ง่ายกว่า)
function resolve(value, defaultValue=null) {
if (value !== undefined && value !== null) {
return value;
} else {
return defaultValue;
}
}
ฉันมีฟังก์ชันสำหรับการแก้ไขตัวแปรที่สามารถเป็นค่า null ให้เป็นค่าจริงของมัน ถ้าพารามิเตอร์เป็น null หรือ undefined มันจะคืนค่าดีฟอลต์ ค่าเริ่มต้นของค่าดีฟอลต์คือ null แรงจูงใจสำหรับฟังก์ชันเช่นนี้สามารถโต้แย้งได้ ดังนั้นเราจะสมมติว่าฟังก์ชันนี้ช่วยบังคับใช้มาตรฐานการเขียนโค้ดหรืออะไรบางอย่าง
มาสังเกตความหมายกันเถอะ:
function resolve(value, defaultValue=null) {
if (value !== undefined && value !== null) {
return value;
} else {
return defaultValue;
}
}
let a = resolve(undefined); // null
let b = resolve(2); // 2
let c = resolve(13, 0); // 13
let d = resolve(null, ''); // ''
let myVar = 'some-value'; // get value from somewhere, could be anything (not just a hard-coded string)
let e = resolve(myVar); // Could be typeof myVar or null
let f = resolve(myVar, 0); // Could be typeof myVar or number (cannot be null)
let g = resolve(myVar, ''); // Could be typeof myVar or string (cannot be null)
หากคุณส่งค่าที่เป็น nullish เข้ามา คุณจะได้ค่ามาตรฐาน หากคุณไม่ระบุค่ามาตรฐาน ค่าเริ่มต้นของค่ามาตรฐานคือ null หากคุณส่งค่ามาตรฐานที่ไม่ใช่ null จะไม่มีทางใดที่จะได้ค่า null วิธีเดียวที่จะได้ค่า null คือการส่งมันเข้ามาอย่างชัดเจนหรือไม่ใส่พารามิเตอร์ defaultValue เลย ดูเหมือนว่านี่จะเป็นตัวเลือกที่ดีสำหรับ conditional types อย่างไรก็ตาม ผมไม่สามารถจัดการกับมันได้ อาจเป็นเพราะนี่เป็นไปไม่ได้ โดยอ้างอิงจากความหมายบางอย่างของ TypeScript
ให้ฉันแสดงให้คุณเห็นว่าฉันพยายามจะทำอะไร สิ่งต่อไปนี้ **ไม่สามารถคอมไพล์ได้**:
function resolve<T, TDefault extends T>(value: T | undefined | null, defaultValue?: TDefault): T | (typeof defaultValue extends undefined ? null : TDefault) {
if (value !== undefined && value !== null) {
return value;
} else {
if (defaultValue !== undefined) {
return defaultValue;
} else {
return null;
}
}
}
let myVar: string | undefined = 'some-value'; // get value from somewhere, could be anything (not just a hard-coded string)
let e = resolve(myVar); // Should return string | null
let f = resolve(myVar, 0); // Should not compile. Number is not assignable to string
let g = resolve(myVar, ''); // Should return string, always.
โดยเฉพาะให้ดูที่ชนิดข้อมูลที่ส่งกลับ:
T | (typeof defaultValue extends undefined ? null : TDefault)
ฉันหวังว่าคุณจะเข้าใจสิ่งที่ฉันพยายามสื่อที่นี่ หากพารามิเตอร์ defaultValue เป็น undefined (ถูกละไว้) ประเภทของค่าที่ส่งกลับจะเป็น T | null มิฉะนั้นประเภทของค่าที่ส่งกลับจะเป็น T | TDefault (อย่างไรก็ตาม TDefault ขยาย T ดังนั้นประเภทค่าที่ส่งกลับจริง ๆ จะเป็นเพียง T) ฉันรู้ว่าสิ่งที่ฉันเขียนนี้ใช้ไม่ได้ หรือแม้แต่ไม่น่าจะมีเหตุผล ฉันเขียนมันเพื่อวัตถุประสงค์ในการสาธิตเท่านั้น typeof defaultValue เทียบเท่ากับการเขียน TDefault | undefined ดังนั้นประเภทเชิงเงื่อนไขที่ฉันเขียนจึงเป็นแบบกระจาย ฯลฯ ฉันได้ลองทำหลายวิธีแต่ดูเหมือนว่าฉันจะไม่สามารถทำได้ ฉันสงสัยว่าการสื่อความหมายของฟังก์ชันนี้สามารถถูกจับโดย TypeScript ได้หรือไม่
มีความคิดเห็นอะไรไหม?
คำตอบ #1
ผมคิดว่ากรณีการใช้งานของคุณจะเหมาะสมกว่าถ้าใช้การโอเวอร์โหลด
function resolve(value: undefined| null) : null
function resolve<TDefault>(value: undefined| null, defaultValue: TDefault) : TDefault
function resolve<T, TDefault extends T>(value: T, defaultValue: TDefault) : T | TDefault
function resolve<T>(value: T) : T | null
function resolve(value, defaultValue=null) {
if (value !== undefined && value !== null) {
return value;
} else {
return defaultValue;
}
}
let a = resolve(undefined); // null
let b = resolve(2); // number
let c = resolve(13, 0); // This one is an error since 0 does not extends 13, but maybe this should be the behaviour ?
let d = resolve(null, ''); // ''
let myVar = 'some-value'; // get value from somewhere, could be anything (not just a hard-coded string)
let e = resolve(myVar); // string | nul
let f = resolve(myVar, 0); // err
g = resolve(myVar, ''); //string
คำตอบ #2
ฉันคิดว่าคุณกำลังพยายามมากเกินไป การกำหนดประเภทของคุณไม่ควรมีพารามิเตอร์แบบมีเงื่อนไข ให้ระบุทางเลือกทั้งหมดแทน ทำไม? โค้ดกำหนดตรรกะ การกำหนดประเภทกำหนดเพียงอินพุตและเอาต์พุตที่เป็นไปได้
(typeof defaultValue extends undefined ? null : TDefault)
ดูเหมือนว่าจะคอมไพล์ได้ใน playground แต่จริง ๆ แล้วสิ่งที่คุณต้องการคือผลลัพธ์ทั้งหมด:
function resolve<T>(value: T | undefined | null, defaultValue: T | null = null): T | null {
return value || defaultValue; //for demo purposes
}
เนื่องจาก T ได้กำหนดแล้วว่าการใส่ input ของ Subclass ของ T นั้นสามารถทำได้ คุณไม่จำเป็นต้องระบุ TDefault เพราะหลังจากเรียกใช้ฟังก์ชันนี้คุณจะไม่รู้ว่าผลลัพธ์คือ T หรือ subclass ของ T สิ่งเดียวที่คุณสามารถทำได้จริง ๆ คือการตรวจสอบประเภท:
if(output instanceof SubClassOfT){
(output as SubClassOfT).SomeSpecificStuf
}
