Tiếng Việt

Nắm vững kiểm tra thuộc tính dư thừa của TypeScript để ngăn lỗi runtime và tăng cường an toàn kiểu đối tượng cho các ứng dụng JavaScript mạnh mẽ, dễ dự đoán.

Kiểm tra thuộc tính dư thừa trong TypeScript: Củng cố an toàn kiểu đối tượng của bạn

Trong lĩnh vực phát triển phần mềm hiện đại, đặc biệt là với JavaScript, việc đảm bảo tính toàn vẹn và khả năng dự đoán của mã nguồn là tối quan trọng. Mặc dù JavaScript mang lại sự linh hoạt to lớn, đôi khi nó có thể dẫn đến lỗi lúc chạy (runtime) do các cấu trúc dữ liệu không mong muốn hoặc sự không khớp thuộc tính. Đây là lúc TypeScript tỏa sáng, cung cấp các khả năng gõ tĩnh giúp phát hiện nhiều lỗi phổ biến trước khi chúng xuất hiện trong môi trường sản phẩm. Một trong những tính năng mạnh mẽ nhất nhưng đôi khi bị hiểu lầm của TypeScript là kiểm tra thuộc tính dư thừa (excess property check).

Bài viết này sẽ đi sâu vào việc kiểm tra thuộc tính dư thừa của TypeScript, giải thích chúng là gì, tại sao chúng lại quan trọng đối với sự an toàn của kiểu đối tượng, và cách tận dụng chúng một cách hiệu quả để xây dựng các ứng dụng mạnh mẽ và dễ dự đoán hơn. Chúng ta sẽ khám phá các kịch bản khác nhau, những cạm bẫy phổ biến và các phương pháp thực hành tốt nhất để giúp các nhà phát triển trên toàn thế giới, bất kể nền tảng của họ, khai thác cơ chế quan trọng này của TypeScript.

Hiểu khái niệm cốt lõi: Kiểm tra thuộc tính dư thừa là gì?

Về cơ bản, kiểm tra thuộc tính dư thừa của TypeScript là một cơ chế của trình biên dịch ngăn bạn gán một đối tượng ký tự (object literal) cho một biến mà kiểu của nó không cho phép một cách tường minh các thuộc tính thừa đó. Nói một cách đơn giản hơn, nếu bạn định nghĩa một object literal và cố gắng gán nó cho một biến có định nghĩa kiểu cụ thể (như interface hoặc type alias), và literal đó chứa các thuộc tính không được khai báo trong kiểu đã định nghĩa, TypeScript sẽ báo lỗi trong quá trình biên dịch.

Hãy minh họa bằng một ví dụ cơ bản:


interface User {
  name: string;
  age: number;
}

const newUser: User = {
  name: 'Alice',
  age: 30,
  email: 'alice@example.com' // Lỗi: Object literal chỉ có thể chỉ định các thuộc tính đã biết, và 'email' không tồn tại trong kiểu 'User'.
};

Trong đoạn mã này, chúng ta định nghĩa một `interface` tên là `User` với hai thuộc tính: `name` và `age`. Khi chúng ta cố gắng tạo một object literal với một thuộc tính bổ sung, `email`, và gán nó cho một biến được định kiểu là `User`, TypeScript ngay lập tức phát hiện ra sự không khớp. Thuộc tính `email` là một thuộc tính 'dư thừa' vì nó không được định nghĩa trong interface `User`. Việc kiểm tra này được thực hiện đặc biệt khi bạn sử dụng một object literal để gán.

Tại sao việc kiểm tra thuộc tính dư thừa lại quan trọng?

Tầm quan trọng của việc kiểm tra thuộc tính dư thừa nằm ở khả năng thực thi một hợp đồng giữa dữ liệu của bạn và cấu trúc dự kiến của nó. Chúng góp phần vào sự an toàn kiểu đối tượng theo nhiều cách quan trọng:

Khi nào việc kiểm tra thuộc tính dư thừa được áp dụng?

Việc hiểu rõ các điều kiện cụ thể mà TypeScript thực hiện các kiểm tra này là rất quan trọng. Chúng chủ yếu được áp dụng cho các đối tượng ký tự (object literal) khi chúng được gán cho một biến hoặc được truyền làm đối số cho một hàm.

Kịch bản 1: Gán Object Literal cho biến

Như đã thấy trong ví dụ `User` ở trên, việc gán trực tiếp một object literal có thuộc tính thừa cho một biến đã được định kiểu sẽ kích hoạt việc kiểm tra.

Kịch bản 2: Truyền Object Literal cho hàm

Khi một hàm mong đợi một đối số có kiểu cụ thể, và bạn truyền một object literal chứa các thuộc tính dư thừa, TypeScript sẽ báo lỗi.


interface Product {
  id: number;
  name: string;
}

function displayProduct(product: Product): void {
  console.log(`Product ID: ${product.id}, Name: ${product.name}`);
}

displayProduct({
  id: 101,
  name: 'Laptop',
  price: 1200 // Lỗi: Đối số kiểu '{ id: number; name: string; price: number; }' không thể gán cho tham số kiểu 'Product'.
             // Object literal chỉ có thể chỉ định các thuộc tính đã biết, và 'price' không tồn tại trong kiểu 'Product'.
});

Ở đây, thuộc tính `price` trong object literal được truyền cho `displayProduct` là một thuộc tính dư thừa, vì interface `Product` không định nghĩa nó.

Khi nào việc kiểm tra thuộc tính dư thừa *Không* được áp dụng?

Hiểu khi nào các kiểm tra này bị bỏ qua cũng quan trọng không kém để tránh nhầm lẫn và biết khi nào bạn có thể cần các chiến lược thay thế.

1. Khi không sử dụng Object Literal để gán

Nếu bạn gán một đối tượng không phải là một object literal (ví dụ: một biến đã chứa một đối tượng), việc kiểm tra thuộc tính dư thừa thường bị bỏ qua.


interface Config {
  timeout: number;
}

function setupConfig(config: Config) {
  console.log(`Timeout set to: ${config.timeout}`);
}

const userProvidedConfig = {
  timeout: 5000,
  retries: 3 // Thuộc tính 'retries' này là thuộc tính dư thừa theo 'Config'
};

setupConfig(userProvidedConfig); // Không có lỗi!

// Mặc dù userProvidedConfig có một thuộc tính thừa, việc kiểm tra bị bỏ qua
// vì nó không phải là một object literal được truyền trực tiếp.
// TypeScript kiểm tra kiểu của chính userProvidedConfig.
// Nếu userProvidedConfig được khai báo với kiểu Config, lỗi sẽ xảy ra sớm hơn.
// Tuy nhiên, nếu được khai báo là 'any' hoặc một kiểu rộng hơn, lỗi sẽ bị hoãn lại.

// Một cách chính xác hơn để hiển thị việc bỏ qua:
let anotherConfig;

if (Math.random() > 0.5) {
  anotherConfig = {
    timeout: 1000,
    host: 'localhost' // Thuộc tính dư thừa
  };
} else {
  anotherConfig = {
    timeout: 2000,
    port: 8080 // Thuộc tính dư thừa
  };
}

setupConfig(anotherConfig as Config); // Không có lỗi do xác nhận kiểu và việc bỏ qua

// Điểm mấu chốt là 'anotherConfig' không phải là một object literal tại thời điểm gán cho setupConfig.
// Nếu chúng ta có một biến trung gian được định kiểu là 'Config', việc gán ban đầu sẽ thất bại.

// Ví dụ về biến trung gian:
let intermediateConfig: Config;

intermediateConfig = {
  timeout: 3000,
  logging: true // Lỗi: Object literal chỉ có thể chỉ định các thuộc tính đã biết, và 'logging' không tồn tại trong kiểu 'Config'.
};

Trong ví dụ `setupConfig(userProvidedConfig)` đầu tiên, `userProvidedConfig` là một biến chứa một đối tượng. TypeScript kiểm tra xem `userProvidedConfig` nói chung có tuân thủ kiểu `Config` hay không. Nó không áp dụng việc kiểm tra object literal nghiêm ngặt cho chính `userProvidedConfig`. Nếu `userProvidedConfig` được khai báo với một kiểu không khớp với `Config`, một lỗi sẽ xảy ra trong quá trình khai báo hoặc gán của nó. Việc bỏ qua xảy ra vì đối tượng đã được tạo và gán cho một biến trước khi được truyền cho hàm.

2. Xác nhận kiểu (Type Assertions)

Bạn có thể bỏ qua việc kiểm tra thuộc tính dư thừa bằng cách sử dụng xác nhận kiểu (type assertions), mặc dù điều này nên được thực hiện một cách thận trọng vì nó ghi đè lên các đảm bảo an toàn của TypeScript.


interface Settings {
  theme: 'dark' | 'light';
}

const mySettings = {
  theme: 'dark',
  fontSize: 14 // Thuộc tính dư thừa
} as Settings;

// Không có lỗi ở đây vì xác nhận kiểu.
// Chúng ta đang nói với TypeScript: "Tin tôi đi, đối tượng này tuân thủ Settings."
console.log(mySettings.theme);
// console.log(mySettings.fontSize); // Điều này sẽ gây ra lỗi runtime nếu fontSize không thực sự tồn tại.

3. Sử dụng Chữ ký chỉ mục (Index Signatures) hoặc Cú pháp Spread trong định nghĩa kiểu

Nếu interface hoặc type alias của bạn cho phép các thuộc tính tùy ý một cách tường minh, việc kiểm tra thuộc tính dư thừa sẽ không được áp dụng.

Sử dụng Chữ ký chỉ mục (Index Signatures):


interface FlexibleObject {
  id: number;
  [key: string]: any; // Cho phép bất kỳ khóa chuỗi nào với bất kỳ giá trị nào
}

const flexibleItem: FlexibleObject = {
  id: 1,
  name: 'Widget',
  version: '1.0.0'
};

// Không có lỗi vì 'name' và 'version' được cho phép bởi chữ ký chỉ mục.
console.log(flexibleItem.name);

Sử dụng Cú pháp Spread trong định nghĩa kiểu (ít phổ biến hơn để bỏ qua kiểm tra trực tiếp, mà chủ yếu để định nghĩa các kiểu tương thích):

Mặc dù không phải là một cách bỏ qua trực tiếp, toán tử spread cho phép tạo các đối tượng mới kết hợp các thuộc tính hiện có, và việc kiểm tra sẽ áp dụng cho object literal mới được hình thành.

4. Sử dụng `Object.assign()` hoặc Cú pháp Spread để hợp nhất

Khi bạn sử dụng `Object.assign()` hoặc cú pháp spread (`...`) để hợp nhất các đối tượng, việc kiểm tra thuộc tính dư thừa hoạt động khác đi. Nó áp dụng cho object literal kết quả được hình thành.


interface BaseConfig {
  host: string;
}

interface ExtendedConfig extends BaseConfig {
  port: number;
}

const defaultConfig: BaseConfig = {
  host: 'localhost'
};

const userConfig = {
  port: 8080,
  timeout: 5000 // Thuộc tính dư thừa so với BaseConfig, nhưng được mong đợi bởi kiểu đã hợp nhất
};

// Spread vào một object literal mới tuân thủ ExtendedConfig
const finalConfig: ExtendedConfig = {
  ...defaultConfig,
  ...userConfig
};

// Điều này thường là ổn vì 'finalConfig' được khai báo là 'ExtendedConfig'
// và các thuộc tính khớp. Việc kiểm tra là trên kiểu của 'finalConfig'.

// Hãy xem xét một kịch bản mà nó *sẽ* thất bại:

interface SmallConfig {
  key: string;
}

const data1 = { key: 'abc', value: 123 }; // 'value' là thừa ở đây
const data2 = { key: 'xyz', status: 'active' }; // 'status' là thừa ở đây

// Cố gắng gán cho một kiểu không chấp nhận các thuộc tính thừa

// const combined: SmallConfig = {
//   ...data1, // Lỗi: Object literal chỉ có thể chỉ định các thuộc tính đã biết, và 'value' không tồn tại trong kiểu 'SmallConfig'.
//   ...data2  // Lỗi: Object literal chỉ có thể chỉ định các thuộc tính đã biết, và 'status' không tồn tại trong kiểu 'SmallConfig'.
// };

// Lỗi xảy ra vì object literal được hình thành bởi cú pháp spread
// chứa các thuộc tính ('value', 'status') không có trong 'SmallConfig'.

// Nếu chúng ta tạo một biến trung gian với kiểu rộng hơn:

const temp: any = {
  ...data1,
  ...data2
};

// Sau đó gán cho SmallConfig, việc kiểm tra thuộc tính dư thừa bị bỏ qua khi tạo literal ban đầu,
// nhưng việc kiểm tra kiểu khi gán vẫn có thể xảy ra nếu kiểu của temp được suy luận chặt chẽ hơn.
// Tuy nhiên, nếu temp là 'any', không có kiểm tra nào xảy ra cho đến khi gán cho 'combined'.

// Hãy làm rõ sự hiểu biết về spread với việc kiểm tra thuộc tính dư thừa:
// Việc kiểm tra xảy ra khi object literal được tạo bởi cú pháp spread được gán
// cho một biến hoặc được truyền cho một hàm mong đợi một kiểu cụ thể hơn.

interface SpecificShape { 
  id: number;
}

const objA = { id: 1, extra1: 'hello' };
const objB = { id: 2, extra2: 'world' };

// Điều này sẽ thất bại nếu SpecificShape không cho phép 'extra1' hoặc 'extra2':
// const merged: SpecificShape = {
//   ...objA,
//   ...objB
// };

// Lý do nó thất bại là vì cú pháp spread thực chất tạo ra một object literal mới.
// Nếu objA và objB có các khóa trùng nhau, khóa sau sẽ thắng. Trình biên dịch
// thấy literal kết quả này và kiểm tra nó với 'SpecificShape'.

// Để làm cho nó hoạt động, bạn có thể cần một bước trung gian hoặc một kiểu dễ dãi hơn:

const tempObj = {
  ...objA,
  ...objB
};

// Bây giờ, nếu tempObj có các thuộc tính không có trong SpecificShape, việc gán sẽ thất bại:
// const mergedCorrected: SpecificShape = tempObj; // Lỗi: Object literal chỉ có thể chỉ định các thuộc tính đã biết...

// Điểm mấu chốt là trình biên dịch phân tích hình dạng của object literal đang được hình thành.
// Nếu literal đó chứa các thuộc tính không được định nghĩa trong kiểu mục tiêu, đó là một lỗi.

// Trường hợp sử dụng điển hình cho cú pháp spread với việc kiểm tra thuộc tính dư thừa:

interface UserProfile {
  userId: string;
  username: string;
}

interface AdminProfile extends UserProfile {
  adminLevel: number;
}

const baseUserData: UserProfile = {
  userId: 'user-123',
  username: 'coder'
};

const adminData = {
  adminLevel: 5,
  lastLogin: '2023-10-27'
};

// Đây là nơi việc kiểm tra thuộc tính dư thừa có liên quan:
// const adminProfile: AdminProfile = {
//   ...baseUserData,
//   ...adminData // Lỗi: Object literal chỉ có thể chỉ định các thuộc tính đã biết, và 'lastLogin' không tồn tại trong kiểu 'AdminProfile'.
// };

// Object literal được tạo bởi spread có 'lastLogin', không có trong 'AdminProfile'.
// Để khắc phục điều này, 'adminData' lý tưởng nên tuân thủ AdminProfile hoặc thuộc tính dư thừa nên được xử lý.

// Cách tiếp cận đã sửa:
const validAdminData = {
  adminLevel: 5
};

const adminProfileCorrect: AdminProfile = {
  ...baseUserData,
  ...validAdminData
};

console.log(adminProfileCorrect.userId);
console.log(adminProfileCorrect.adminLevel);

Việc kiểm tra thuộc tính dư thừa áp dụng cho object literal kết quả được tạo bởi cú pháp spread. Nếu object literal này chứa các thuộc tính không được khai báo trong kiểu mục tiêu, TypeScript sẽ báo lỗi.

Các chiến lược để xử lý thuộc tính dư thừa

Mặc dù việc kiểm tra thuộc tính dư thừa là hữu ích, có những kịch bản hợp lệ mà bạn có thể có các thuộc tính thừa mà bạn muốn bao gồm hoặc xử lý khác đi. Dưới đây là các chiến lược phổ biến:

1. Thuộc tính còn lại (Rest Properties) với Type Alias hoặc Interface

Bạn có thể sử dụng cú pháp tham số còn lại (`...rest`) trong type alias hoặc interface để nắm bắt bất kỳ thuộc tính còn lại nào không được định nghĩa một cách tường minh. Đây là một cách sạch sẽ để công nhận và thu thập các thuộc tính dư thừa này.


interface UserProfile {
  id: number;
  name: string;
}

interface UserWithMetadata extends UserProfile {
  metadata: {
    [key: string]: any;
  };
}

// Hoặc phổ biến hơn với một type alias và cú pháp rest:
type UserProfileWithMetadata = UserProfile & {
  [key: string]: any;
};

const user1: UserProfileWithMetadata = {
  id: 1,
  name: 'Bob',
  email: 'bob@example.com',
  isAdmin: true
};

// Không có lỗi, vì 'email' và 'isAdmin' được nắm bắt bởi chữ ký chỉ mục trong UserProfileWithMetadata.
console.log(user1.email);
console.log(user1.isAdmin);

// Một cách khác sử dụng tham số rest trong định nghĩa kiểu:
interface ConfigWithRest {
  apiUrl: string;
  timeout?: number;
  // Nắm bắt tất cả các thuộc tính khác vào 'extraConfig'
  [key: string]: any;
}

const appConfig: ConfigWithRest = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  featureFlags: {
    newUI: true,
    betaFeatures: false
  }
};

console.log(appConfig.featureFlags);

Sử dụng `[key: string]: any;` hoặc các chữ ký chỉ mục tương tự là cách thành ngữ để xử lý các thuộc tính bổ sung tùy ý.

2. Tách cấu trúc (Destructuring) với Cú pháp Rest

Khi bạn nhận một đối tượng và cần trích xuất các thuộc tính cụ thể trong khi giữ lại phần còn lại, việc tách cấu trúc với cú pháp rest là vô giá.


interface Employee {
  employeeId: string;
  department: string;
}

function processEmployeeData(data: Employee & { [key: string]: any }) {
  const { employeeId, department, ...otherDetails } = data;

  console.log(`Employee ID: ${employeeId}`);
  console.log(`Department: ${department}`);
  console.log('Other details:', otherDetails);
  // otherDetails sẽ chứa bất kỳ thuộc tính nào không được tách cấu trúc một cách tường minh,
  // như 'salary', 'startDate', v.v.
}

const employeeInfo = {
  employeeId: 'emp-789',
  department: 'Engineering',
  salary: 90000,
  startDate: '2022-01-15'
};

processEmployeeData(employeeInfo);

// Ngay cả khi employeeInfo ban đầu có một thuộc tính thừa, việc kiểm tra thuộc tính dư thừa
// bị bỏ qua nếu chữ ký hàm chấp nhận nó (ví dụ: sử dụng chữ ký chỉ mục).
// Nếu processEmployeeData được định kiểu nghiêm ngặt là 'Employee', và employeeInfo có 'salary',
// một lỗi sẽ xảy ra NẾU employeeInfo là một object literal được truyền trực tiếp.
// Nhưng ở đây, employeeInfo là một biến, và kiểu của hàm xử lý các thuộc tính thừa.

3. Định nghĩa tường minh tất cả các thuộc tính (nếu đã biết)

Nếu bạn biết các thuộc tính bổ sung tiềm năng, cách tiếp cận tốt nhất là thêm chúng vào interface hoặc type alias của bạn. Điều này cung cấp sự an toàn kiểu cao nhất.


interface UserProfile {
  id: number;
  name: string;
  email?: string; // email tùy chọn
}

const userWithEmail: UserProfile = {
  id: 2,
  name: 'Charlie',
  email: 'charlie@example.com'
};

const userWithoutEmail: UserProfile = {
  id: 3,
  name: 'David'
};

// Nếu chúng ta cố gắng thêm một thuộc tính không có trong UserProfile:
// const userWithExtra: UserProfile = {
//   id: 4,
//   name: 'Eve',
//   phoneNumber: '555-1234'
// }; // Lỗi: Object literal chỉ có thể chỉ định các thuộc tính đã biết, và 'phoneNumber' không tồn tại trong kiểu 'UserProfile'.

4. Sử dụng `as` để xác nhận kiểu (thận trọng)

Như đã trình bày trước đó, xác nhận kiểu có thể ngăn chặn việc kiểm tra thuộc tính dư thừa. Hãy sử dụng điều này một cách tiết kiệm và chỉ khi bạn hoàn toàn chắc chắn về hình dạng của đối tượng.


interface ProductConfig {
  id: string;
  version: string;
}

// Hãy tưởng tượng điều này đến từ một nguồn bên ngoài hoặc một module ít nghiêm ngặt hơn
const externalConfig = {
  id: 'prod-abc',
  version: '1.2',
  debugMode: true // Thuộc tính dư thừa
};

// Nếu bạn biết 'externalConfig' sẽ luôn có 'id' và 'version' và bạn muốn coi nó như ProductConfig:
const productConfig = externalConfig as ProductConfig;

// Xác nhận này bỏ qua việc kiểm tra thuộc tính dư thừa trên chính `externalConfig`.
// Tuy nhiên, nếu bạn truyền trực tiếp một object literal:

// const productConfigLiteral: ProductConfig = {
//   id: 'prod-xyz',
//   version: '2.0',
//   debugMode: false
// }; // Lỗi: Object literal chỉ có thể chỉ định các thuộc tính đã biết, và 'debugMode' không tồn tại trong kiểu 'ProductConfig'.

5. Bộ bảo vệ kiểu (Type Guards)

Đối với các kịch bản phức tạp hơn, các bộ bảo vệ kiểu có thể giúp thu hẹp kiểu và xử lý thuộc tính theo điều kiện.


interface Shape {
  kind: 'circle' | 'square';
}

interface Circle extends Shape {
  kind: 'circle';
  radius: number;
}

interface Square extends Shape {
  kind: 'square';
  sideLength: number;
}

function calculateArea(shape: Shape) {
  if (shape.kind === 'circle') {
    // TypeScript biết 'shape' là một Circle ở đây
    console.log(Math.PI * shape.radius ** 2);
  } else if (shape.kind === 'square') {
    // TypeScript biết 'shape' là một Square ở đây
    console.log(shape.sideLength ** 2);
  }
}

const circleData = {
  kind: 'circle' as const, // Sử dụng 'as const' để suy luận kiểu literal
  radius: 10,
  color: 'red' // Thuộc tính dư thừa
};

// Khi được truyền cho calculateArea, chữ ký hàm mong đợi 'Shape'.
// Bản thân hàm sẽ truy cập 'kind' một cách chính xác.
// Nếu calculateArea mong đợi 'Circle' trực tiếp và nhận circleData
// như một object literal, 'color' sẽ là một vấn đề.

// Hãy minh họa việc kiểm tra thuộc tính dư thừa với một hàm mong đợi một kiểu con cụ thể:

function processCircle(circle: Circle) {
  console.log(`Processing circle with radius: ${circle.radius}`);
}

// processCircle(circleData); // Lỗi: Đối số kiểu '{ kind: "circle"; radius: number; color: string; }' không thể gán cho tham số kiểu 'Circle'.
                         // Object literal chỉ có thể chỉ định các thuộc tính đã biết, và 'color' không tồn tại trong kiểu 'Circle'.

// Để khắc phục điều này, bạn có thể tách cấu trúc hoặc sử dụng một kiểu dễ dãi hơn cho circleData:

const { color, ...circleDataWithoutColor } = circleData;
processCircle(circleDataWithoutColor);

// Hoặc định nghĩa circleData để bao gồm một kiểu rộng hơn:

const circleDataWithExtras: Circle & { [key: string]: any } = {
  kind: 'circle',
  radius: 15,
  color: 'blue'
};
processCircle(circleDataWithExtras); // Bây giờ nó hoạt động.

Những cạm bẫy phổ biến và cách tránh

Ngay cả những nhà phát triển có kinh nghiệm đôi khi cũng có thể bị bất ngờ bởi việc kiểm tra thuộc tính dư thừa. Dưới đây là những cạm bẫy phổ biến:

Những lưu ý toàn cục và các thực hành tốt nhất

Khi làm việc trong một môi trường phát triển toàn cầu và đa dạng, việc tuân thủ các thực hành nhất quán về an toàn kiểu là rất quan trọng:

Kết luận

Việc kiểm tra thuộc tính dư thừa của TypeScript là một nền tảng cho khả năng cung cấp sự an toàn kiểu đối tượng mạnh mẽ của nó. Bằng cách hiểu khi nào và tại sao các kiểm tra này xảy ra, các nhà phát triển có thể viết mã dễ dự đoán hơn và ít bị lỗi hơn.

Đối với các nhà phát triển trên toàn thế giới, việc nắm bắt tính năng này có nghĩa là ít bất ngờ hơn lúc chạy, hợp tác dễ dàng hơn và các cơ sở mã dễ bảo trì hơn. Cho dù bạn đang xây dựng một tiện ích nhỏ hay một ứng dụng doanh nghiệp quy mô lớn, việc thành thạo kiểm tra thuộc tính dư thừa chắc chắn sẽ nâng cao chất lượng và độ tin cậy của các dự án JavaScript của bạn.

Những điểm chính cần nhớ:

Bằng cách áp dụng một cách có ý thức những nguyên tắc này, bạn có thể tăng cường đáng kể sự an toàn và khả năng bảo trì của mã TypeScript của mình, dẫn đến kết quả phát triển phần mềm thành công hơn.