TypeScript Best Practices 2026 เขียน TypeScript ให้ดีขึ้นด้วย 20 เทคนิคจากมืออาชีพ
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
}
}
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)
- เริ่ม allowJs: true: ให้ JS และ TS อยู่ด้วยกันได้
- เปลี่ยนทีละไฟล์: .js → .ts เริ่มจากไฟล์เล็กๆ
- ใช้ @ts-check: เพิ่ม
// @ts-checkที่บรรทัดแรกของ JS file เพื่อให้ TypeScript ตรวจสอบ - เปิด strict ทีละตัว: เริ่มจาก noImplicitAny → strictNullChecks → เปิดทั้งหมด
- อย่า 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 ไม่ใช่อุปสรรค
