เกิดอะไรขึ้น
เรามี route map ที่เขียนเป็น object แล้วใส่ type ให้ตัวแปรเป็น Record<string, Route> ตรวจ shape ได้ก็จริง แต่ autocomplete ไม่เห็น key จริงอย่าง home หรือ login และพิมพ์ key ผิดก็ไม่ error เพราะ type บอกว่า string ไหนก็ได้
เราอยากได้ทั้งสองอย่าง คือให้ compiler ตรวจว่าทุก entry มี shape ถูก และให้ editor รู้ว่า key ไหนมีอยู่จริง
ทำไมถึงเป็นแบบนั้น
เมื่อเราเขียน type annotation ตัวแปรจะมี type ตามที่ระบุ ไม่ใช่ตามค่าที่ใส่ จึงเหลือแต่ข้อมูลกว้าง ๆ ของ Record<string, Route> ส่วนข้อมูลเฉพาะของ object ที่เขียนไว้หายไป
satisfies (มาตั้งแต่ TypeScript 4.9) ตรวจ expression เทียบกับ type แล้ว ไม่เปลี่ยน type ของตัวแปร type ที่ได้ยังมาจากค่าที่เขียนจริง key จึงเป็นชุดที่แน่นอน และ property ที่ type เป้าหมายกำหนดเป็น literal union เช่น 'GET' | 'POST' ก็คงเป็น literal เดิม ถ้าอยากให้ทุกค่าเป็น literal ให้ใช้ as const ร่วมด้วย
ตอนนี้เราทำอย่างไร
ตอนนี้ config และ route map ของเราใช้ satisfies แทน annotation พิมพ์ key ผิดแล้ว compiler เตือนทันที autocomplete เห็นเฉพาะ key จริง และถ้ามี entry ไหนผิด shape ก็ error ที่ entry นั้นเลย
ถ้าต้องการให้ตัวแปรกว้างโดยตั้งใจ เช่นรับค่าได้หลายแบบ ก็ยังใช้ annotation ตามปกติได้ satisfies เหมาะกับ object ที่เรารู้ว่ามีอะไรอยู่ข้างใน
type Route = { path: string; method: 'GET' | 'POST' }
const routes = {
home: { path: '/', method: 'GET' },
login: { path: '/login', method: 'POST' },
} satisfies Record<string, Route>
routes.login.method // 'POST'
routes.logn // error: Property 'logn' does not existเช็กลิสต์สั้น ๆ
- 1ลบ type annotation ออกจากตัวแปร
- 2ใส่ satisfies ตามด้วย type ที่ต้องการตรวจท้าย object
- 3ลองพิมพ์ key ผิดเพื่อดูว่า compiler เตือน
- 4ถ้าต้องการ literal ทั้งหมด เพิ่ม as const ก่อน satisfies
จำไว้สั้น ๆ
ใช้ satisfies เมื่ออยากให้ compiler ตรวจ shape โดยไม่ทำให้ type ของ object กว้างขึ้น