Giới thiệu về Utility Types

Utility Types là một tập hợp các kiểu dữ liệu được cung cấp sẵn trong TypeScript, giúp chúng ta thao tác và biến đổi các kiểu một cách nhanh chóng mà không cần viết lại các định nghĩa phức tạp. Khi làm việc với các đối tượng lớn, việc tái sử dụng các kiểu đã định nghĩa sẽ giảm thiểu lỗi và cải thiện khả năng bảo trì mã nguồn.

Partial – biến mọi thuộc tính thành tùy chọn

Utility Partial chuyển tất cả các thuộc tính của một kiểu thành optional. Đây là công cụ hữu ích khi xây dựng các hàm cập nhật dữ liệu, nơi chỉ một số trường cần được truyền vào.

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

type UserUpdate = Partial<User>;

function updateUser(id: number, data: UserUpdate) {
    // Giả sử có một API gọi PATCH
    console.log('Updating user', id, data);
}

updateUser(1, { name: 'Nguyen Van A' }); // Chỉ truyền một trường

Pick – chọn lọc một số thuộc tính

Utility Pick cho phép bạn tạo một kiểu mới chỉ chứa một tập hợp các thuộc tính được chỉ định từ kiểu gốc. Thường dùng khi bạn muốn trả về một phần dữ liệu của một đối tượng.

type UserSummary = Pick<User, 'id' | 'name'>;

const summary: UserSummary = {
    id: 2,
    name: 'Le Thi B'
};

Omit – loại bỏ một số thuộc tính

Utility Omit là ngược lại của Pick, nó tạo một kiểu mới bằng cách loại bỏ các thuộc tính không mong muốn.

type UserWithoutEmail = Omit<User, 'email'>;

const user: UserWithoutEmail = {
    id: 3,
    name: 'Tran C'
};

Required – biến mọi thuộc tính thành bắt buộc

Trong một số trường hợp, bạn có thể có một kiểu với các thuộc tính tùy chọn, nhưng muốn chắc chắn rằng ở một bước nào đó tất cả đều phải có giá trị. Utility Required giúp đạt được điều này.

type PartialUser = {
    id?: number;
    name?: string;
    email?: string;
};

type CompleteUser = Required<PartialUser>;

// Lỗi nếu thiếu thuộc tính
// const u: CompleteUser = { id: 1, name: 'A' }; // TypeScript sẽ báo lỗi

Readonly – làm cho thuộc tính không thể thay đổi

Utility Readonly biến các thuộc tính của một kiểu thành không thể gán lại sau khi khởi tạo, giúp bảo vệ dữ liệu khỏi việc thay đổi ngoài ý muốn.

type ImmutableUser = Readonly<User>;

const immutable: ImmutableUser = {
    id: 4,
    name: 'Pham D',
    email: '[email protected]'
};

// immutable.name = 'New Name'; // Lỗi biên dịch

Record – tạo một kiểu map với key và value cố định

Utility Record rất hữu ích khi bạn cần một đối tượng ánh xạ (map) với các key xác định và cùng một kiểu giá trị.

type Role = 'admin' | 'editor' | 'viewer';

type Permissions = Record<Role, string[]>;

const perms: Permissions = {
    admin: ['create', 'read', 'update', 'delete'],
    editor: ['read', 'update'],
    viewer: ['read']
};

Kết hợp các Utility Types

Bạn có thể kết hợp nhiều Utility Types để tạo ra các kiểu phức tạp hơn. Ví dụ, một kiểu Partial nhưng vẫn giữ một số thuộc tính bắt buộc:

type UserWithRequiredId = Partial<User> & { id: number };

const userPartial: UserWithRequiredId = {
    id: 5,
    name: 'Hoang E' // email có thể bỏ qua
};

Thực hành nhanh với TypeScript Compiler

Để kiểm tra các Utility Types, bạn có thể chạy một dự án TypeScript nhanh bằng lệnh sau:

npx tsc --init
npm install typescript --save-dev
npx tsc src/index.ts --watch

Việc quan sát lỗi biên dịch khi thay đổi các kiểu sẽ giúp bạn hiểu sâu hơn về cách TypeScript bảo vệ mã.

Kết luận

Utility Types là công cụ mạnh mẽ giúp giảm thiểu việc viết lại kiểu, tăng tính tái sử dụng và giảm lỗi runtime. Khi áp dụng chúng một cách hợp lý, dự án của bạn sẽ trở nên sạch sẽ và dễ bảo trì hơn.

Để nâng cao hơn nữa kỹ năng TypeScript, Tham khảo khóa học "Lập trình TypeScript từ cơ bản đến nâng cao" tại đây.