Home > Blog > tech

TypeScript Best Practices 2026 เขียน TypeScript ให้ดีขึ้นด้วย 20 เทคนิคจากมืออาชีพ

TypeScript Best Practices 2026
2026-04-16 | tech | 3800 words

TypeScript กลายเป็นมาตรฐานของ Frontend และ Backend development ไปแล้ว ในปี 2026 ถ้าคุณยังเขียน TypeScript เหมือน JavaScript ที่แค่ใส่ Type ไป คุณยังไม่ได้ใช้ TypeScript เต็มศักยภาพ บทความนี้รวม 20 เทคนิคจากมืออาชีพที่ใช้จริงใน Production

1. เปิด Strict Mode เสมอ

// tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    // strict = เปิดทั้งหมดเหล่านี้:
    // "noImplicitAny": true,
    // "strictNullChecks": true,
    // "strictFunctionTypes": true,
    // "strictBindCallApply": true,
    // "strictPropertyInitialization": true,
    // "noImplicitThis": true,
    // "alwaysStrict": true,
    // "useUnknownInCatchVariables": true
  }
}
เหตุผล: Strict mode จับ Bug ตั้งแต่ตอน Compile ไม่ต้องรอ Runtime error ถ้าเริ่มโปรเจกต์ใหม่ เปิด strict ตั้งแต่แรก

2. อย่าใช้ any — ใช้ unknown แทน

// BAD — any ปิด Type checking ทั้งหมด
function parse(data: any) {
  return data.name.toUpperCase(); // ไม่ Error แต่ Runtime crash ได้
}

// GOOD — unknown บังคับให้ตรวจสอบก่อนใช้
function parse(data: unknown) {
  if (typeof data === 'object' && data !== null && 'name' in data) {
    return (data as { name: string }).name.toUpperCase();
  }
  throw new Error('Invalid data');
}

3. ใช้ Discriminated Unions

// Discriminated Union — ทำให้ Type narrowing อัตโนมัติ
type Result<T> =
  | { status: 'success'; data: T }
  | { status: 'error'; error: string }
  | { status: 'loading' };

function handleResult(result: Result<User>) {
  switch (result.status) {
    case 'success':
      console.log(result.data.name); // TypeScript รู้ว่ามี data
      break;
    case 'error':
      console.log(result.error); // TypeScript รู้ว่ามี error
      break;
    case 'loading':
      console.log('Loading...');
      break;
  }
}

4. const assertion

// without as const — Type กว้างเกินไป
const colors = ['red', 'green', 'blue']; // string[]
const config = { api: '/api', timeout: 5000 }; // { api: string; timeout: number }

// with as const — Type แคบลง เป็น Literal types
const colors = ['red', 'green', 'blue'] as const; // readonly ["red", "green", "blue"]
const config = { api: '/api', timeout: 5000 } as const;
// { readonly api: "/api"; readonly timeout: 5000 }

5. satisfies Operator (TypeScript 4.9+)

type Colors = Record<string, [number, number, number]>;

// ปัญหา: ถ้าใช้ type annotation จะ lose literal types
const colors: Colors = {
  red: [255, 0, 0],
  green: [0, 255, 0],
};
colors.red; // [number, number, number] — ไม่รู้ว่ามี key "red"

// satisfies: ตรวจสอบ Type แต่ไม่ widen
const colors = {
  red: [255, 0, 0],
  green: [0, 255, 0],
} satisfies Colors;
colors.red; // [number, number, number] — รู้ว่ามี key "red" ✓
colors.blue; // Error! ✓

6. Template Literal Types

type EventName = 'click' | 'focus' | 'blur';
type Handler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"

type Route = `/${string}`;
function navigate(path: Route) { /* ... */ }
navigate('/users');    // OK ✓
navigate('users');     // Error! ✓ ต้องขึ้นต้นด้วย /

7. Branded Types

// ปัญหา: UserId กับ OrderId เป็น string เหมือนกัน สลับกันได้
type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };

function createUserId(id: string): UserId { return id as UserId; }
function createOrderId(id: string): OrderId { return id as OrderId; }

function getUser(id: UserId) { /* ... */ }
function getOrder(id: OrderId) { /* ... */ }

const userId = createUserId('u-123');
const orderId = createOrderId('o-456');

getUser(userId);    // OK ✓
getUser(orderId);   // Error! ✓ ป้องกันสลับ ID

8. Exhaustive Checking

type Shape = 'circle' | 'square' | 'triangle';

function area(shape: Shape): number {
  switch (shape) {
    case 'circle': return Math.PI * 10 * 10;
    case 'square': return 10 * 10;
    case 'triangle': return 0.5 * 10 * 10;
    default:
      // ถ้าเพิ่ม Shape ใหม่แล้วลืม handle → Compile error!
      const _exhaustive: never = shape;
      throw new Error(`Unhandled shape: ${_exhaustive}`);
  }
}

9. Zod Validation

import { z } from 'zod';

// Define schema
const UserSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  age: z.number().int().min(0).max(150),
  role: z.enum(['admin', 'user', 'moderator']),
});

// Infer TypeScript type จาก Zod schema
type User = z.infer<typeof UserSchema>;
// { name: string; email: string; age: number; role: "admin" | "user" | "moderator" }

// Validate at runtime
const result = UserSchema.safeParse(unknownData);
if (result.success) {
  const user: User = result.data; // Type-safe ✓
} else {
  console.error(result.error.issues);
}

10. Utility Types Mastery

interface User {
  id: number;
  name: string;
  email: string;
  password: string;
  createdAt: Date;
}

// Partial — ทุก Field เป็น Optional
type UpdateUser = Partial<User>;

// Pick — เลือกเฉพาะ Field ที่ต้องการ
type UserProfile = Pick<User, 'id' | 'name' | 'email'>;

// Omit — ตัด Field ที่ไม่ต้องการ
type PublicUser = Omit<User, 'password'>;

// Required — ทุก Field เป็น Required
type CompleteUser = Required<Partial<User>>;

// Record — สร้าง Object type
type UserRoles = Record<string, 'admin' | 'user'>;

// Readonly — ทุก Field เป็น Readonly
type FrozenUser = Readonly<User>;

// ReturnType — ดึง Return type ของ Function
type Result = ReturnType<typeof fetchUser>;

// Parameters — ดึง Parameter types ของ Function
type Params = Parameters<typeof fetchUser>;

11. Generic Constraints

// BAD — ไม่มี Constraint
function getProperty<T>(obj: T, key: string) {
  return obj[key]; // Error! T ไม่รู้ว่ามี key
}

// GOOD — ใช้ Constraint
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key]; // OK ✓ Type-safe
}

const user = { name: 'John', age: 30 };
getProperty(user, 'name');  // string ✓
getProperty(user, 'foo');   // Error! ✓

12. Module Augmentation

// เพิ่ม Type ให้ Library ที่มีอยู่
declare module 'express' {
  interface Request {
    user?: {
      id: string;
      role: 'admin' | 'user';
    };
  }
}

// ตอนนี้ req.user มี Type แล้ว
app.get('/profile', (req, res) => {
  if (req.user?.role === 'admin') {
    // TypeScript รู้ว่า user มี id และ role
  }
});

13. Declaration Merging

// Interface สามารถ Merge ได้ (Type ไม่ได้)
interface Config {
  database: string;
}

interface Config {
  port: number;
}

// Result: Config = { database: string; port: number }
// ใช้สำหรับ extend Library types

14. Path Aliases

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

// ใช้:
import { Button } from '@components/Button';  // แทน '../../../components/Button'
import { formatDate } from '@utils/date';      // สะอาดกว่ามาก

15. Barrel Exports

// src/components/index.ts (Barrel file)
export { Button } from './Button';
export { Input } from './Input';
export { Modal } from './Modal';
export { Card } from './Card';

// ใช้:
import { Button, Input, Modal, Card } from '@components';
// แทนที่จะ import ทีละไฟล์

// ข้อควรระวัง: Barrel exports อาจทำให้ Bundle size ใหญ่ขึ้น
// ถ้าใช้ Tree-shaking ไม่ได้ → import ตรงจากไฟล์แทน

16. Error Handling Patterns

// Pattern 1: Result Type (ไม่ใช้ throw)
type Result<T, E = Error> =
  | { ok: true; value: T }
  | { ok: false; error: E };

async function fetchUser(id: string): Promise<Result<User>> {
  try {
    const res = await fetch(`/api/users/${id}`);
    if (!res.ok) return { ok: false, error: new Error(`HTTP ${res.status}`) };
    const data = await res.json();
    return { ok: true, value: data };
  } catch (e) {
    return { ok: false, error: e instanceof Error ? e : new Error(String(e)) };
  }
}

// ใช้:
const result = await fetchUser('123');
if (result.ok) {
  console.log(result.value.name); // Type-safe ✓
} else {
  console.error(result.error.message);
}

17. Testing Types

// ใช้ Type assertions ทดสอบว่า Type ถูกต้อง
type Assert<T, Expected> = T extends Expected ? true : false;
type AssertEqual<T, Expected> = [T] extends [Expected]
  ? [Expected] extends [T] ? true : false
  : false;

// ทดสอบ:
type Test1 = AssertEqual<Pick<User, 'name'>, { name: string }>; // true ✓
type Test2 = AssertEqual<ReturnType<typeof add>, number>; // true ✓

18. Performance Tips

// 1. ใช้ interface แทน type สำหรับ Object shapes (Compile เร็วกว่า)
interface User { name: string; } // ดีกว่า type User = { name: string; }

// 2. หลีกเลี่ยง Deep type inference
// BAD: TypeScript ต้อง infer หลายชั้น
const result = arr.map(x => transform(x)).filter(x => validate(x)).reduce(...);
// GOOD: ระบุ Type ช่วย
const result: FinalType = arr.map(...).filter(...).reduce(...);

// 3. ใช้ Project References สำหรับ Monorepo
// tsconfig.json
{
  "references": [
    { "path": "./packages/core" },
    { "path": "./packages/ui" }
  ]
}

19. Migration Strategies (JS → TS)

  1. เริ่ม allowJs: true: ให้ JS และ TS อยู่ด้วยกันได้
  2. เปลี่ยนทีละไฟล์: .js → .ts เริ่มจากไฟล์เล็กๆ
  3. ใช้ @ts-check: เพิ่ม // @ts-check ที่บรรทัดแรกของ JS file เพื่อให้ TypeScript ตรวจสอบ
  4. เปิด strict ทีละตัว: เริ่มจาก noImplicitAny → strictNullChecks → เปิดทั้งหมด
  5. อย่า migrate ทุกไฟล์พร้อมกัน: ทำ Sprint ละ 5-10 ไฟล์

20. TypeScript 5+ Features

// TypeScript 5.0: Decorators (Stage 3)
function log(target: any, context: ClassMethodDecoratorContext) {
  return function (...args: any[]) {
    console.log(`Calling ${String(context.name)}`);
    return target.apply(this, args);
  };
}

class UserService {
  @log
  getUser(id: string) { /* ... */ }
}

// TypeScript 5.2: using declarations (Explicit Resource Management)
async function processFile() {
  using file = await openFile('data.txt');
  // file จะถูก dispose อัตโนมัติเมื่อออกจาก scope
  // ไม่ต้อง try-finally-close
}

// TypeScript 5.5: Inferred type predicates
function isString(x: unknown) {
  return typeof x === 'string';
  // TypeScript 5.5 infer ว่า return type คือ "x is string" อัตโนมัติ
}

สรุป

TypeScript ในปี 2026 มีความสามารถมากกว่าแค่ "JavaScript + Types" มาก เทคนิคทั้ง 20 ข้อนี้จะช่วยให้ Code ของคุณ Type-safe มากขึ้น ตรวจจับ Bug ได้เร็วขึ้น และ Maintain ง่ายขึ้นในระยะยาว เริ่มจาก Strict mode แล้วค่อยๆ เพิ่มเทคนิคอื่นทีละตัว คุณจะรู้สึกว่า TypeScript เป็นเพื่อนที่ช่วยเขียน Code ไม่ใช่อุปสรรค


Back to Blog | iCafe Forex | SiamLanCard | Siam2R