Những thủ thuật TypeScript hữu ích tôi sử dụng hàng ngày

07 tháng 4, 2026·4 phút đọc

Tác giả chia sẻ những mẫu mã và kỹ thuật TypeScript thực tế được sử dụng thường xuyên trong các dự án React Native và Node.js, giúp thu hẹp khoảng cách giữa tài liệu lý thuyết và ứng dụng thực tế. Bài viết tập trung vào các giải pháp xử lý trạng thái, kiểu dữ liệu và tối ưu hóa mã nguồn hiệu quả.

Những thủ thuật TypeScript hữu ích tôi sử dụng hàng ngày

Tôi đã viết TypeScript được vài năm nay, làm việc với React Native, Node.js và nhiều loại sản phẩm khác nhau. Có một khoảng cách khá lớn giữa những gì tài liệu hướng dẫn và những gì bạn thực sự cần dùng hàng ngày.

Dưới đây là những mẫu mã (patterns) mà tôi thường xuyên quay lại sử dụng.

Liên hợp phân biệt (Discriminated Unions) để quản lý trạng thái

Mẫu này đã thay đổi cách tôi mô hình hóa dữ liệu. Thay vì sử dụng một loạt các trường tùy chọn (optional fields) có thể tồn tại hoặc không, bạn định nghĩa từng trạng thái một cách rõ ràng.

type RequestState =
  | { status: "idle" }
  | { status: "loading" }
  | { status: "success"; data: User }
  | { status: "error"; message: string };

Giờ đây TypeScript biết chính xác những gì có sẵn trong từng trường hợp. Bạn sẽ không còn phải rải rác các kiểm tra data có thể là undefined khắp nơi nữa.

Sử dụng satisfies thay vì chú thích kiểu trực tiếp

Đây là tính năng khá mới nhưng tôi sử dụng nó rất nhiều hiện nay. Sự khác biệt có vẻ tinh tế nhưng lại rất hữu ích.

const config = {
  theme: "dark",
  language: "en",
} satisfies Record<string, string>;

Với satisfies, bạn vẫn có được kiểm tra kiểu mà không làm mất đi các kiểu literal (kiểu chữ). Nếu bạn chú thích trực tiếp bằng Record, bạn sẽ mất đi các giá trị cụ thể. Điều nhỏ nhặt này tạo ra sự khác biệt lớn khi bạn đang xâu chuỗi hoặc suy luận kiểu từ nó.

Các kiểu tiện ích (Utility Types) bạn thực sự cần

Ai cũng biết đến PartialRequired. Nhưng đây là những kiểu mà tôi thường xuyên sử dụng hơn:

// Chỉ chọn những gì bạn cần
type Preview = Pick<Article, "title" | "slug" | "createdAt">;

// Loại bỏ những gì bạn không cần
type PublicUser = Omit<User, "password" | "token">;

// Làm cho các trường cụ thể trở nên tùy chọn
type UpdateInput = Partial<Pick<User, "name" | "bio">> & Pick<User, "id">;

Cái cuối cùng là thứ tôi sử dụng liên tục cho các endpoint cập nhật (update) hoặc vá lỗi (patch).

Kiểu chuỗi mẫu (Template Literal Types) cho các hợp đồng chuỗi

Rất hữu ích khi bạn xử lý tên sự kiện, định tuyến (routes) hoặc bất kỳ thứ gì dựa trên chuỗi nhưng cần có cấu trúc.

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Endpoint = `/${string}`;
type Route = `${HttpMethod} ${Endpoint}`;

const route: Route = "GET /users"; // hợp lệ
const bad: Route = "FETCH /users"; // lỗi

Nó giúp bắt một lớp lỗi ngay tại thời điểm biên dịch thay vì khi chạy chương trình.

Sử dụng infer để trích xuất kiểu từ Generics

Có vẻ đáng sợ lúc đầu, nhưng một khi bạn hiểu ra, bạn sẽ dùng nó ở khắp nơi.

type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

type UnpackPromise<T> = T extends Promise<infer U> ? U : T;

Tôi sử dụng UnpackPromise liên tục khi làm việc với các hàm bất đồng bộ (async) và tôi cần kiểu dữ liệu đã giải quyết (resolved type) mà không cần phải await.

Khẳng định hằng số (Const Assertions) cho đối tượng cấu hình

Hãy ngừng mở rộng (widening) kiểu dữ liệu của bạn khi không cần thiết.

const ROLES = ["admin", "user", "guest"] as const;
type Role = typeof ROLES[number]; // "admin" | "user" | "guest"

Gọn gàng, không có sự trùng lặp thủ công, và kiểu dữ liệu tự động đồng bộ hóa.

Không có cái nào trong số này là quá cao siêu. Chúng chỉ là những thứ xuất hiện lặp đi lặp lại khi bạn xây dựng các sản phẩm thực tế. Càng sử dụng chúng nhiều, bạn càng cảm thấy trình biên dịch TypeScript giống như một đồng đội thay vì một thứ gì đó bạn phải chống lại.

Đó là sự thay đổi đáng để theo đuổi.

Bài viết được tổng hợp và biên soạn bằng AI từ các nguồn tin tức công nghệ. Nội dung mang tính tham khảo. Xem bài gốc ↗