TypeScript không chỉ giúp bạn “bắt lỗi chính tả trong code”, mà còn là vũ khí giúp dự án của bạn sống sót trong production — nhất là khi bạn làm việc nhóm hoặc maintain code lâu dài.

Nếu bạn đã từng vật lộn với any, unknown, prop type lộn xộn trong React, hay request-response model khó kiểm soát trong Node.js, bài viết này sẽ giúp bạn bước sang “level tiếp theo” trong việc làm chủ TypeScript thực tế.

1. Luôn bắt đầu với strict mode

Trong file tsconfig.json, bật ngay:

{
  "compilerOptions": {
    "strict": true
  }
}

Điều này kích hoạt một loạt rule: noImplicitAny, strictNullChecks, strictBindCallApply…

Lợi ích: Ngăn hàng trăm bug runtime ngay từ giai đoạn compile.

Trong dự án React hoặc Node.js lớn, strict mode là cách duy nhất để đảm bảo type nhất quán giữa các module.

2. Dùng React.FC đúng cách (và cẩn thận)

Nhiều dev React dùng React.FC để định nghĩa component:

const Button: React.FC<{ label: string }> = ({ label }) => {
  return <button>{label}</button>;
};

Nhưng React.FC có vài hạn chế:

  • Tự động thêm children, đôi khi gây lỗi khó hiểu.

  • Khó kiểm soát generic props.

 Cách tốt hơn:

type ButtonProps = {
  label: string;
  onClick?: () => void;
};

const Button = ({ label, onClick }: ButtonProps) => {
  return <button onClick={onClick}>{label}</button>;
};
Tip: Chỉ dùng React.FC khi bạn thực sự cần children mặc định hoặc generic props.
Còn không, hãy khai báo thủ công như trên — dễ đọc và an toàn hơn.

3. Dùng useState với type an toàn

Trong React, useState đôi khi gây lỗi khi bạn không chỉ định type rõ ràng.

Sai lầm phổ biến:

const [user, setUser] = useState({});

TS hiểu user là {} (object rỗng) — bạn sẽ mất hết gợi ý type!

Cách đúng:

type User = { name: string; age: number };
const [user, setUser] = useState<User | null>(null);

Hoặc nếu state có thể thay đổi type:

const [value, setValue] = useState<string | number>('');
Mẹo: Luôn định nghĩa type rõ ràng cho state hoặc tạo interface riêng, đặc biệt với form hoặc API response.

4. Dùng as const trong React để cố định literal

Khi định nghĩa các constant cho UI hoặc logic, thêm as const để tránh lỗi sai chính tả:

const STATUS = {
  PENDING: 'pending',
  SUCCESS: 'success',
  ERROR: 'error',
} as const;

type Status = typeof STATUS[keyof typeof STATUS];

const Badge = ({ status }: { status: Status }) => {
  return <div>{status}</div>;
};
Lợi ích: Khi bạn dùng 'pending' | 'success' | 'error', autocomplete sẽ hỗ trợ, và TS báo lỗi nếu sai chính tả.

5. Dùng Pick, Omit, Partial trong React props

Trong dự án React, component thường chia sẻ cùng một type User, Post, Product,…

Thay vì tạo type mới, hãy tái sử dụng.

type User = { id: number; name: string; email: string; avatar: string };

type UserCardProps = Pick<User, 'name' | 'avatar'>;

Hoặc khi chỉ muốn cho phép cập nhật một phần user:

type UpdateUserPayload = Partial<Omit<User, 'id'>>;
Các utility type giúp code props rõ ràng hơn, tránh duplication, và đồng bộ với model backend.

6. Dùng interface + extends để mô hình hóa component phức tạp

Khi bạn có nhiều biến thể component, interface là lựa chọn linh hoạt hơn type.

 

interface BaseButton {
  label: string;
}

interface IconButton extends BaseButton {
  icon: string;
}

interface LinkButton extends BaseButton {
  href: string;
}

Bạn có thể dùng union để kết hợp:

type ButtonProps = IconButton | LinkButton;

TypeScript sẽ tự động hỗ trợ type narrowing khi bạn check if ('icon' in props).

7. Trong Node.js — tạo type chung cho API request/response

Một trong những lợi ích lớn của TypeScript ở backend là tái sử dụng type giữa client và server.

Ví dụ:

// shared/types.ts
export interface UserResponse {
  id: number;
  name: string;
  email: string;
}

Dùng ở backend (Express):

app.get('/users/:id', (req, res) => {
  const user: UserResponse = { id: 1, name: 'An', email: '[email protected]' };
  res.json(user);
});
Kết quả: Không còn sai lệch giữa backend – frontend, hạn chế bug khi đổi field.

8. Dùng zod hoặc io-ts để kết hợp runtime validation + TypeScript type

TypeScript chỉ kiểm tra lúc compile, không tồn tại ở runtime.

Vì vậy khi nhận dữ liệu từ API hoặc body request, bạn phải validate thủ công.

 Dùng zod:

import { z } from 'zod';

const UserSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
});

type User = z.infer<typeof UserSchema>;

// Validate runtime
const user = UserSchema.parse(dataFromApi);

zod giúp bạn:

• Validate thật (runtime)

• Sinh type tự động (compile)

• Dùng chung giữa React & Node.js.

9. Dùng never và exhaustive check trong Node.js logic

Khi viết logic có nhiều trường hợp, never giúp bạn đảm bảo không bỏ sót.

type PaymentMethod = 'cash' | 'credit' | 'crypto';

function handlePayment(method: PaymentMethod) {
  switch (method) {
    case 'cash':
      return 'Thanh toán tiền mặt';
    case 'credit':
      return 'Thanh toán qua thẻ';
    case 'crypto':
      return 'Thanh toán bằng crypto';
    default:
      const _exhaustive: never = method;
      throw new Error(`Unknown method: ${_exhaustive}`);
  }
}
Nếu sau này thêm paypal mà quên xử lý, TypeScript sẽ cảnh báo ngay - tránh bug logic cực kỳ hiệu quả.

10. Tận dụng satisfies để đảm bảo cấu trúc mà không mất literal

Từ TypeScript 4.9 trở đi, bạn có thể dùng satisfies:

const ROUTES = {
  HOME: '/',
  LOGIN: '/login',
  PROFILE: '/profile',
} satisfies Record<string, string>;

type RouteKey = keyof typeof ROUTES; // 'HOME' | 'LOGIN' | 'PROFILE'

as thì “ép kiểu”, còn satisfies là “kiểm tra kiểu” — nếu bạn gõ sai, TypeScript báo lỗi nhưng vẫn giữ literal type.

Hữu ích cực kỳ trong config, routes, API mapping hoặc env setup.

Tổng kết

TypeScript trong thực tế không chỉ để “đẹp code” — mà để giảm rủi ro khi dự án phức tạp dần lên.

Càng làm nhiều React component, càng viết nhiều API endpoint, bạn càng thấy TypeScript giúp mình:

Tình huống

Mẹo TypeScript phù hợp

State phức tạp

useState<Type>

Nhiều component chia sẻ props

Pick / Omit / Partial

API client-server

Shared type + zod validation

Config & constant

as const + satisfies

Kiểm tra logic toàn diện

never + exhaustive check