Skip to content

บทที่ 01 — ติดตั้งและตั้งค่า TypeScript

← บทที่ 00 | สารบัญ | บทที่ 02: Basic Types →

⏱ ใช้เวลา 1–2 ชั่วโมง — บทนี้ต้องลงมือทำตามไปด้วย เปิด terminal ไว้เลย

📌 เวอร์ชันอ้างอิงของบทนี้: TypeScript 5.7 (ออกปลายปี 2024 — เป็น baseline ของหนังสือเล่มนี้) ฟีเจอร์บางตัวที่จะพูดถึงในบทหลัง ๆ ต้องการเวอร์ชันที่สูงกว่านี้ — จะระบุ "ต้องการ TS เวอร์ชัน X" กำกับเมื่อใช้


บทนี้จะพาคุณทำอะไร

บทที่แล้วเราเข้าใจ "ภาพใหญ่" ไปแล้ว บทนี้เราจะลงมือจริง:

  1. ติดตั้ง TypeScript ลงเครื่อง
  2. เขียนไฟล์ .ts ไฟล์แรก แล้วทำให้มันรันได้
  3. ทำความรู้จัก tsconfig.json — ไฟล์ตั้งค่าที่สำคัญที่สุด (จะอธิบายทีละบรรทัด ไม่ปล่อยให้งง)
  4. รู้จักวิธีรันโค้ด TypeScript แบบต่าง ๆ และเลือกใช้ให้ถูก
  5. ตั้งค่า editor ให้พร้อมทำงาน

⚠️ สิ่งที่ต้องมีก่อน: เครื่องคุณต้องติดตั้ง Node.js ไว้แล้ว

แนะนำให้ใช้ เวอร์ชันที่เป็น LTS (Long-Term Support = รุ่นที่ได้รับการดูแลระยะยาว มั่นคง เหมาะกับมือใหม่) เช่น Node 22 LTS ขึ้นไป ในบท Part 2.4 เราจะใช้ฟีเจอร์ "รันไฟล์ .ts ตรง ๆ" ซึ่งพฤติกรรมต่างกันตามเวอร์ชัน — Node 22 ใช้ได้แต่ต้องเติม flag --experimental-strip-types เอง ส่วน Node เวอร์ชันที่ใหม่กว่านั้นบางรุ่นเปิดฟีเจอร์นี้ให้เป็นค่าเริ่มต้น (ไม่ต้องเติม flag) — ฟีเจอร์นี้ยังค่อนข้างใหม่/อยู่ในช่วงทดลอง ควรตรวจสอบสถานะล่าสุดจากเอกสาร Node.js อีกที

ถ้ามี Node เวอร์ชันเก่ากว่า 22 อยู่ ก็ยังเรียนตามบทนี้ได้ทั้งหมด เพียงแต่ส่วน "รัน .ts ตรง ๆ" ใน Part 2.4 จะข้ามไป (ใช้วิธี tsx หรือ compile ก่อนแทนได้)

ตัว Node.js จะติดตั้งคำสั่ง node และ npm (Node Package Manager — เครื่องมือจัดการ library) มาให้พร้อมกัน ถ้ายังไม่มี Node เลย ให้ไปดาวน์โหลดจาก nodejs.org ก่อน (เลือกปุ่มที่เขียนว่า LTS) เสร็จแล้วเปิด terminal พิมพ์ node --version เพื่อตรวจสอบว่าติดตั้งสำเร็จ

💡 วิธีเปิด terminal

  • Windows: กด Win + R → พิมพ์ cmd → Enter (หรือค้นหา "PowerShell" ใน Start menu)
  • macOS: กด Cmd + Space → พิมพ์ Terminal → Enter
  • Linux: ขึ้นกับ distro — โดยทั่วไปหาใน Application menu หรือกด Ctrl + Alt + T

💡 npm คืออะไร? npm (Node Package Manager) คือเครื่องมือสำหรับ "ดาวน์โหลดและจัดการ library" ที่มาพร้อม Node.js — เราจะใช้มันลง TypeScript และเครื่องมืออื่น ๆ


Part 1: ติดตั้ง TypeScript

TypeScript ติดตั้งได้ 2 แบบ ต้องเข้าใจความต่างก่อนเลือก

1.1 ติดตั้งแบบ "ผูกกับโปรเจกต์" (แนะนำ)

วิธีนี้คือติดตั้ง TypeScript ไว้ "ในโฟลเดอร์โปรเจกต์" โดยเฉพาะ ก่อนอื่นสร้างโฟลเดอร์โปรเจกต์ใหม่:

bash
mkdir my-ts-project        # สร้างโฟลเดอร์
cd my-ts-project           # เข้าไปในโฟลเดอร์

npm init -y                # สร้างไฟล์ package.json (แฟ้มข้อมูลโปรเจกต์)
npm install -D typescript  # ติดตั้ง TypeScript

อธิบายคำสั่งสุดท้าย:

  • npm install — สั่งดาวน์โหลด library
  • -D — ย่อมาจาก --save-dev หมายถึง "library นี้ใช้แค่ตอนพัฒนา ไม่ต้องเอาไปรันจริง" (เพราะ TypeScript compile เสร็จแล้วเหลือแต่ JavaScript)
  • typescript — ชื่อ library

หลังติดตั้ง TypeScript จะอยู่ในโฟลเดอร์ node_modules ของโปรเจกต์นี้

ทำไมแนะนำวิธีนี้? เพราะแต่ละโปรเจกต์อาจต้องการ TypeScript คนละเวอร์ชัน การผูกเวอร์ชันไว้กับโปรเจกต์ทำให้ทุกคนในทีม (และเซิร์ฟเวอร์ build) ใช้เวอร์ชันเดียวกันเป๊ะ ไม่มีปัญหา "เครื่องฉันรันได้ เครื่องเธอรันไม่ได้"

1.2 ติดตั้งแบบ "ทั้งเครื่อง" (global)

bash
npm install -g typescript   # -g = global

วิธีนี้ติดตั้ง TypeScript ไว้ใช้ได้ทุกที่ในเครื่อง สะดวกสำหรับทดลองเล่น แต่ ไม่แนะนำสำหรับโปรเจกต์จริง เพราะปัญหาเรื่องเวอร์ชันที่กล่าวไปข้างบน

1.3 ตรวจสอบว่าติดตั้งสำเร็จ

bash
npx tsc --version
# ควรขึ้นประมาณ: Version 5.x.x

💡 npx คืออะไร? npx คือคำสั่งที่ช่วย "รันเครื่องมือที่ติดตั้งไว้ในโปรเจกต์" ในที่นี้ npx tsc หมายถึง "รัน tsc ตัวที่อยู่ใน node_modules ของโปรเจกต์นี้" — ถ้าเราพิมพ์ tsc เฉย ๆ เครื่องอาจหาไม่เจอ (เพราะมันไม่ได้ติดตั้งแบบ global)

tsc ย่อมาจาก TypeScript Compiler — คือตัวแปลงและตรวจสอบโค้ดที่เราพูดถึงในบทที่แล้ว


Part 2: เขียนและรันไฟล์ TypeScript ไฟล์แรก

2.1 สร้างไฟล์

สร้างไฟล์ชื่อ hello.ts (สังเกตนามสกุล .ts — นี่คือไฟล์ TypeScript):

typescript
const greet = (name: string): string => `Hello, ${name}!`;

console.log(greet("Alice"));

อธิบายโค้ด:

  • name: string — พารามิเตอร์ name ต้องเป็นข้อความ
  • : string หลังวงเล็บ — ฟังก์ชันนี้คืนค่าเป็นข้อความ
  • ส่วนที่เหลือคือ JavaScript ปกติ

2.2 วิธีที่ 1 — compile แล้วรัน (เข้าใจกลไกพื้นฐาน)

bash
npx tsc hello.ts     # แปลง hello.ts → ได้ไฟล์ hello.js ออกมา
node hello.js        # รันไฟล์ JavaScript ที่ได้

ลองเปิดดูไฟล์ hello.js ที่เกิดขึ้น คุณจะเห็นว่า : string หายไปหมด — นี่คือ type erasure ที่เราเรียนในบทที่แล้วของจริง

ขั้นตอนนี้แสดงให้เห็นกลไกพื้นฐานชัดเจน แต่ในการทำงานจริงเราไม่อยากพิมพ์ 2 คำสั่งทุกครั้ง มีวิธีที่สะดวกกว่า

2.3 วิธีที่ 2 — รันไฟล์ .ts ตรง ๆ ด้วย tsx (แนะนำตอน develop)

tsx เป็นเครื่องมือที่ให้เรา "รันไฟล์ .ts ได้เลย" โดยมันแอบ compile ให้เบื้องหลังแบบเร็วมาก

bash
npm install -D tsx        # ติดตั้ง
npx tsx hello.ts          # รันได้เลย ไม่ต้องสร้าง .js ก่อน

สะดวกกว่ามาก เหมาะกับตอนพัฒนา/ทดลอง

2.4 วิธีที่ 3 — runtime สมัยใหม่ที่รัน TypeScript ได้เอง

bash
# Node.js 22 (LTS) — ต้องเติม flag --experimental-strip-types เอง
node --experimental-strip-types hello.ts

# Node.js เวอร์ชันใหม่กว่า (ตั้งแต่ 23.6 เป็นต้นมา) — บางรุ่นเปิดให้รัน .ts ได้เลย ไม่ต้องเติม flag
node hello.ts

# Bun — runtime ทางเลือกแทน Node.js รองรับ TypeScript เต็มตัวมาแต่เกิด
bun run hello.ts

# Deno — runtime ทางเลือกอีกตัว รองรับ TypeScript เต็มตัวเช่นกัน
deno run hello.ts

📌 หมายเหตุเรื่องเวอร์ชัน Node: ฟีเจอร์ "strip types" (ลบ type ทิ้งแล้วรัน) เพิ่มเข้ามาครั้งแรกใน Node 22 แบบ "ต้องเปิดด้วย flag --experimental-strip-types" จากนั้นตั้งแต่ Node 23.6 เป็นต้นมาบางรุ่นก็เปิดให้เป็นค่าเริ่มต้น (ไม่ต้องเติม flag) แต่ให้ระวังว่า Node เลขคี่ (เช่น 23) เป็นรุ่น Current ไม่ใช่ LTS — หมดอายุการดูแลเร็ว จึงแนะนำให้ยึด รุ่น LTS (เช่น Node 22 LTS หรือรุ่น LTS ที่ใหม่กว่า) เป็นหลัก และเนื่องจากฟีเจอร์นี้ยังใหม่/อยู่ในช่วงทดลอง ควรตรวจสอบสถานะล่าสุดจากเอกสาร Node.js ว่าเวอร์ชันไหนเปิด default ให้แล้วบ้าง ส่วน Node เวอร์ชันต่ำกว่า 22 จะรัน .ts ตรง ๆ ไม่ได้ ต้องใช้ tsx หรือ compile ก่อน

📌 สรุปควรใช้อันไหน? ตอนเรียนและพัฒนา ใช้ tsx (หรือ Bun/Node ถ้ามี) เพราะเร็วและสะดวก ส่วน tsc เราจะใช้เป็นหลักเพื่อ "ตรวจ type" มากกว่าเพื่อ compile — เดี๋ยวจะอธิบายใน Part 7


Part 3: tsconfig.json — ไฟล์ตั้งค่าหัวใจของโปรเจกต์

นี่คือส่วนที่สำคัญที่สุดของบท tsconfig.json คือไฟล์ที่บอก TypeScript ว่า "จะให้เข้มงวดแค่ไหน, จะ compile ออกมาเป็นแบบไหน, จะตรวจไฟล์ไหนบ้าง" ถ้าตั้งค่าผิด ประสบการณ์การใช้ TypeScript จะแย่ไปเลย

3.1 สร้างไฟล์

bash
npx tsc --init

คำสั่งนี้สร้างไฟล์ tsconfig.json พร้อมตัวเลือกมากมาย (และ comment อธิบาย) แต่ค่าตั้งต้นนั้นไม่เหมาะกับงานปี 2026 เราจะเขียนใหม่ให้ดีกว่า

3.2 tsconfig.json ที่แนะนำ (อธิบายทีละบรรทัด)

เพื่อไม่ให้ท่วมตัวเลือกในรอบเดียว เราจะแบ่งเป็น 2 ระดับ:

  • ระดับ "พื้นฐาน" — เปิดไว้ในทุกโปรเจกต์ใหม่ ตัวเลือกที่ตรงตัวเข้าใจง่าย
  • ระดับ "แนะนำเพิ่ม" — ตัวเลือกขั้นสูงที่ดีต่อ "งานจริง" แต่อาจดูแปลกสำหรับมือใหม่ — จะเพิ่มทีหลังเมื่อเริ่มชินแล้ว

ระดับพื้นฐาน — เริ่มจากชุดนี้ก่อน

ลบเนื้อหาในไฟล์ แล้วใส่แบบนี้:

json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "lib": ["ES2022", "DOM"],
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

ทีนี้มาทำความเข้าใจทุกตัว — อย่าข้าม เพราะตัวเลือกพวกนี้คุณจะเจอตลอดชีวิตการเขียน TypeScript

target — "จะ compile ออกมาเป็น JavaScript เวอร์ชันไหน" JavaScript มีหลายเวอร์ชันตามปี (ES2015, ES2020, ES2022, ...) เวอร์ชันใหม่มีไวยากรณ์สะดวกกว่า แต่เครื่องเก่าอาจรันไม่ได้ ตั้ง ES2022 กำลังดีสำหรับปี 2026 — เบราว์เซอร์และ Node.js สมัยใหม่รองรับหมด

target กับ ESNext — คำว่า ESNext ย่อมาจาก "ECMAScript รุ่น Next (ถัดไป)" หมายถึงรุ่นใหม่ล่าสุดที่ TypeScript เวอร์ชันนั้นรู้จัก ในปี 2026 คือประมาณ ES2025 ขึ้นอยู่กับเวอร์ชัน TypeScript ที่ใช้ — สำหรับมือใหม่ใช้ ES2022 ที่ระบุชัดเจนจะปลอดภัยกว่า เพราะพฤติกรรมไม่เปลี่ยนตาม TypeScript version

module — "จะใช้ระบบ import/export แบบไหนในไฟล์ผลลัพธ์" JavaScript มี 2 ระบบหลักในการแยกไฟล์: แบบเก่า (CommonJS — ใช้ require) และแบบใหม่ (ES Modules ย่อ ESM — มาตรฐานใหม่ที่ใช้ import/export) ตั้ง ESNext หมายถึงใช้ระบบใหม่ล่าสุด

moduleResolution — "จะค้นหาไฟล์ที่ import ยังไง" ตอนเราเขียน import x from "./foo" TypeScript ต้องรู้ว่าจะไปหาไฟล์ยังไง ตั้ง Bundler เหมาะกับโปรเจกต์ที่ใช้ bundler สมัยใหม่ (Vite, esbuild) — ข้อดีคือไม่ต้องเติม .js ต่อท้ายตอน import

⚠️ ข้อควรระวัง: ค่า Bundler เหมาะเมื่อโปรเจกต์ มี bundler (Vite/esbuild/webpack) อยู่ในสายงานจริง ถ้าคุณตั้งใจจะรันด้วย Node ล้วน ๆ หรือ compile ด้วย tsc อย่างเดียว (แบบตัวอย่าง npx tsc hello.ts ก่อนหน้านี้) ควรใช้ "moduleResolution": "NodeNext" คู่กับ "module": "NodeNext" แทน มิฉะนั้นเส้นทาง import ที่ tsc ยอมให้ผ่านอาจกลับพังตอนรันจริงบน Node — สำหรับบทเรียนนี้ที่เราใช้ tsx เป็นหลัก Bundler ยังใช้ได้สบาย

lib — "มี API อะไรให้ใช้ได้บ้าง" บอก TypeScript ว่าโค้ดเราจะรันในสภาพแวดล้อมที่มีอะไร ["ES2022", "DOM"] หมายถึง "มีฟีเจอร์ JavaScript ES2022 และมี API ของเบราว์เซอร์ (DOM — เช่น document, window)" ถ้าเขียนโปรแกรมฝั่ง Node.js ล้วน ๆ อาจตัด "DOM" ออก

outDir — "compile แล้วเอาไฟล์ .js ไปไว้โฟลเดอร์ไหน" — ที่นี่คือ ./dist

rootDir — "ไฟล์โค้ดต้นฉบับอยู่โฟลเดอร์ไหน" — ที่นี่คือ ./src

strictตัวสำคัญที่สุด — เปิดโหมดเข้มงวดทั้งหมด (อธิบายละเอียดใน 3.3)

esModuleInterop — ช่วยให้ import library แบบเก่า (CommonJS) กับแบบใหม่ (ESM) เข้ากันได้ราบรื่น — เปิดไว้เถอะ

skipLibCheck — "ไม่ต้องตรวจ type ในไฟล์ของ library คนอื่น" — เปิดไว้เพื่อให้ compile เร็วขึ้น (เราเชื่อว่า library เขาตรวจมาแล้ว)

forceConsistentCasingInFileNames — บังคับให้การใช้ตัวพิมพ์เล็ก/ใหญ่ในชื่อไฟล์ตรงกันเป๊ะ ป้องกันบั๊กที่เกิดเฉพาะบนบางระบบปฏิบัติการ (Windows ไม่สนตัวพิมพ์ แต่ Linux สน)

resolveJsonModule — อนุญาตให้ import ไฟล์ .json ได้

ส่วน 2 บรรทัดล่าง:

  • include — "ตรวจไฟล์ในโฟลเดอร์ไหน" — ที่นี่คือทุกไฟล์ใน src
  • exclude — "ไม่ต้องตรวจโฟลเดอร์ไหน"

ระดับแนะนำเพิ่ม — เปิดเมื่อเริ่มชินแล้ว

ตัวเลือกกลุ่มนี้เป็นของ "งานจริงในปี 2026 มักเปิด" แต่ผลของมันเข้าใจยากกว่าตัวข้างบน ถ้าเพิ่งเริ่ม ข้ามกล่องนี้ไปก่อนได้เลย แล้วค่อยกลับมาเปิดอ่านตอนที่เริ่มชินกับ tsconfig แล้ว

กดเพื่อดู "ตัวเลือกแนะนำเพิ่ม" (ขั้นสูง — ข้ามได้ถ้าเพิ่งเริ่ม)
json
{
  "compilerOptions": {
    "isolatedModules": true,
    "verbatimModuleSyntax": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,
    "isolatedDeclarations": true
  }
}

💡 สองตัวเลือกแรก (isolatedModules และ verbatimModuleSyntax) ควบคุมวิธีที่ TypeScript จัดการ import ที่เป็น type เพื่อให้ build tool ทำงานได้ถูกต้อง — อ่านคำอธิบายแล้วค่อยเปิดใช้ทีหลังได้

isolatedModules — บังคับให้แต่ละไฟล์ compile แยกกันได้แบบไม่ต้องรู้ไฟล์อื่น

ทำไมต้องบังคับแบบนี้? เพราะ bundler สมัยใหม่ (เช่น Vite, esbuild, swc — เครื่องมือที่รวมและแปลงไฟล์ให้เร็ว) จะทำงานแบบ "ทีละไฟล์" เพื่อความเร็ว มันไม่ได้มองทั้งโปรเจกต์พร้อมกัน แต่ฟีเจอร์บางตัวของ TypeScript ต้องดูทั้งโปรเจกต์ถึงจะ compile ถูก ตัวเลือกนี้จึงคอย "ห้าม" ไม่ให้เราใช้ฟีเจอร์เหล่านั้น เพื่อให้ bundler ทำงานได้ไม่มีปัญหา

ตัวอย่างฟีเจอร์ที่โดนห้าม: const enum (จะเรียนในบทที่ 02) จะใช้ไม่ได้เมื่อเปิด option นี้

verbatimModuleSyntax — ("verbatim" แปลว่า "ตามตัวอักษร") บังคับให้เขียน import type กำกับเมื่อ import เฉพาะ type — ทำให้ TypeScript ลบ import ที่เป็น type ทิ้งตอน compile ได้ถูกต้องเสมอ ตัวอย่าง:

typescript
// ❌ ไม่ผ่าน (เมื่อ verbatimModuleSyntax: true)
// User เป็น type อย่างเดียว แต่เขียน import แบบ value
import { User } from "./types";   // ไฟล์ ./types.ts คือตัวอย่างสมมติ — ยังไม่ต้องสร้างตอนนี้

function greet(user: User) {
    return `Hello ${user.name}`;
}

// ✅ ผ่าน — เติม "type" กำกับให้ชัด
import type { User } from "./types";   // ไฟล์ ./types.ts คือตัวอย่างสมมติ — ยังไม่ต้องสร้างตอนนี้

function greet(user: User) {
    return `Hello ${user.name}`;
}

// ✅ ผ่าน — กรณีไฟล์เดียวกัน export ทั้ง value และ type
import { calculate, type CalcOptions } from "./calc";
//        ^^^^^^^^^ value           ^^^^^^^^^^^^^^^^^ type (ต้องเติม type ข้างหน้า)

ทำไมถึงสำคัญ — เพราะถ้าไม่เติม type เครื่องมือ build บางตัวจะคิดว่า User เป็น value แล้วพยายาม require ไฟล์ที่อาจ export type เปล่า ๆ (ไม่มี value จริง) — เกิด error ตอน build

noUncheckedIndexedAccess — ตัวนี้สำคัญและคนมักไม่เปิด (อธิบายใน 3.4)

noImplicitOverride — เมื่อ override method ของ class แม่ ต้องเขียนคำว่า override กำกับให้ชัด (เรื่อง class จะลงลึกในบทที่ 08 — ตอนนี้แค่รู้ว่ามีตัวเลือกนี้)

isolatedDeclarations — (TS 5.5+) บังคับให้ทุก exported declaration มี type annotation ชัดเจนพอที่จะสร้างไฟล์ .d.ts ได้โดยไม่ต้องรัน type inference — ทำให้ build .d.ts แบบ parallel ได้ เหมาะกับ monorepo ขนาดใหญ่

⚠️ ตัวนี้เข้มมาก อย่าเพิ่งเปิด — มันบังคับให้ทุกอย่างที่ export (ทั้งฟังก์ชันและตัวแปร) ต้องเขียน type ของค่าที่คืน/ชนิดกำกับเองให้ครบ ห้ามพึ่ง "การเดา type อัตโนมัติ" ของ TypeScript เลย โค้ดมือใหม่ทั่วไปที่ปล่อยให้ TS เดา type ให้ พอเปิดตัวนี้จะขึ้น error เต็มไปหมดจนงง แนะนำให้ ปิดไว้ก่อน แล้วค่อยเปิดเฉพาะตอนที่คุณต้องการสร้างไฟล์ .d.ts แบบขนานในโปรเจกต์ใหญ่จริง ๆ

3.3 strict: true — เปิดไว้เสมอ และนี่คือเหตุผล

"strict": true เป็นการเปิดสวิตช์ตรวจสอบที่เข้มงวดหลายตัวพร้อมกัน ตัวที่สำคัญที่สุด 2 ตัวคือ:

1. noImplicitAny — ห้าม TypeScript "เดา type เป็น any เงียบ ๆ"

typescript
// ถ้าไม่เปิด strict — TypeScript ปล่อยผ่าน (param เป็น any โดยปริยาย)
function double(x) { return x * 2; }

// ถ้าเปิด strict — ฟ้องทันที!
// ❌ Error: Parameter 'x' implicitly has an 'any' type.
//   (implicitly = โดยปริยาย/ไม่ได้บอกชัด — TypeScript เดาให้เป็น any แบบเงียบ ๆ)
// → บังคับให้เราใส่ type: function double(x: number)

จำได้ไหมว่า any คือ "การปิดการตรวจสอบ" — ถ้าปล่อยให้ TypeScript เดาเป็น any เงียบ ๆ คุณจะเสียประโยชน์ของ TypeScript ไปฟรี ๆ

2. strictNullChecks — บังคับให้จัดการกับค่า null / undefined อย่างชัดเจน

typescript
// ถ้าไม่เปิด — โค้ดนี้ผ่าน แต่จะพังตอน runtime ถ้า find ไม่เจอ
const user = users.find(u => u.id === 1);
console.log(user.name);   // 💥 อาจ "Cannot read property 'name' of undefined"

// ถ้าเปิด — TypeScript ฟ้องว่า user อาจเป็น undefined
// ❌ Error: 'user' is possibly 'undefined'.
// → บังคับให้เราเช็กก่อน: if (user) { console.log(user.name); }

นี่คือฟีเจอร์ที่ป้องกันบั๊กที่พบบ่อยที่สุดในโลก JavaScript — "อ่าน property ของ null/undefined"

🎯 กฎเหล็ก: โปรเจกต์ใหม่ทุกโปรเจกต์ เปิด strict: true เสมอ การปิดมันเพื่อ "ความง่าย" คือการโกหกตัวเอง — บั๊กไม่ได้หายไป มันแค่ถูกเลื่อนไปเจอตอน runtime

3.4 noUncheckedIndexedAccess — ตัวที่ควรเปิดเพิ่ม

ตัวนี้ไม่ได้รวมอยู่ใน strict แต่ควรเปิด มันแก้ปัญหานี้:

typescript
const fruits = ["apple", "banana"];

const third = fruits[2];   // ตำแหน่ง 2 ไม่มีของจริง!

โดยปกติ TypeScript จะบอกว่า third เป็น string — แต่ความจริงตอน runtime มันคือ undefined (เพราะ array มีแค่ 2 ตัว)

เมื่อเปิด noUncheckedIndexedAccess TypeScript จะซื่อสัตย์ขึ้น — มันจะบอกว่า third เป็น string | undefined บังคับให้เราเช็กก่อนใช้ ป้องกันบั๊กได้อีกชั้น

3.5 ตัวเลือกเข้มงวดเพิ่มเติม (สำหรับโปรเจกต์ที่อยากแน่นจริง)

เมื่อคุณเก่งขึ้น พิจารณาเปิดพวกนี้เพิ่ม:

json
{
  "noFallthroughCasesInSwitch": true,        // กัน switch-case ที่ลืมใส่ break
  "noPropertyAccessFromIndexSignature": true, // บังคับใช้ obj["key"] กับ index signature
  "exactOptionalPropertyTypes": true          // แยกแยะ "ไม่มี field" กับ "field เป็น undefined"
}

ตอนนี้ยังไม่ต้องเปิดก็ได้ ค่อยกลับมาเมื่อเข้าใจมากขึ้น


Part 4: วิธีรันโค้ด TypeScript — เลือกใช้ให้ถูกงาน

เราเห็นวิธีรันไปแล้วใน Part 2 มาสรุปให้ชัดว่าแต่ละแบบเหมาะกับอะไร

4.1 ตอนพัฒนา (development) — ใช้ tsx watch (โหมดเฝ้าดูไฟล์)

bash
npx tsx watch src/main.ts

watch (เฝ้าดู) หมายถึง "เฝ้าดูไฟล์" — ทุกครั้งที่คุณแก้โค้ดแล้วเซฟ มันจะรันใหม่ให้อัตโนมัติ ทำให้พัฒนาเร็ว

4.2 ตรวจชนิด (type-check) อย่างเดียว — ใช้ tsc --noEmit

bash
npx tsc --noEmit

--noEmit แปลว่า "ตรวจ type อย่างเดียว ไม่ต้องสร้างไฟล์ .js" คำสั่งนี้สำคัญมาก เพราะ tsx/Bun ตอนรันโค้ด จะข้ามการตรวจ type เพื่อความเร็ว — มันแค่ลบ type ทิ้งแล้วรัน ดังนั้นเราต้องมีคำสั่งแยกไว้ "ตรวจ type จริงจัง" และนั่นคือ tsc --noEmit

4.3 โหมด watch สำหรับตรวจ type

bash
npx tsc --noEmit --watch

เปิดทิ้งไว้ใน terminal อีกหน้าต่าง มันจะตรวจ type ให้ตลอดเวลาที่คุณเขียนโค้ด


Part 5: ตั้งค่า script ใน package.json

แทนที่จะจำคำสั่งยาว ๆ เราตั้ง "ทางลัด" ไว้ในไฟล์ package.json ได้ เปิดไฟล์นั้น แล้วเพิ่มส่วน scripts:

json
{
  "scripts": {
    "dev": "tsx watch src/main.ts",
    "typecheck": "tsc --noEmit",
    "build": "tsc"
  }
}

จากนั้นเรียกใช้ด้วย npm run:

bash
npm run dev         # รันโหมดพัฒนา
npm run typecheck   # ตรวจ type
npm run build       # compile เป็น .js

ข้อดีคือทุกคนในทีมใช้คำสั่งชุดเดียวกัน ไม่ต้องจำรายละเอียด


Part 6: ตั้งค่า Editor (VS Code)

VS Code คือ editor ที่เข้ากับ TypeScript ดีที่สุด — มันมี "TypeScript Language Server" ติดตั้งมาในตัว ทำให้เห็น error และ autocomplete แบบเรียลไทม์

6.1 คีย์ลัดที่ต้องรู้

คีย์ลัดทำอะไร
เอาเมาส์ชี้ (hover)แสดง type ของสิ่งนั้น
F12กระโดดไปยังจุดที่นิยาม (definition) ของสิ่งนั้น
Shift+F12หาทุกที่ที่ใช้สิ่งนี้ (find references)
F2เปลี่ยนชื่อ (rename) ทุกที่ในโปรเจกต์พร้อมกัน
Ctrl+.แสดง "quick fix" — TypeScript เสนอวิธีแก้ error ให้

ลองใช้ F2 rename ดูสักครั้ง คุณจะเห็นพลังของ TypeScript ทันที

6.2 ตั้งค่า VS Code ที่แนะนำ

สร้างไฟล์ .vscode/settings.json ในโปรเจกต์:

json
{
  "typescript.tsdk": "node_modules/typescript/lib",
  "typescript.updateImportsOnFileMove.enabled": "always",
  "editor.codeActionsOnSave": {
    "source.organizeImports": "explicit"
  }
}
  • typescript.tsdk — บอก VS Code ให้ใช้ TypeScript เวอร์ชันของโปรเจกต์ (ไม่ใช่เวอร์ชันที่ติดมากับ VS Code) — สำคัญเพื่อให้ตรงกับตอน build
  • updateImportsOnFileMove — เวลาย้ายไฟล์ ให้แก้ path ใน import ให้อัตโนมัติ
  • organizeImports — เวลาเซฟ ให้จัดเรียง import ให้เป็นระเบียบ

Part 7: Path Alias — เลิกเขียน import แบบ ../../..

เมื่อโปรเจกต์ใหญ่ขึ้น คุณจะเจอ import น่าเกลียดแบบนี้:

typescript
import { Button } from "../../../components/Button";

แก้ได้ด้วย "path alias" — ตั้งชื่อย่อแทน path เพิ่มใน tsconfig.json:

json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}

แล้วเขียน import ได้สวยขึ้น:

typescript
import { Button } from "@/components/Button";

⚠️ ข้อควรระวัง: path alias ต้องตั้งให้ "เครื่องมือ build รู้จักด้วย" ไม่ใช่แค่ TypeScript ถ้าใช้ Vite ต้องไปตั้งในไฟล์ vite.config.ts ด้วย:

typescript
resolve: { alias: { "@": "/src" } }

ไม่งั้น TypeScript จะไม่ฟ้อง error แต่โปรแกรมจะพังตอน build เพราะ bundler หาไฟล์ไม่เจอ

ถ้ายังไม่ได้ใช้ Vite (หรือ bundler ใด ๆ) — ข้ามส่วนนี้ไปก่อนได้ ตอนนี้แค่รู้จักหน้าตา path alias พอ เดี๋ยวเจอตอนใช้ Vite จริงค่อยกลับมาตั้ง


Part 8: โครงสร้างโปรเจกต์ที่แนะนำ

text
my-ts-project/
├── src/                    ← โค้ดต้นฉบับทั้งหมดอยู่ที่นี่
│   ├── main.ts             ← ไฟล์เริ่มต้น
│   ├── lib/                ← ฟังก์ชันช่วยเหลือต่าง ๆ
│   └── types/              ← ไฟล์ type ที่เราเขียนเอง (.d.ts)
├── dist/                   ← ผลลัพธ์หลัง build (ควรใส่ใน .gitignore)
├── tsconfig.json           ← ไฟล์ตั้งค่า TypeScript
├── package.json            ← ไฟล์ข้อมูลโปรเจกต์
└── node_modules/           ← library ที่ติดตั้ง (ควรใส่ใน .gitignore)

หลักการง่าย ๆ: โค้ดที่เราเขียนอยู่ใน src/ ผลลัพธ์ที่ build ออกมาอยู่ใน dist/


Part 9: แก้ Error ที่เจอบ่อยตอนเริ่มต้น

มือใหม่มักเจอ error ไม่กี่แบบซ้ำ ๆ มารู้จักไว้ล่วงหน้า

9.1 "Cannot find name 'X'" / "Cannot find module"

แปลว่า TypeScript ไม่รู้จักสิ่งนั้น สาเหตุที่พบบ่อย:

  • ลืม import — เพิ่ม import ให้ถูก
  • library ไม่มี type — ติดตั้งไฟล์ type เพิ่ม: npm install -D @types/ชื่อlibrary

9.2 "Type 'X' is not assignable to type 'Y'"

แปลว่า "คุณเอา type X ไปใส่ที่ที่ต้องการ type Y" — type ไม่ตรงกัน ให้ดูว่าค่าที่ส่งผิดประเภทตรงไหน นี่คือ error ที่ดี — TypeScript กำลังจับบั๊กให้คุณ

9.3 "Object is possibly 'null'" หรือ "possibly 'undefined'"

แปลว่าค่านั้นอาจเป็น null/undefined (มาจาก strictNullChecks) วิธีแก้คือเช็กก่อนใช้:

typescript
// วิธีที่ 1 — เช็กด้วย if
if (user) {
    console.log(user.name);
}

// วิธีที่ 2 — optional chaining (ถ้า user เป็น null/undefined ทั้งนิพจน์จะได้ undefined)
console.log(user?.name);

9.4 "Parameter 'x' implicitly has an 'any' type"

แปลว่าคุณลืมใส่ type ให้พารามิเตอร์ (มาจาก noImplicitAny) — แค่ใส่ type ให้มัน


Part 10: Lab — สร้างโปรเจกต์จริง

ทำตามขั้นตอนนี้ให้ครบ จะได้โปรเจกต์ TypeScript ที่พร้อมใช้งาน

ขั้นที่ 1 — สร้างโปรเจกต์

bash
mkdir ts-playground && cd ts-playground
npm init -y
npm install -D typescript tsx

ขั้นที่ 2 — สร้าง tsconfig.json ตามแบบใน Part 3.2 (ก๊อปได้เลย)

ขั้นที่ 3 — สร้างไฟล์ src/main.ts

typescript
interface Task {
    id: number;
    title: string;
    done: boolean;
}

function summarize(tasks: Task[]): string {
    const doneCount = tasks.filter(t => t.done).length;
    return `เสร็จแล้ว ${doneCount} จาก ${tasks.length} งาน`;
}

const myTasks: Task[] = [
    { id: 1, title: "เรียน TypeScript", done: true },
    { id: 2, title: "ทำ Lab", done: false },
];

console.log(summarize(myTasks));

ขั้นที่ 4 — เพิ่ม script ใน package.json (ตาม Part 5)

ขั้นที่ 5 — รัน

bash
npm run dev         # ควรเห็น: เสร็จแล้ว 1 จาก 2 งาน
npm run typecheck   # ควรไม่มี error

ขั้นที่ 6 (ทดลองให้เกิด error) — ลองแก้ done: false เป็น done: "no" แล้วรัน npm run typecheck อีกครั้ง คุณควรเห็น TypeScript ฟ้อง error — นี่คือ TypeScript กำลังทำงานปกป้องคุณ จากนั้นแก้กลับ


Part 11: Checkpoint

1. ทำไมแนะนำติดตั้ง TypeScript แบบผูกกับโปรเจกต์ มากกว่าแบบ global?

เพื่อให้ทุกคนในทีมและเซิร์ฟเวอร์ build ใช้ TypeScript เวอร์ชันเดียวกันเป๊ะ ป้องกันปัญหา "เครื่องฉันรันได้ เครื่องเธอรันไม่ได้"

2. tsc กับ tsx ต่างกันยังไง?

tsc = TypeScript Compiler — ตรวจ type และ/หรือ compile เป็นไฟล์ .js tsx = เครื่องมือที่ให้ "รันไฟล์ .ts ได้เลย" ตอนพัฒนา (เร็ว แต่ข้ามการตรวจ type)

3. strict: true เปิดอะไรที่สำคัญบ้าง?

ตัวสำคัญคือ noImplicitAny (ห้ามเดา type เป็น any เงียบ ๆ) และ strictNullChecks (บังคับจัดการ null/undefined ให้ชัดเจน)

4. noUncheckedIndexedAccess ทำอะไร?

ทำให้การเข้าถึง array/object ด้วย index ให้ type เป็น T | undefined แทน T — สะท้อนความจริงว่าตำแหน่งนั้นอาจไม่มีของ บังคับให้เราเช็กก่อนใช้

5. target กับ module ใน tsconfig ต่างกันยังไง?

target = "compile ออกมาเป็น JavaScript เวอร์ชันไหน" (เกี่ยวกับไวยากรณ์) module = "ใช้ระบบ import/export แบบไหนในไฟล์ผลลัพธ์" (CommonJS หรือ ES Modules)

6. ทำไมต้องมีคำสั่ง tsc --noEmit แยกต่างหาก ทั้งที่ใช้ tsx รันได้?

เพราะ tsx/Bun ตอนรัน จะข้ามการตรวจ type เพื่อความเร็ว — มันแค่ลบ type ทิ้งแล้วรัน เราจึงต้องมี tsc --noEmit ไว้ "ตรวจ type จริงจัง" แยกต่างหาก

7. ตั้ง path alias แล้วต้องทำอะไรเพิ่ม?

ต้องตั้งให้ "เครื่องมือ build รู้จักด้วย" เช่นใน vite.config.ts ไม่ใช่แค่ใน tsconfig.json ไม่งั้นจะพังตอน build


Part 12: สรุปบทนี้

  • ติดตั้ง TypeScript แบบ ผูกกับโปรเจกต์ ด้วย npm install -D typescript
  • รันโค้ดตอนพัฒนาด้วย tsx, ตรวจ type ด้วย tsc --noEmit — เป็นคนละงานกัน
  • tsconfig.json คือหัวใจ — เปิด strict: true เสมอ และเพิ่ม noUncheckedIndexedAccess
  • ตั้ง script ใน package.json ให้ทีมใช้คำสั่งชุดเดียวกัน
  • ตั้งค่า VS Code ให้ใช้ TypeScript เวอร์ชันของโปรเจกต์
  • โครงสร้าง: โค้ดอยู่ src/, ผลลัพธ์อยู่ dist/

บทถัดไป — เริ่มเรียน Basic Types ของจริง


← บทที่ 00 | สารบัญ | บทที่ 02: Basic Types →