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

typescript - การตรวจสอบประเภทของคีย์ของค่าคงที่แบบ literal โดยไม่ทำให้ประเภทสูญหาย


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

ถ้าฉันทำดังต่อไปนี้:

interface MyInterface {
  keyA: string;
  keyB: string;
}

export const MY_KEYS: Record<string, keyof MyInterface> = {
  CONSTANT_KEY_A: 'keyA',
  CONSTANT_KEY_B: 'keyB',
} as const;

MY_KEYS.CONSTANT_KEY_A

ดังนั้น MY_KEYS.CONSTANT_KEY_A คือประเภท 'keyA' | 'keyB' แต่ฉันต้องการให้มันเป็น 'keyA'!

ถ้าฉันลบ Record<string, keyof MyInterface> มันก็จะทำงานได้ แต่ก็หมายความว่าคีย์ของฉันจะไม่ได้รับการตรวจสอบประเภทให้เป็นหนึ่งในคีย์ของ MyInterface อีกต่อไป

มีไอเดียไหมว่าฉันจะทำแบบนั้นได้อย่างไร?

ตัวอย่างเช่น ผมอาจจะเพิ่มตัวแปรตายตัวที่สองที่ทำการตรวจสอบ Record และเก็บตัวแรกของผมไว้เพียงแค่ as const โดยไม่แปลงเป็น Record แต่ก็ค่อนข้างยืดยาวและไม่ค่อยชัดเจนเท่าไหร่

ขอบคุณ!

คำตอบ #1

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

const checkType = <T>() => <U extends T>(u: U) => u;

แล้วจากนั้นเพื่อให้มันตรวจสอบประเภทเฉพาะ คุณเรียกมันและระบุประเภทนั้นด้วยตนเอง:

const checkKeys = checkType<Record<string, keyof MyInterface>>();

ตอนนี้คุณมีฟังก์ชันที่จะรับค่าได้เฉพาะชนิดที่ถูกต้องเท่านั้น และจะแสดงข้อผิดพลาดตามที่คาดไว้เมื่อคุณทำอะไรผิด:

export const MY_KEYS = checkKeys({
  CONSTANT_KEY_A: 'keyA',
  CONSTANT_KEY_B: 'keyB',
  // CONSTANT_KEY_C: 'keyC', // error! '"keyC"' is not assignable to  '"keyA" | "keyB"'.
})

แต่ผลลัพธ์ของฟังก์ชันไม่ได้ขยายออกไปตามที่ต้องการ:

MY_KEYS.CONSTANT_KEY_A // "keyA"

โปรดทราบว่าคุณไม่จำเป็นต้องใช้ as const เลย เพราะตัวคอมไพเลอร์เห็นว่าคุณต้องการตีความวัตถุลิตเทอรัลให้สามารถกำหนดเป็น Record<string, keyof MyInterface> ได้ ดังนั้นจึงไม่ขยายค่าสตริงลิตเทอรัลเป็น string อย่างไรก็ตาม คุณยังสามารถใช้ as const ได้ถ้ามันช่วยคุณในวิธีอื่น ๆ.


ในเชิงแนวคิด นี่ไม่ต่างจากแนวคิด "ตัวแปรตาย" ของคุณจริง ๆ และแน่นอนว่าตอนรันไทม์ (() => u => u)()(someValue) เป็นวิธีเขียน someValue ที่แปลกมาก แต่ถ้าไม่มีตัวดำเนินการในตัว "ตรวจสอบการกำหนดค่าโดยไม่ขยายชนิด" ใน TypeScript (เช่น อาจจะ microsoft/TypeScript#30809?) นี่คือวิธีที่ดีที่สุดที่ฉันรู้ในการทำสิ่งนี้โดยทั่วไป

โอเค หวังว่านี่จะช่วยได้ โชคดีนะ!

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

คำตอบ #2

สำหรับทุกคนที่ใช้ TypeScript 4.9.5 หรือสูงกว่า คุณสามารถใช้ satisfies (ลิงก์):

export const MY_KEYS = {
  CONSTANT_KEY_A: 'keyA',
  CONSTANT_KEY_B: 'keyB',
} as const satisfies Record<string, keyof MyInterface>;

MY_KEYS.CONSTANT_KEY_A
// (property) CONSTANT_KEY_A: "keyA"

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