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"
