Tiếng Việt

Khai phá sức mạnh của const assertions trong TypeScript để suy luận kiểu bất biến, nâng cao tính an toàn và khả năng dự đoán của mã nguồn. Tìm hiểu cách sử dụng chúng hiệu quả qua các ví dụ thực tế.

TypeScript Const Assertions: Suy luận kiểu bất biến cho mã nguồn mạnh mẽ

TypeScript, một tập hợp con mở rộng của JavaScript, mang đến kiểu tĩnh cho thế giới phát triển web năng động. Một trong những tính năng mạnh mẽ của nó là suy luận kiểu (type inference), nơi trình biên dịch tự động suy ra kiểu của một biến. Const assertions, được giới thiệu trong TypeScript 3.4, đưa việc suy luận kiểu lên một tầm cao mới, cho phép bạn thực thi tính bất biến và tạo ra mã nguồn mạnh mẽ và dễ dự đoán hơn.

Const Assertions là gì?

Const assertions là một cách để thông báo cho trình biên dịch TypeScript rằng bạn muốn một giá trị phải là bất biến. Chúng được áp dụng bằng cách sử dụng cú pháp as const sau một giá trị hoặc biểu thức nguyên bản (literal). Điều này hướng dẫn trình biên dịch suy luận ra kiểu hẹp nhất có thể (kiểu nguyên bản) cho biểu thức và đánh dấu tất cả các thuộc tính là readonly.

Về bản chất, const assertions cung cấp một mức độ an toàn kiểu mạnh hơn so với việc chỉ khai báo một biến bằng const. Trong khi const ngăn chặn việc gán lại giá trị cho chính biến đó, nó không ngăn chặn việc sửa đổi đối tượng hoặc mảng mà biến đó tham chiếu đến. Const assertions cũng ngăn chặn việc sửa đổi các thuộc tính của đối tượng.

Lợi ích của việc sử dụng Const Assertions

Các ví dụ thực tế

Ví dụ 1: Sử dụng cơ bản với một giá trị nguyên bản (Literal)

Nếu không có const assertion, TypeScript sẽ suy luận kiểu của messagestring:


const message = "Hello, World!"; // Type: string

Với const assertion, TypeScript sẽ suy luận kiểu là chuỗi nguyên bản "Hello, World!":


const message = "Hello, World!" as const; // Type: "Hello, World!"

Điều này cho phép bạn sử dụng kiểu chuỗi nguyên bản trong các định nghĩa và so sánh kiểu chính xác hơn.

Ví dụ 2: Sử dụng Const Assertions với mảng

Hãy xem xét một mảng các màu sắc:


const colors = ["red", "green", "blue"]; // Type: string[]

Mặc dù mảng được khai báo với const, bạn vẫn có thể sửa đổi các phần tử của nó:


colors[0] = "purple"; // No error
console.log(colors); // Output: ["purple", "green", "blue"]

Bằng cách thêm một const assertion, TypeScript sẽ suy luận mảng là một tuple gồm các chuỗi chỉ đọc (readonly):


const colors = ["red", "green", "blue"] as const; // Type: readonly ["red", "green", "blue"]

Bây giờ, việc cố gắng sửa đổi mảng sẽ dẫn đến lỗi TypeScript:


// colors[0] = "purple"; // Error: Index signature in type 'readonly ["red", "green", "blue"]' only permits reading.

Điều này đảm bảo rằng mảng colors vẫn bất biến.

Ví dụ 3: Sử dụng Const Assertions với đối tượng

Tương tự như mảng, đối tượng cũng có thể được làm cho bất biến bằng const assertions:


const person = {
  name: "Alice",
  age: 30,
}; // Type: { name: string; age: number; }

Ngay cả với const, bạn vẫn có thể sửa đổi các thuộc tính của đối tượng person:


person.age = 31; // No error
console.log(person); // Output: { name: "Alice", age: 31 }

Thêm một const assertion sẽ làm cho các thuộc tính của đối tượng trở thành readonly:


const person = {
  name: "Alice",
  age: 30,
} as const; // Type: { readonly name: "Alice"; readonly age: 30; }

Bây giờ, việc cố gắng sửa đổi đối tượng sẽ dẫn đến lỗi TypeScript:


// person.age = 31; // Error: Cannot assign to 'age' because it is a read-only property.

Ví dụ 4: Sử dụng Const Assertions với đối tượng và mảng lồng nhau

Const assertions có thể được áp dụng cho các đối tượng và mảng lồng nhau để tạo ra các cấu trúc dữ liệu bất biến sâu. Hãy xem xét ví dụ sau:


const config = {
  apiUrl: "https://api.example.com",
  endpoints: {
    users: "/users",
    products: "/products",
  },
  supportedLanguages: ["en", "fr", "de"],
} as const;

// Type:
// {
//   readonly apiUrl: "https://api.example.com";
//   readonly endpoints: {
//     readonly users: "/users";
//     readonly products: "/products";
//   };
//   readonly supportedLanguages: readonly ["en", "fr", "de"];
// }

Trong ví dụ này, đối tượng config, đối tượng endpoints lồng bên trong, và mảng supportedLanguages đều được đánh dấu là readonly. Điều này đảm bảo rằng không có phần nào của cấu hình có thể bị sửa đổi vô tình trong quá trình chạy.

Ví dụ 5: Const Assertions với kiểu trả về của hàm

Bạn có thể sử dụng const assertions để đảm bảo rằng một hàm trả về một giá trị bất biến. Điều này đặc biệt hữu ích khi tạo các hàm tiện ích không nên sửa đổi đầu vào hoặc tạo ra đầu ra có thể thay đổi.


function createImmutableArray(items: T[]): readonly T[] {
  return [...items] as const;
}

const numbers = [1, 2, 3];
const immutableNumbers = createImmutableArray(numbers);

// Type of immutableNumbers: readonly [1, 2, 3]

// immutableNumbers[0] = 4; // Error: Index signature in type 'readonly [1, 2, 3]' only permits reading.

Các trường hợp sử dụng và kịch bản

Quản lý cấu hình

Const assertions là lý tưởng để quản lý cấu hình ứng dụng. Bằng cách khai báo các đối tượng cấu hình của bạn với as const, bạn có thể đảm bảo rằng cấu hình vẫn nhất quán trong suốt vòng đời của ứng dụng. Điều này ngăn chặn các sửa đổi vô tình có thể dẫn đến hành vi không mong muốn.


const appConfig = {
  appName: "My Application",
  version: "1.0.0",
  apiEndpoint: "https://api.example.com",
} as const;

Định nghĩa hằng số

Const assertions cũng hữu ích để định nghĩa các hằng số với các kiểu nguyên bản cụ thể. Điều này có thể cải thiện độ an toàn của kiểu và sự rõ ràng của mã nguồn.


const HTTP_STATUS_OK = 200 as const; // Type: 200
const HTTP_STATUS_NOT_FOUND = 404 as const; // Type: 404

Làm việc với Redux hoặc các thư viện quản lý trạng thái khác

Trong các thư viện quản lý trạng thái như Redux, tính bất biến là một nguyên tắc cốt lõi. Const assertions có thể giúp thực thi tính bất biến trong các reducer và action creator của bạn, ngăn chặn các đột biến trạng thái vô tình.


// Example Redux reducer

interface State {
  readonly count: number;
}

const initialState: State = { count: 0 } as const;

function reducer(state: State = initialState, action: { type: string }): State {
  switch (action.type) {
    default:
      return state;
  }
}

Quốc tế hóa (i18n)

Khi làm việc với quốc tế hóa, bạn thường có một tập hợp các ngôn ngữ được hỗ trợ và các mã địa phương tương ứng. Const assertions có thể đảm bảo rằng tập hợp này vẫn bất biến, ngăn chặn các việc thêm hoặc sửa đổi vô tình có thể làm hỏng việc triển khai i18n của bạn. Ví dụ, hãy tưởng tượng việc hỗ trợ tiếng Anh (en), tiếng Pháp (fr), tiếng Đức (de), tiếng Tây Ban Nha (es) và tiếng Nhật (ja):


const supportedLanguages = ["en", "fr", "de", "es", "ja"] as const;

type SupportedLanguage = typeof supportedLanguages[number]; // Type: "en" | "fr" | "de" | "es" | "ja"

function greet(language: SupportedLanguage) {
  switch (language) {
    case "en":
      return "Hello!";
    case "fr":
      return "Bonjour!";
    case "de":
      return "Guten Tag!";
    case "es":
      return "¡Hola!";
    case "ja":
      return "こんにちは!";
    default:
      return "Lời chào không có sẵn cho ngôn ngữ này.";
  }
}

Hạn chế và những điều cần cân nhắc

Các phương pháp thay thế cho Const Assertions

Mặc dù const assertions là một công cụ mạnh mẽ để thực thi tính bất biến, có những cách tiếp cận khác bạn có thể xem xét:

Các phương pháp hay nhất

Kết luận

TypeScript const assertions là một công cụ có giá trị để thực thi tính bất biến và cải thiện an toàn kiểu trong mã nguồn của bạn. Bằng cách sử dụng as const, bạn có thể hướng dẫn trình biên dịch suy luận ra kiểu hẹp nhất có thể cho một giá trị và đánh dấu tất cả các thuộc tính là readonly. Điều này có thể giúp ngăn chặn các sửa đổi vô tình, cải thiện khả năng dự đoán của mã và mở ra khả năng kiểm tra kiểu chính xác hơn. Mặc dù const assertions có một số hạn chế, chúng là một sự bổ sung mạnh mẽ cho ngôn ngữ TypeScript và có thể nâng cao đáng kể sự mạnh mẽ của các ứng dụng của bạn.

Bằng cách kết hợp const assertions một cách chiến lược vào các dự án TypeScript của mình, bạn có thể viết mã nguồn đáng tin cậy, dễ bảo trì và dễ dự đoán hơn. Hãy nắm bắt sức mạnh của suy luận kiểu bất biến và nâng cao các phương pháp phát triển phần mềm của bạn.