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

typescript - ทำไมการใช้ตัวดำเนินการตามเงื่อนไขจึงส่งผลให้เกิดประเภทอินเทอร์เซ็กชัน?


ฉันกำลังพยายามสร้างฟังก์ชันทั่วไปที่คืนค่าตามเงื่อนไขโดยขึ้นอยู่กับประเภทของอาร์กิวเมนต์ของมัน แต่ฉันติดขัดในการพยายามใช้งานประเภทการคืนค่า

สมมติว่ามีประเภท Basket

type Basket = {
    Fruit: 'banana',
    Veggie: 'tomato'
}

ตอนนี้ถ้าฉันต้องการส่งค่ากลับแบบมีเงื่อนไขระหว่าง 'กล้วย' หรือ 'มะเขือเทศ' ขึ้นอยู่กับอาร์กิวเมนต์ที่ส่งไปยังฟังก์ชัน ฉันไม่สามารถทำให้มันคอมไพล์ได้:

const f1 = <T extends keyof Basket>(t: T): T extends 'Fruit'? 'banana': 'tomato' => {
    if (t == 'Fruit') {
        return 'banana' //Error on this line - doesn't compile -- Type '"banana"' is not assignable to type '"banana" & "tomato"
    } else {
        return  'tomato' //Error on this line - doesn't compile -- Type '"tomato"' is not assignable to type '"banana" & "tomato"
    }
}

ตอนนี้เมื่อฉันสร้างอินสแตนซ์ของสิ่งนี้หลังจากส่งพารามิเตอร์เจเนอริกที่ถูกต้อง ฉันได้รับชนิดที่ฉันคาดหวัง แต่โค้ดไม่สามารถคอมไพล์ได้

const doesntCompile: 'banana' = f1<'Fruit'>('') //type: 'banana', which is what I want ... but this doesn't compile due to the error above.

อย่างไรก็ตาม ถ้าฉันไม่ใช้เจเนอริก ฉันจะได้ชนิดผลรวมดังนี้

//This compiles
const f2 = <T extends keyof Basket>(t: string): Basket[keyof Basket] => { //return type is 'banana' | 'tomato'
    if (t == 'yellow') {
        return  'banana'
    } else {
        return  'tomato'
    }
}
const complies: 'banana' | 'tomato' = f2<'Fruit'>('') //type: 'banana' | 'tomato', but I want the type here to be 'banana'

ตอนนี้มันคอมไพล์ได้เรียบร้อย แต่ฉันสูญเสียประโยชน์ของความปลอดภัยของประเภทข้อมูล

ฉันจะทำให้ตัวอย่างนี้ทำงานได้อย่างไรในขณะที่ยังคงรักษา Generics อยู่? ความช่วยเหลือใด ๆ จะขอบคุณอย่างมาก

คำตอบ #1

เกี่ยวกับปัญหาของคุณ มันมาจากชนิดเงื่อนไขที่ถูกเลื่อนออกไป ดูเอกสารของ TypeScript: https://www.typescriptlang.org/docs/handbook/advanced-types.html#conditional-types. (ค้นหาคำว่า conditional types are deferred เพื่อไปยังตำแหน่งที่ถูกต้องในหน้า).

วิธีแก้ปัญหาที่ง่ายที่สุดคือการใช้ลายเซ็นการใช้งานแยกต่างหากที่อนุญาตมากกว่า ในขณะที่ยังคงลายเซ็นสาธารณะที่มีประเภทตามเงื่อนไขซึ่งเหมาะสมกว่าสำหรับผู้เรียก:

type Basket = {
    Fruit: 'banana',
    Veggie: 'tomato'
}

function f3 <T extends keyof Basket>(t: T): Basket[T]; 
function f3 <T extends keyof Basket>(t: string): Basket[keyof Basket] {
    if (t == 'Fruit') {
        return  'banana'
    } else {
        return  'tomato'
    }
}

const complies2 = f3('Fruit'); // complies2 is "banana"

หมายเหตุ: ฟังก์ชันลูกศรไม่สามารถใช้กับการโอเวอร์โหลดฟังก์ชันได้

บทความที่เกี่ยวข้อง