Migration Strategy
migrate ทีละส่วน ไม่ใช่ทีเดียว
หัวข้อที่มีชื่อว่า “migrate ทีละส่วน ไม่ใช่ทีเดียว”การ rewrite JS codebase จริงไป TypeScript ทีเดียวคือวิธีที่ migration ตายกัน จุดสำคัญของ TypeScript คือเป็น type system แบบ gradual: JS และ TS อยู่ด้วยกันได้ทีละไฟล์ คุณจึงแปลงทีละนิดขณะที่ยัง ship ได้ตลอด
เส้นทางแบบเป็น phase
หัวข้อที่มีชื่อว่า “เส้นทางแบบเป็น phase”flowchart LR js["allowJs: TS builds alongside JS"] --> checkjs["checkJs: type-check the JS too"] checkjs --> convert["rename .js to .ts file by file"] convert --> strict["ratchet strict flags one at a time"]
- เปิด
allowJsตอนนี้ TypeScript compile project แบบผสมได้ — ไฟล์.jsbuild เหมือนเดิม และคุณเริ่มเพิ่มไฟล์.tsข้าง ๆ ได้ - เพิ่ม
checkJs(และ// @ts-check) TypeScript type-check JavaScript ของคุณด้วย inference และ JSDoc คุณได้ error จริงก่อนแปลงไฟล์สักไฟล์ — first pass ฟรี ๆ - แปลงทีละไฟล์ rename
.jsเป็น.ts, แก้ error ที่โผล่, commit เริ่มจาก leaf (utility ที่ dependency น้อย) แล้วไล่เข้าหา core - ratchet strictness อย่าเริ่มด้วย
strictเต็ม เปิด flag ทีละตัว —noImplicitAnyก่อน แล้วstrictNullChecksแล้วที่เหลือ แก้แต่ละ wave ให้เสร็จก่อนเปิดตัวถัดไป
แต่ละ step ship ได้ คุณไม่เคยติดอยู่ในสภาพ migrate ค้างครึ่ง ๆ นาน ๆ
any เป็น tool ก่อน แล้วเป็นหนี้
หัวข้อที่มีชื่อว่า “any เป็น tool ก่อน แล้วเป็นหนี้”ระหว่าง migrate any เป็นวิธีที่ถูกต้องในการบอกว่า “ยังไม่ type — ไปต่อก่อน” อันตรายคือปล่อยทิ้งไว้ วินัยสองข้อช่วยให้ any ซื่อสัตย์:
- ทำให้ explicit และ grep เจอ เลือก
anyที่ explicit (หรือ alias// TODO: type this) แทน implicit เพื่อให้หาและกำจัดทีหลังได้ - ratchet ด้วย
noImplicitAnyพอเปิด compiler เลิกแอบใส่any— code ใหม่ต้อง type หนี้จึงมีแต่ลดลง
@ts-expect-error ดีกว่า @ts-ignore
หัวข้อที่มีชื่อว่า “@ts-expect-error ดีกว่า @ts-ignore”เมื่อต้อง suppress error ให้ใช้ @ts-expect-error ไม่ใช่ @ts-ignore:
// @ts-expect-error — legacy shape, tracked in TICKET-123legacyCall(weirdValue);ทั้งคู่ silence error ในบรรทัดถัดไป แต่ @ts-expect-error จะ error ด้วยถ้าบรรทัดนั้นเลิกมี error ดังนั้นเมื่อคุณแก้ type ที่เป็นต้นเหตุทีหลัง suppression ที่ไม่จำเป็นแล้วจะทำให้ build พัง และบอกให้คุณลบทิ้ง ส่วน @ts-ignore แค่เน่าเงียบ ๆ
การเขียน library
หัวข้อที่มีชื่อว่า “การเขียน library”ถ้าคุณ publish package มีกฎเพิ่มอีกไม่กี่ข้อ:
- ship declaration file (
.d.ts) ตั้ง"declaration": trueเพื่อให้ผู้ใช้ได้ type ของคุณ ชี้"types"ในpackage.jsonไปที่ entry.d.ts - อย่าให้ internal type leak เฉพาะ type ของ public API เท่านั้นที่เป็น contract เก็บ type ของ implementation ไว้ไม่ export เพื่อให้เปลี่ยนได้อย่างอิสระ
- มอง type เป็น semver การเปลี่ยนที่ทำให้ type checking ของผู้ใช้พังคือ breaking change แม้ runtime behavior จะเหมือนเดิม การ widen parameter มักปลอดภัย; การ narrow parameter หรือเปลี่ยน return type ทำให้ build ของ downstream พังได้