TypeScript
Type-safe JavaScript สำหรับ frontend/backend — static analysis, generics, type-level programming
What I Can Do
- เขียน type-safe application ทั้ง frontend (React/Next.js) และ backend (Node.js)
- ออกแบบ complex type systems — generics, conditional types, mapped types
- Migrate JavaScript projects เข้า TypeScript แบบ incremental
- สร้าง shared type definitions ระหว่าง frontend/backend (monorepo)
- เขียน custom utility types สำหรับ domain-specific patterns
Commands I Use Daily
# type check โดยไม่ emit files (ใช้ตรวจใน CI)
tsc --noEmit
# run TypeScript ตรง (ไม่ต้อง compile ก่อน)
npx tsx src/index.ts
# watch mode — auto recheck เมื่อไฟล์เปลี่ยน
tsc --watch
# init tsconfig.json ใหม่
tsc --initพื้นฐาน
TypeScript คืออะไร
TypeScript คือ JavaScript + ระบบ types — เขียน code เหมือน JavaScript ทุกอย่าง แต่เพิ่ม type annotations เพื่อให้ compiler ช่วยจับ bugs ก่อน run code จริง เมื่อ compile จะได้ JavaScript ธรรมดาออกมา
Basic Types
Types พื้นฐานที่ใช้บ่อย:
string,number,boolean— types หลักnull,undefined— ค่าว่างany— ปิด type checking (หลีกเลี่ยงให้มากที่สุด)unknown— เหมือน any แต่ปลอดภัยกว่า ต้องตรวจ type ก่อนใช้never— ค่าที่ไม่มีวันเกิดขึ้น (เช่น function ที่ throw เสมอ)void— function ที่ไม่ return ค่า
Type Annotations vs Inference
TypeScript ฉลาดพอที่จะ "เดา" type ได้จาก value:
let x = 1— TypeScript รู้ว่าเป็นnumberโดยไม่ต้องบอกlet x: number = 1— บอก type ชัดเจน (ไม่จำเป็นถ้า infer ได้)- ควร annotate: function parameters, complex objects, return types ที่ไม่ชัดเจน
- ไม่ต้อง annotate: ตัวแปรที่ assign ค่าทันที, return types ที่ชัดเจน
Interfaces & Type Aliases
สองวิธีสร้าง custom types:
- Interface — ใช้ describe รูปร่างของ object, extend ต่อได้ เหมาะสำหรับ API contracts
- Type Alias — ใช้ได้กับทุกอย่าง (union, intersection, primitives) แต่ extend ไม่ได้แบบ interface
กฎง่ายๆ: ใช้ interface สำหรับ objects, ใช้ type สำหรับ unions และ types ที่ซับซ้อน
Enums
Enums คือ กลุ่มค่าคงที่ที่ตั้งชื่อให้:
- Numeric enum —
enum Directionที่มีUp = 0, Down = 1, Left = 2, Right = 3 - String enum —
enum Statusที่มีActive = "ACTIVE", Inactive = "INACTIVE" - const enum — compile เป็น inline values ตรงๆ (เล็กกว่า)
- ในหลายกรณี string literal union เช่น
type Status = "active" | "inactive"เรียบง่ายกว่าและ tree-shakeable
Tuple Types
Tuple คือ array ที่กำหนด type ของแต่ละ position:
type Pair = [string, number]— position แรกเป็น string, ที่สองเป็น number- ใช้บ่อยกับ React hooks:
const [count, setCount] = useState(0)return tuple[number, function] type Rest = [string, ...number[]]— string ตัวแรก ตามด้วย numbers กี่ตัวก็ได้
Types ที่ใช้บ่อย
Union Types
ค่าที่เป็นได้หลาย type — ใช้ | คั่น:
type Status = "active" | "inactive"— ค่าเป็นได้แค่สอง string นี้type ID = string | number— เป็น string หรือ number ก็ได้- ต้อง "narrow" ก่อนใช้ เช่น ใช้
if (typeof id === "string")แล้ว TypeScript จะรู้ว่า block นั้น id เป็น string
Intersection Types
รวม types เข้าด้วยกัน — ใช้ &:
type Employee = Person & Job— ต้องมี properties ของทั้ง Person และ Job- ใช้สำหรับ merge object types เข้าด้วยกัน
Literal Types
กำหนดค่าที่ยอมรับได้แบบเฉพาะเจาะจง:
type Direction = "up" | "down" | "left" | "right"— ใช้แทน enum ได้ เรียบง่ายกว่า- แนะนำใช้ string literal unions แทน enums เพราะ simpler และ tree-shakeable
Type Guards & Narrowing
TypeScript ตาม control flow ได้ — เมื่อตรวจ type แล้ว TypeScript จะ narrow type ให้อัตโนมัติ:
typeof x === "string"— narrowing ด้วย typeofx instanceof Date— narrowing ด้วย instanceof"name" in obj— ตรวจว่า property มีไหม- Custom type guard —
function isUser(x: unknown): x is Userสำหรับ logic ที่ซับซ้อน
Type Assertion & as const
Type assertion — บอก TypeScript ว่า "เชื่อเถอะ ค่านี้เป็น type นี้" ใช้ value as Type
- ใช้เท่าที่จำเป็น เพราะ compiler ไม่ตรวจให้ (อาจ error ตอน runtime)
- ถ้าเลือกได้ ใช้ type guard ดีกว่าเพราะ runtime-safe
as const — ทำให้ค่าเป็น readonly และ literal type:
const routes = ["/home", "/about"] as const— type เป็นreadonly ["/home", "/about"]แทนstring[]- ใช้บ่อยกับ config objects, route definitions, lookup tables
- ช่วยให้ TypeScript รู้ค่าที่แน่นอนแทนที่จะเป็นแค่
stringหรือnumber
Index Signatures
กำหนด type สำหรับ object ที่ไม่รู้ keys ล่วงหน้า:
[key: string]: number— keys เป็น string อะไรก็ได้ values เป็น number- ใช้เมื่อ object เป็น dynamic เช่น ข้อมูลจาก API ที่ keys ไม่แน่นอน
- สามารถผสมกับ known properties ได้ เช่น กำหนด
name: stringชัดเจน แล้วที่เหลือเป็น[key: string]: unknown
keyof & typeof Operators
เครื่องมือพื้นฐานสำหรับดึง types จากค่าที่มีอยู่:
- keyof — ดึง keys ของ type ออกมาเป็น union เช่น
keyof Userได้"name" | "age" | "email" - typeof — ดึง type จากตัวแปร เช่น
typeof configได้ type ของ config object - ใช้ร่วมกัน:
keyof typeof configดึง keys ของ object ที่ประกาศด้วยconst - เป็นพื้นฐานของ mapped types และ generic constraints
Functions & Generics
Function Types
- ประกาศ parameter types และ return type:
function add(a: number, b: number): number - Optional parameters:
name?: string - Default values:
count: number = 0 - Overloads — function เดียวรับ input หลาย type แล้ว return type ต่างกันตาม input
Generics คืออะไร
Generics ทำให้เขียน function/class ที่ทำงานกับ "type อะไรก็ได้" โดยยังคง type safety:
function first<T>(arr: T[]): T— รับ array ของ type ใดก็ได้ return element เดียวกัน- ดีกว่า
anyเพราะยังรักษา type information ไว้ - ใช้กับ functions, classes, interfaces, type aliases
Generic Constraints
จำกัดว่า generic type ต้องเป็นอะไร:
T extends string— T ต้องเป็น string หรือ subtype ของ stringK extends keyof T— K ต้องเป็น key ของ T- ใช้เมื่อต้องการ access properties ของ generic type
Utility Types
TypeScript มี built-in utility types ที่ช่วยแปลง types:
Partial<T>— ทุก property เป็น optional (ใช้สำหรับ update functions)Required<T>— ทุก property เป็น requiredPick<T, "a" | "b">— เลือกเฉพาะ properties ที่ต้องการOmit<T, "password">— ตัด properties ที่ไม่ต้องการออกRecord<string, number>— object ที่ keys เป็น string, values เป็น numberReadonly<T>— ทุก property แก้ไม่ได้
Advanced Types
Discriminated Unions
Union ที่มี "ป้ายบอก" ว่าเป็น type ไหน — TypeScript จะ narrow ได้อัตโนมัติเมื่อตรวจป้าย:
- ทุก type ใน union มี field เดียวกัน (discriminant) ที่ค่าต่างกัน
- ใช้
switchหรือifตรวจ discriminant แล้ว TypeScript รู้ type ทันที - เช่น
type Shapeที่มีCircle(kind: "circle") กับSquare(kind: "square") — ตรวจshape.kindแล้วได้ properties ของแต่ละ type
Mapped Types
สร้าง type ใหม่จาก type เดิม โดยแปลงทุก property:
- ใช้
in keyofวน properties — เช่น ทำให้ทุก property เป็น readonly, optional, หรือเปลี่ยน value type - เป็นหลักการเบื้องหลังของ
Partial,Required,Readonly
Conditional Types
Type-level if/else — T extends U ? X : Y:
- ถ้า T เป็น subtype ของ U ได้ type X, ไม่งั้นได้ type Y
- ใช้สร้าง types ที่ปรับตาม input เช่น
type IsString<T>ที่ returntrueหรือfalse - ทำงานกับ unions แบบ distributive (ตรวจทีละตัวใน union)
Infer Keyword
ใช้ "ถาม" TypeScript ว่า type ส่วนไหนเป็นอะไร — ใช้ใน conditional types:
- เช่น
ReturnType<T>ดึง return type ของ function ออกมา Parameters<T>ดึง parameter types ออกมา- สร้าง utility types ที่ extract ส่วนต่างๆ ของ type ได้
Template Literal Types
สร้าง string types จาก template — เช่น type ที่ต้องขึ้นต้นด้วย /api/ ตามด้วย string อะไรก็ได้ หรือ generate combinations จาก union types
Project Setup
Module System
- ES Modules (
import/export) เป็น standard — ตั้งmoduleResolutionใน tsconfig .d.tsfiles = type definitions สำหรับ JavaScript libraries ที่ไม่มี types ในตัว@types/*packages จาก DefinitelyTyped — เช่น@types/express
tsconfig ที่สำคัญ
strict: true— เปิด strict checks ทุกตัว (แนะนำ)noEmit: true— ใช้เมื่อ bundler จัดการ compilation (เช่น Next.js)incremental: true— เร่ง builds โดยใช้ cacheskipLibCheck: true— ข้าม type check ของ node_modules (เร็วขึ้น)
Variance (Covariance / Contravariance)
เรื่องของ type compatibility เมื่อมี inheritance:
- Covariant (output) — ถ้า Dog extends Animal,
Arrayของ Dog ใช้แทนArrayของ Animal ได้ - Contravariant (input) — function parameters ทำงานกลับกัน
- TypeScript 4.7+ มี
in/outkeywords สำหรับระบุ variance ชัดเจน
ปัญหาที่เจอบ่อย & วิธีแก้
Type 'X' is not assignable to type 'Y'
Error ที่เจอบ่อยที่สุด — type ไม่ตรงกัน
สาเหตุ: ส่ง value ผิด type, property ขาดหาย, API return type ไม่ตรงกับที่ประกาศ
วิธีแก้:
- อ่าน error ให้ครบ — TypeScript บอกชัดว่า type ไหนไม่ตรงตรง property ไหน
- ตรวจว่า type definition ตรงกับ data จริงไหม — โดยเฉพาะ API response
- อย่าใช้
asแก้ปัญหา — แก้ที่ type definition หรือ data ให้ถูก - ถ้า type ถูกแล้วจริงๆ แต่ TypeScript ไม่เข้าใจ: ใช้ type guard narrow ก่อน
Object is possibly 'undefined' / 'null'
TypeScript เตือนว่าค่าอาจเป็น undefined หรือ null
สาเหตุ: optional property (user?.name), function ที่อาจ return undefined (เช่น array.find()), API response ที่ field อาจไม่มี
วิธีแก้:
- ตรวจก่อนใช้:
if (user)แล้ว TypeScript จะ narrow ให้ - ใช้ nullish coalescing:
user?.name ?? "Anonymous" - ห้ามใช้
!(non-null assertion) เป็นนิสัย — เหมือนบอก TypeScript ว่า "เชื่อเถอะ ไม่ null" ถ้าผิดจะ crash ตอน runtime - ออกแบบ type ให้ชัดเจน: ถ้า field ต้องมีเสมอ อย่าทำเป็น optional
any แพร่กระจายทั่ว codebase
เริ่มจาก any ตัวเดียว แล้วแพร่ไปทุกที่ที่ใช้ค่านั้น
สาเหตุ: API response ไม่มี type, library ไม่มี type definitions, ใช้ any เพื่อแก้ error เร็วๆ
วิธีแก้:
- ใช้
unknownแทนanyแล้ว narrow ด้วย type guard — บังคับให้ตรวจ type ก่อนใช้ - สร้าง type สำหรับ API response ให้ครบ
- ใช้
@ts-expect-errorแทนanyสำหรับ edge cases ที่ type system ไม่รองรับ — มี error message ชัดเจนกว่า - เปิด
noImplicitAnyใน tsconfig
Type ถูก แต่ runtime error
TypeScript ตรวจแค่ตอน compile — ไม่ได้ป้องกัน runtime errors
สาเหตุ: API return data ไม่ตรง type, as assertion ผิด, any ซ่อน type ที่ผิด, external data ไม่ validate
วิธีแก้:
- validate data ที่ system boundaries (API response, user input, env vars)
- ใช้ runtime validation library เช่น
zod— define schema ครั้งเดียว ได้ทั้ง runtime validation และ TypeScript type - อย่า trust
as— ถ้าต้องใช้asแสดงว่า type อาจไม่ถูก
Generic type ซับซ้อนเกินไป
Type errors ยาว 20 บรรทัดอ่านไม่รู้เรื่อง
สาเหตุ: generics ซ้อนกันหลายชั้น, utility types chain ยาว, library types ที่ซับซ้อน
วิธีแก้:
- hover ดู type ใน IDE — มักจะ resolve ให้เห็น actual type
- แยก type ออกมาตั้งชื่อ — แทนที่จะ inline ทุกอย่าง ให้สร้าง
typeแยกแต่ละชั้น - อ่าน error จากล่างขึ้นบน — root cause มักอยู่ท้ายสุด
- ลอง simplify: เอา generic ออกก่อน ใส่ concrete type ดูว่า error หายไหม
Import type ไม่เจอ / Module not found
TypeScript หา module หรือ type ไม่เจอ
สาเหตุ: path alias ไม่ตรงกับ tsconfig, missing @types/* package, moduleResolution ผิด
วิธีแก้:
- ตรวจ
pathsใน tsconfig ว่าตรงกับ bundler config (เช่น Next.js ใช้@/*) - install
@types/package-nameสำหรับ library ที่ไม่มี built-in types - ตรวจ
moduleResolution— Next.js ใช้"bundler", Node.js ใช้"node16"หรือ"nodenext"
Strict mode เปิดแล้ว error ท่วม
เปิด strict: true ใน project เดิมแล้ว error เป็นร้อย
วิธีแก้:
- อย่าเปิด
strict: trueทีเดียว — เปิดทีละ flag:strictNullChecks,noImplicitAny,strictFunctionTypes - แก้ file ที่ critical ก่อน ที่เหลือใช้
// @ts-expect-errorไว้ก่อน - ใน project ใหม่: เปิด
strict: trueตั้งแต่แรกเสมอ
Related Skills
- Next.js — React framework ที่ใช้ TypeScript เป็น first-class
- JavaScript — ภาษาพื้นฐานที่ TypeScript build ทับ
- Go — backend language ที่ใช้คู่กับ TypeScript frontend