Skip to content
Home
Go back

TypeScript: Interface và Type khác nhau ở đâu

Mục lục

Xem mục lục

Điểm chung

Cả interface và type đều dùng để mô tả shape của một object, và phần lớn trường hợp cơ bản có thể dùng thay thế cho nhau:

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

type UserType = {
  id: number;
  name: string;
};

const u1: UserInterface = { id: 1, name: "An" };
const u2: UserType = { id: 2, name: "Bình" };

Khác biệt thật sự chỉ lộ ra ở các trường hợp nằm ngoài object shape đơn giản.

Khả năng mở rộng (Declaration Merging)

interface khai báo trùng tên nhiều lần sẽ tự động merge lại thành một, các thuộc tính được gộp chung:

interface User {
  id: number;
}

interface User {
  name: string;
}

// User bây giờ có cả id và name
const u: User = { id: 1, name: "An" };

type khai báo trùng tên sẽ báo lỗi biên dịch ngay:

type User = {
  id: number;
};

type User = {
  // Lỗi: Duplicate identifier 'User'
  name: string;
};

Declaration merging là lý do các thư viện thường expose type public bằng interface: người dùng thư viện có thể tự mở rộng thêm field vào interface đó ở chỗ khác, ví dụ mở rộng Window hay các type toàn cục của Express.

extends vs Intersection (&)

interface mở rộng bằng extends:

interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
}

type kết hợp bằng intersection &:

type Animal = {
  name: string;
};

type Dog = Animal & {
  breed: string;
};

Khác biệt xuất hiện khi có xung đột thuộc tính cùng tên nhưng khác kiểu dữ liệu. interface extends sẽ báo lỗi ngay lúc khai báo:

interface A {
  value: string;
}

interface B extends A {
  value: number; // Lỗi: Interface 'B' incorrectly extends interface 'A'
}

Intersection với type không báo lỗi, mà âm thầm gộp hai kiểu lại thành never, vì không có giá trị nào vừa là string vừa là number:

type A = { value: string };
type B = A & { value: number };

// B.value có kiểu never, gán bất kỳ giá trị nào cũng báo lỗi
const b: B = { value: "x" }; // Lỗi

Đây là điểm cần lưu ý: lỗi xung đột kiểu ở interface được phát hiện sớm và rõ ràng hơn, trong khi type intersection để lỗi trôi tới tận chỗ sử dụng giá trị.

type làm được nhiều hơn object shape

interface chỉ mô tả được shape của object (hoặc function, class). type làm được nhiều việc mà interface không làm được:

Union type:

type Status = "pending" | "success" | "error";

Tuple:

type Point = [number, number];

Alias cho primitive hoặc kiểu bất kỳ:

type ID = string | number;

Mapped type:

type Partial<T> = {
  [K in keyof T]?: T[K];
};

Conditional type:

type IsString<T> = T extends string ? true : false;

Không có cách nào biểu diễn các kiểu trên bằng interface, vì interface chỉ có cú pháp mô tả cấu trúc object, không có cơ chế union hay conditional.

Hiệu năng compiler với union lớn

TypeScript compiler xử lý interface hiệu quả hơn type khi số lượng thuộc tính hoặc số lần merge tăng lên, vì interface có cấu trúc cố định, dễ cache lại kết quả kiểm tra kiểu. type, đặc biệt là union và intersection lớn, phải được compiler tính toán lại (resolve) mỗi lần sử dụng, không cache được như interface.

Với object shape đơn giản, khác biệt hiệu năng gần như không đáng kể. Nhưng với codebase lớn, nhiều union phức tạp lồng nhau, type có thể làm chậm compile time rõ rệt hơn interface. Đây là lý do các file khai báo kiểu (.d.ts) của thư viện lớn thường ưu tiên interface cho object shape.

Trường hợp bắt buộc phải dùng interface

Implement bằng class: implements hoạt động với cả interface lẫn type, nhưng interface là lựa chọn tự nhiên hơn khi mô tả contract cho class, vì thông báo lỗi rõ ràng hơn khi class thiếu thuộc tính:

interface Shape {
  area(): number;
}

class Circle implements Shape {
  constructor(private radius: number) {}
  area() {
    return Math.PI * this.radius ** 2;
  }
}

Cần declaration merging: khi cần mở rộng một type có sẵn từ thư viện bên ngoài (ví dụ thêm field vào Request của Express, hay mở rộng Window ở global scope), chỉ interface làm được, vì type không hỗ trợ khai báo trùng tên.

Quy tắc chọn trong thực tế

TypeScript Handbook đưa ra một heuristic ngắn gọn: dùng interface cho đến khi cần một tính năng chỉ type mới có (“use interface until you need to use features from type”). Nói cách khác, interface là lựa chọn mặc định, type chỉ dùng khi thực sự cần.

Cụ thể hóa quy tắc đó:

Tổng kết

Tiêu chíinterfacetype
Mô tả object shapeCóCó
Declaration mergingCóKhông, báo lỗi trùng tên
Mở rộngextends, báo lỗi sớm khi xung đột&, xung đột thành never âm thầm
Union, tuple, conditional typeKhông hỗ trợCó
Hiệu năng compiler (union/merge lớn)Tốt hơnCó thể chậm hơn
Dùng với implementsPhù hợpCũng dùng được, nhưng ít tự nhiên hơn

Phần lớn trường hợp object shape đơn giản, chọn cái nào cũng không khác biệt nhiều. Khác biệt chỉ thật sự quan trọng khi cần declaration merging, cần các kiểu nâng cao như union/conditional type, hoặc khi hiệu năng compile time trở thành vấn đề trong codebase lớn.


Share this post:

Previous Post
Từ khóa `this` trong JavaScript: Cách xác định giá trị
Next Post
Higher-Order Function trong JavaScript