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

ฉันจะทำให้ Typescript เข้าใจว่าประเภทค่าของวัตถุที่มีชนิดเดียวกันคืออะไรโดยไม่เสียคีย์จากการประกาศแบบสแตติกได้อย่างไร?


ฉันจะทำให้ TypeScript เข้าใจประเภทค่าของอ็อบเจ็กต์ได้อย่างไรโดยไม่สูญเสียการเข้าถึงข้อมูลคีย์จากการประกาศแบบสถิตของมัน?

ในโค้ดด้านล่างนี้ฉันต้องการประโยชน์ของ

  • TS รู้ชนิดของค่าด้วย ในกรณีนี้คือฟังก์ชัน ดังนั้นฉันไม่จำเป็นต้องระบุลายเซ็นของมันทุกครั้ง
  • TS รู้คีย์ของอ็อบเจ็กต์จากการประกาศแบบสแตติก ดังนั้นฉันจึงสามารถอ้างอิงพวกมันในลักษณะที่ปลอดภัยตามชนิดจากที่อื่นได้

วัตถุนี้มีความเป็นเนื้อเดียวกันและจะมีฟังก์ชันจำนวนมาก การระบุลายเซ็นทุกครั้งดูเหมือนจะยุ่งยาก

ตัวอย่าง 1 - TS ไม่รู้คีย์ของออบเจ็กต์

    interface Foo {
        [key: string]: (n: number) => any
    }
    
    const obj1: Foo = {
        // TS already knows that `n` is a number 🙂
        bar(n) {return n}
    }
    
    type KeyOfObj1 = keyof typeof obj1
    const kob1: KeyOfObj1 = "xyz" // This does not fail static type checking 😢
    

ตัวอย่าง 2 - TS ไม่ทราบลายเซ็นของฟังก์ชันซึ่งเป็นค่าของวัตถุ

    const obj2 = {
        // Notice here I have to specify the type of `n` 😢
        bar(n: number) {return n}
    }
    
    type KeyOfObj2 = keyof typeof obj2
    const kob2: KeyOfObj2 = "xyz" // This fails static type checking 🙂

ฉันจะได้สิ่งที่ดีที่สุดจากทั้งสองโลกได้อย่างไร?

ป.ล. ผมเข้าใจว่าผมสามารถดึงคีย์ออกมาทำเป็น enum ได้ แต่ดูเหมือนว่าจะน่าเกลียดและซ้ำซ้อนพอสมควร แต่อาจจะเป็นทางเดียวจริง ๆ 😕

คำตอบ #1

นี่เป็นสถานการณ์ที่ค่อนข้างพบได้บ่อย: คุณต้องการเขียนวัตถุ ให้สมาชิกของมันมีประเภทตามบริบทตามประเภทที่อ่อนกว่า (ในกรณีนี้คือ Foo) แต่ยังคงเก็บประเภทที่แข็งแรงกว่าไว้เพื่อวัตถุประสงค์อื่น (ในกรณีนี้ คุณไม่ต้องการที่จะ "ลืม" ว่า keys คืออะไร)

สิ่งที่มักทำกันคือการเขียนฟังก์ชันอัตลักษณ์ทั่วไป เช่น ด้านล่าง ซึ่งตรวจสอบว่าประเภทของอาร์กิวเมนต์ T เป็นซับไทป์ของ Foo ขณะที่คืนค่าประเภทที่เข้มงวดยิ่งกว่า T:

function foo<T extends Foo>(obj: T): T {
    return obj;
}

จากนั้นเมื่อคุณสร้างวัตถุ คอมไพเลอร์จะกำหนดประเภทของมันตามบริบทเหมือนว่าเป็นซับไทป์บางประเภทของ Foo แต่ยังคงเก็บซับไทป์นั้น (พร้อมชื่อคีย์เฉพาะของมัน) สำหรับการกำหนดค่าให้กับ obj:

const obj = foo({
    foo(n) { return n + 1; },
    bar(n) { return n * 2; },
});

// 'foo' | 'bar'
type ObjKeys = keyof typeof obj

ลิงก์สนามเด็กเล่น

น่าเสียดาย ที่อย่างน้อยในตอนนี้ คุณจำเป็นต้องใช้ฟังก์ชันเอกลักษณ์ทั่วไปเพื่อให้สิ่งนี้ทำงานได้ ปัจจุบันมีข้อเสนอที่จะเพิ่มไวยากรณ์ใหม่ให้กับภาษา ซึ่งจะช่วยให้ปัญหาแบบนี้แก้ไขได้โดยไม่ต้องเพิ่มฟังก์ชันเสริมที่ "ไร้ประโยชน์"