นำรูปแบบระดับผู้เชี่ยวชาญมาใช้เพื่อรักษาความปลอดภัยของ codebase จริง: การจำลอง nominality, การตรวจสอบโดยไม่ขยาย type และ ambient declaration
เปิดบทเรียนนี้ใน Kodokonการกำหนด type ของ TypeScript เป็นแบบ structural: type สองตัวที่มีรูปทรงเดียวกันสามารถใช้แทนกันได้ ดังนั้น string ที่เป็น user-id จึงแยกไม่ออกจาก string ตัวอื่น ๆ Branding จำลองการกำหนด type แบบ nominal โดยทำ intersect base type กับ phantom property ที่ key เป็น unique symbol: ไม่มีค่าจริงใดมีมันเลย แต่ตอนนี้คอมไพเลอร์แยกแต่ละ brand ออกจากกันได้
declare const brand: unique symbol;
type Brand<T, Name extends string> =
T & { readonly [brand]: Name };
type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;
function asUserId(raw: string): UserId {
return raw as UserId;
}
declare function loadUser(id: UserId): void;
loadUser(asUserId("u_42")); // OK
// loadUser("u_42"); // error: bare string rejectedโอเปอเรเตอร์ satisfies (TypeScript 4.9) เติมเต็มช่องว่างที่แม่นยำ annotation : T ขยาย type ของตัวแปรให้เป็น T และสูญเสีย literal ที่อนุมานได้; as T ปิดการตรวจสอบบางส่วน satisfies T ตรวจสอบว่า expression สอดคล้องกับ T โดยไม่แตะต้อง type ที่อนุมานได้ ซึ่งยังคงแม่นยำที่สุดเท่าที่จะเป็นไปได้ มันเป็นเครื่องมือที่เหมาะสมสำหรับ object ที่เป็นการตั้งค่า: ตรวจสอบครบถ้วน ความแม่นยำยังคงอยู่
type RouteDef = { path: string; auth?: boolean };
const routes = {
home: { path: "/" },
admin: { path: "/admin", auth: true },
} satisfies Record<string, RouteDef>;
routes.admin.auth;
// literal type true: the inferred precision is
// preserved, and an unknown key would be rejectedไฟล์ .d.ts มีเฉพาะ ambient declaration เท่านั้น: มันอธิบายค่าที่จะมีอยู่ ณ เวลารัน โดยไม่ปล่อย JavaScript ออกมาแม้แต่บรรทัดเดียว การขยาย global scope จากภายใน module นั้นบล็อก declare global เป็นสิ่งจำเป็น - และสเปกอนุญาตให้ทำได้เฉพาะในไฟล์ที่เป็น module จริง ๆ เท่านั้น จึงเป็นที่มาของสำนวน export {} ที่ด้านบนของไฟล์
export {};
declare global {
interface Window {
analytics: { track(event: string): void };
}
}สำหรับ dependency ของ JavaScript ที่ไม่มี type เผยแพร่มา declare module สร้าง ambient module: คอมไพเลอร์จะใช้ declaration ของคุณสำหรับทุกการ import ของแพ็กเกจนั้น ระวังขอบเขตของ skipLibCheck: เมื่อเปิดใช้งาน มันจะเพิกเฉยต่อ error จากไฟล์ .d.ts ทั้งหมด รวมถึงของคุณด้วย; ดังนั้นจงตรวจสอบ declaration ของคุณผ่าน type test ที่วางไว้ในไฟล์ .ts ทั่วไป
declare module "legacy-lib" {
export interface InitOptions {
debug?: boolean;
}
export function init(options?: InitOptions): void;
}satisfies T กับ annotation : T คืออะไร?Brand<string, ...> มีอะไรอยู่ข้างใน?export {} ที่ด้านบนของไฟล์ .d.ts ที่ใช้ declare global?