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

typescript - วิธีการเก็บการพิมพ์ชื่อคีย์แบบอัตโนมัติเป็นส่วนหนึ่งของอินเทอร์เฟซในขณะที่ยังระบุประเภทค่าที่ TS?


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

interface IElementType = {[key: string]: SpecialProperties}

และแทนที่ด้วยบางอย่างเช่น

interface IElementType = {[key: keyof elements]: SpecialProperties}

ในตัวอย่างเช่น

const elements: IElementType = {
  pirate: {
    name: 'patchy',
    type: 'matey'
  },
  captain: {
    name: 'peggy',
    type: 'pegleg'
  },
  ...
}


// for reference
interface SpecialProperties {
  name: string,
  type: string
}

โดยที่ keyof elements คือฉันกำลังคิดขึ้นมาเองในสิ่งที่ฉันอยากให้เป็นไปได้ (ทราบว่าสิ่งนี้ใช้ไม่ได้เพราะ elements เป็นวัตถุ js) แต่หวังว่าฉันอาจคิดผิดไป แรงกระตุ้นที่นี่คือการรักษาข้อมูลประเภท/intellisense เมื่อทำการนำเข้าตัวแปรนี้ไปยังส่วนอื่นของโปรเจกต์ ฉันสามารถพิมพ์ elements.pirate และ ts จะรู้ว่าคีย์นี้มีอยู่จริง เมื่อเราใช้ [key: string] เราโดยนัยกำลังทำให้คีย์ในวัตถุเป็นอนามัยและทิ้งข้อมูลนั้นไป แตกต่างจากเมื่อเรากำหนดตัวแปรโดยไม่มีการระบุประเภทอย่างชัดเจน เช่น

const elements = { ... }

ที่ TypeScript กำหนดประเภทของตัวแปรนี้โดยปริยายด้วยประเภทที่มีคีย์ชัดเจนที่วัตถุมี ดังนั้นในกรณีข้างต้น ประเภทจะตรงกับค่าของวัตถุทั้งหมดพอดี

ฉันหวังว่าจะมีจุดกึ่งกลางตรงนี้ที่เราสามารถรักษาการพิมพ์แบบแฝงของชื่อคีย์ไว้ได้ แต่บังคับให้ค่าของมันเป็นค่าเฉพาะได้ สิ่งนี้เป็นไปได้ไหม?

คำตอบ #1

ก็ คุณสามารถ แสดงออก สิ่งที่คุณกำลังพูดได้ เนื่องจาก keyof typeof elements เป็นชนิดที่ถูกต้องสมบูรณ์ แต่คุณจะไม่สามารถนิยาม IElementType ในแง่ของ elements และในทางกลับกันโดยไม่เกิดคำเตือนเรื่องความเป็นวงกลม:

type IElementType = Record<keyof typeof elements, SpecialProperties>;
//   ~~~~~~~~~~~~ <-- Type alias 'IElementType' circularly references itself.
const elements: IElementType = { /* .. */ }
//    ~~~~~~~~ <-- 'elements' is referenced directly or indirectly in its own type annotation.

ดังนั้นเราจำเป็นต้องละทิ้งความคิดนั้น


ถ้าคุณใส่คำอธิบายประกอบให้กับ elements ด้วยประเภทที่ยังไม่รู้จักคีย์เฉพาะ เช่น {[k: string]: SpecialProperties} ที่มี index signature ตัวคอมไพเลอร์จะ ขยาย elements ไปเป็นประเภทนั้นและลืมคีย์เฉพาะเหล่านั้น

เนื่องจากคุณต้องการที่จะสามารถกำหนด elements ในลักษณะที่ตัวคอมไพเลอร์ ตรวจสอบ โดยไม่ขยายชนิดออก ดังนั้น คุณไม่สามารถทำการกำกับประเภทมันได้ มีคำร้องขอฟีเจอร์ที่ microsoft/TypeScript#7481 สำหรับการเพิ่มโอเปอเรเตอร์ชนิดบางอย่างที่ทำหน้าที่เหมือนกับการกำกับประเภทแบบไม่ขยาย แต่ในตอนนี้มันยังไม่ใช่ส่วนหนึ่งของภาษา

แทนที่จะทำเช่นนั้น คุณสามารถกำหนดฟังก์ชันช่วยเหลือแบบทั่วไปที่คุณใช้แทนการใช้คำอธิบายประเภท คุณจะเปลี่ยน const elements: IElementType=someValue เป็น const elements=asElementType(someValue) โดยที่ asElementType() ถูกกำหนดดังนี้:

const asElementType = <K extends PropertyKey>(t: { [P in K]: SpecialProperties }) => t;

ฟังก์ชันนี้จะยอมรับอินพุตก็ต่อเมื่อมันสามารถสรุปประเภทคีย์ K ได้ (ซึ่งอาจเป็นยูเนียนของคีย์) โดยที่อินพุตมีคีย์ K และค่าของ SpecialProperties เนื่องจากฟังก์ชันเพียงแค่ส่งกลับอินพุตของมัน ชนิดของค่าที่ส่งกลับจะเหมือนกับอินพุต: เป็นประเภทอ็อบเจ็กต์ที่มีบางส่วนของประเภทคีย์ที่สรุปได้ K และประเภทค่าของ SpecialProperties

มาลองดูมันในการใช้งานกันเถอะ:

const elements = asElementType({
    pirate: {
        name: 'patchy',
        type: 'matey'
    },
    captain: {
        name: 'peggy',
        type: 'pegleg'
    },
});
/* const elements: {
    pirate: SpecialProperties;
    captain: SpecialProperties;
} */

นั่นใช้ได้; ตอนนี้ elements มีชนิดเป็น {pirate: SpecialProperties, captain: SpecialProperties} และถ้าคุณทำผิดพลาด คอมไพเลอร์จะแจ้งเตือนคุณ:

const oops = asElementType({
    swabbie: { // error! Property 'type' is missing
        name: 'bilgey'
    }
})

จึงแสดงให้เห็นว่า asElementType() ทำงานคล้ายกับอินเทอร์เฟซที่มีประเภทคีย์แบบไดนามิก แต่มีประเภทค่าคงที่

ลิงก์ Playground ไปยังโค้ด

คำตอบ #2

สำหรับตัวอย่างนี้...

const elements: IElementType = {
  pirate: {
    name: 'patchy',
    type: 'matey'
  },
  captain: {
    name: 'peggy',
    type: 'pegleg'
  },
  ...
}

...ในการทำงาน ให้กำหนด IElementType เป็นอินเทอร์เฟซแบบทั่วไป แต่การใช้การประกาศ interface ไม่สามารถใช้ได้ในกรณีนี้. :(

type IElementType<K extends string> = {
  [x in K]: SpecialProperties;
};

ตัวแปร elements จะมีชนิดเป็น IElementType<'pirate' | 'captain'> สำหรับสองคีย์แรก