TypeScript ที่ Scale แตกต่างกัน
เมื่อ TypeScript codebase ของคุณอยู่ภายใต้ 10K lines เกือบทุกวิธีใช้ได้ Types เป็น loose exports เป็น casual และ refactoring ใช้ได้ง่ายเพราะคุณสามารถจดจำ entire codebase ในหัวของคุณ ที่ 50K+ lines ที่มี multiple contributors การตัดสินใจที่คุณทำตั้งแต่เริ่มต้นเริ่มทบต้น — ดีหรือร้าย
ฉันได้เก็บรักษา TypeScript codebases 3 อันที่เกิน 200K lines of code และรูปแบบที่สำคัญที่ scale แตกต่างจาก tutorials สอน
รูปแบบ 1: Branded Types สำหรับ Domain Safety
Primitive obsession — ใช้ plain strings และ numbers ทุกแห่ง — เป็น single biggest source ของ bugs ใน large TypeScript codebases เมื่อ function ของคุณยอมรับ userId: string ไม่มีสิ่งใดหยุด pass orderId: string TypeScript’s structural typing หมายความว่า both เป็น strings
Branded types แก้ไขนี้โดยสร้าง nominal type distinctions UserId ไม่สามารถส่งต่อไปยังที่ OrderId คาดหวัง แม้ว่า both เป็น strings at runtime runtime cost zero — พวกเขาเป็น purely compile-time guardrails
รูปแบบนี้จับ bugs ที่ unit tests หลายครั้ง miss: ส่ง wrong ID ไปยัง function mixing up currency amounts หรือสับสน timestamps ที่มี different semantics
รูปแบบ 2: Discriminated Unions สำหรับ State Machines
ทุก non-trivial application มี state machines: request เป็น loading succeeded หรือ failed user เป็น active suspended หรือ deleted order เป็น pending paid shipped หรือ cancelled
Discriminated unions จำลองเหล่านี้อย่างสมบูรณ์แบบ แทนที่จะเป็น single type ที่มี optional fields (status: string, data?: T, error?: Error) สร้าง union ที่ variant แต่ละรายการมีแน่นอน fields ที่สำคัญสำหรับสภาวะนั้น
Compiler จึง enforces exhaustive handling — ถ้าคุณเพิ่ม new state ทุก switch statement ที่จัดการ union จะ error จนกว่าคุณจะจัดการ new case นี่เป็น incredibly valuable ใน large codebase ที่ state handling logic กระจายข้าม dozens files
รูปแบบ 3: Strict Module Boundaries
ใน large codebase ไม่ใช่ทุกไฟล์ที่ควร importable จากทุกแห่ง สร้าง module boundaries โดยใช้ barrel exports (index.ts files) ที่อย่างชัดเจน declare module’s public API
internal directory structure ของ module คือ implementation detail external consumers import จาก module’s index ไม่ใช่จาก internal files นี่หมายความว่าคุณ refactor internals โดยไม่ breaking consumers และ module’s public API ของคุณ documented โดย exports
Enforce นี้ด้วย ESLint rules ที่ป้องกัน deep imports ข้าม module boundaries บาง teams ใช้ TypeScript path aliases ทำให้ module boundaries ชัดเจนยิ่งขึ้น
รูปแบบ 4: Generic Constraints สำหรับ API Layers
API layer ของคุณควร typed end-to-end จาก HTTP handler ไปยัง database query Generic constraints ทำให้เป็นไปได้โดยไม่ duplicating types
define API contract ของคุณเป็น type ที่ map routes ไปยัง request/response types จากนั้น write generic functions ที่ accept route key และ automatically infer correct request และ response types นี่ให้ type safety จาก API client ผ่าน handler ไปยัง database ด้วย types defined ใน single place
เมื่อคุณ change API response type ทุก consumer ที่ใช้มันไม่ถูกต้องจะ fail at compile time ใน large codebase ที่มี dozens API endpoints นี่ป้องกัน entire class ของ integration bugs
รูปแบบ 5: Utility Types สำหรับ Transformation
TypeScript’s built-in utility types (Pick, Omit, Partial, Required) มีประสิทธิภาพแต่ไม่เพียงพอสำหรับ complex transformations สร้าง library ของ domain-specific utility types
Common patterns รวม: DeepPartial สำหรับ nested optional fields ใน update operations StrictOmit ที่ errors ถ้าคุณพยายาม omit key ที่ไม่มี Prettify ที่ flatten intersections สำหรับ readable hover types และ NonEmptyArray สำหรับ arrays ที่ต้องมี at least one element
Utility types เหล่านี้ encode business rules ที่ type level function ที่ requires NonEmptyArray parameter ไม่สามารถ receive empty array ขจัด class ของ runtime errors
รูปแบบ 6: Type-Safe Event Systems
Large applications มักจะใช้ event-driven patterns สำหรับ cross-module communication ไม่มี types event systems กลายเป็น maintenance nightmare — คุณกำลัง passing untyped payloads ผ่าน string-keyed channels
Define type map ที่ associates event names ด้วย payload types จากนั้น write generic emit และ listen functions ที่ infer correct payload type จาก event name เพิ่ม new event หมายความว่า adding one entry ไปยัง type map และ all emitters และ listeners ได้รับ correct types automatically
รูปแบบ 7: Const Assertions สำหรับ Configuration
Large codebases มี lots of configuration: feature flags route definitions permission matrices error codes ใช้ const assertions (as const) turn เหล่านี้จาก loose types เป็น literal types
configuration object ที่ defined ด้วย as const ให้ exact string literal types สำหรับ values ซึ่ง enables exhaustive checking autocomplete และ type narrowing ที่ loose string types ไม่สามารถให้ไป
รูปแบบ 8: Template Literal Types สำหรับ String Patterns
TypeScript 4.1 introduced template literal types ที่ให้คุณ define patterns สำหรับ strings นี่เป็น invaluable สำหรับ APIs ที่ follow naming conventions
คุณสามารถ define types สำหรับ API routes CSS class patterns translation keys หรือ any other string ที่ follow predictable format compiler enforces pattern ที่ทุก usage site ไป catching typos และ convention violations at compile time
Compiler Configuration สำหรับ Scale
Strict Mode Is Non-Negotiable
Enable strict: true จาก day one Retrofitting strict mode ไปยัง large codebase เป็นปวดและ expensive individual flags ที่มันเปิดใช้งาน (strictNullChecks, noImplicitAny, strictFunctionTypes, etc.) แต่ละ catch real bugs ที่ would otherwise reach production
Incremental Compilation
Enable incremental: true และ tsBuildInfo output ใน 200K line codebase นี่สามารถ reduce recompilation time จาก 30 วินาที ไปยัง 3 วินาที compiler caches type information และเพียง rechecks changed files และ dependents ของพวกเขา
Project References
สำหรับ monorepos ใช้ TypeScript project references เพื่อ break codebase ของคุณเข้าไป independently compilable units ทุก package ได้รับ own tsconfig.json ด้วย references array compiler builds packages ใน dependency order และ caches results
Testing Types
อย่าเพียงแค่ test runtime behavior — test types ของคุณ Libraries เช่น ts-expect-error comments conditional types ที่ resolve ไปยัง never สำหรับ invalid inputs และ dedicated type testing tools ให้คุณ verify ที่ types ของคุณ catch errors ตามที่คาดหวัง
นี่เป็นอย่างยิ่ง important สำหรับ utility types และ generic functions type ที่ควรมี reject invalid input ควรมี tests proving ที่ invalid input causes compile error
The Long Game
TypeScript ที่ scale เกี่ยวกับ leverage: ทุก hour ที่ลงทุนใน good types บันทึก hundreds hours debugging code review และ runtime errors ข้าม team รูปแบบข้างบนไม่เกี่ยวกับ type gymnastics สำหรับ own sake — พวกเขา encode business rules ป้องกัน real bugs และทำให้ refactoring ปลอดภัย ใน codebases เกินไปใหญ่สำหรับใคร one person เพื่อเข้าใจอย่างเต็มที่