Skip to content
← All Skills
🔷

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

bash
# 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 enumenum Direction ที่มี Up = 0, Down = 1, Left = 2, Right = 3
  • String enumenum 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 ด้วย typeof
  • x 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 ของ string
  • K 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 เป็น required
  • Pick<T, "a" | "b"> — เลือกเฉพาะ properties ที่ต้องการ
  • Omit<T, "password"> — ตัด properties ที่ไม่ต้องการออก
  • Record<string, number> — object ที่ keys เป็น string, values เป็น number
  • Readonly<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> ที่ return true หรือ 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.ts files = type definitions สำหรับ JavaScript libraries ที่ไม่มี types ในตัว
  • @types/* packages จาก DefinitelyTyped — เช่น @types/express

tsconfig ที่สำคัญ

  • strict: true — เปิด strict checks ทุกตัว (แนะนำ)
  • noEmit: true — ใช้เมื่อ bundler จัดการ compilation (เช่น Next.js)
  • incremental: true — เร่ง builds โดยใช้ cache
  • skipLibCheck: 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/out keywords สำหรับระบุ 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