Giao diện TypeScript so với loại: Sự khác biệt thực sự, được thử nghiệm bằng trình biên dịch

  1. Trang chủ
  2. Blog
  3. Phát triển phần mềm

Phát triển phần mềm

Giao diện TypeScript so với loại: Sự khác biệt thực sự, đã được thử nghiệm bằng trình biên dịch

Các giao diện và bí danh loại trùng nhau nhiều đến mức sự lựa chọn thường mang lại cảm giác tùy tiện. Đây là những gì mỗi bên có thể làm mà bên kia không thể, được xác nhận bằng các lỗi biên dịch thực sự, cộng với một quy tắc đơn giản mà nhóm của bạn có thể tuân theo.

Đọc 8 phút
Giao diện TypeScript so với loại: sự khác biệt thực sự, được thử nghiệm với trình biên dịch

Điểm chính

  • Đối với hình dạng đối tượng đơn giản, giao diện và bí danh loại gần như có thể thay thế cho nhau.
  • Chỉ type mới có thể đặt tên cho các tập hợp, bộ dữ liệu và các loại phi đối tượng khác.
  • Chỉ interface hỗ trợ hợp nhất khai báo, trong đó hai khai báo kết hợp.
  • Một quy tắc chung: giao diện cho hình dạng đối tượng, type cho mọi thứ khác, được áp dụng nhất quán.

Rất ít câu hỏi về TypeScript gây ra nhiều tranh luận như Giao diện TypeScript so với loại. Nhiều bài viết liệt kê những khác biệt từng quan trọng trong các phiên bản cũ nhưng hiện đã lỗi thời. Vì vậy, thay vì lặp lại các tuyên bố, chúng tôi đã thử nghiệm chúng.

Chúng tôi đã viết một tệp thể hiện từng điểm khác biệt và kiểm tra nó bằng TypeScript 5.6.3 ở chế độ nghiêm ngặt. Các lỗi trình biên dịch được trích dẫn dưới đây xuất phát từ lần chạy đó. Hành vi có thể thay đổi giữa các phiên bản, vì vậy hãy kiểm tra phiên bản TypeScript mà dự án của bạn sử dụng. Nếu bạn chưa quen với ngôn ngữ này, hãy bắt đầu với Hướng dẫn TypeScript dành cho người mới bắt đầu của chúng tôi.

Câu trả lời ngắn gọn

interface mô tả hình dạng của một vật thể và có thể được mở rộng hoặc mở lại. Bí danh type đặt tên cho loại bất kỳ nào, bao gồm các đối tượng, kết hợp, bộ dữ liệu và loại hàm. Khi cả hai đều có thể hoạt động, sự khác biệt chủ yếu là ở phong cách.

Chúng giống nhau ở chỗ nào

Hai khai báo này mô tả các hình dạng giống hệt nhau:

interface User {
  id: number;
  name: string;
  email?: string;
}

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

const a: User = { id: 1, name: "Asha" };
const b: UserAlias = a;   // no error

TypeScript sử dụng kiểu cấu trúc, có nghĩa là nó so sánh các hình dạng hơn là so sánh tên. Đó là lý do tại sao User có thể được gán cho UserAlias mà không có khiếu nại. Cả hai đều hỗ trợ các thuộc tính tùy chọn, thuộc tính readonly, phương thức và chữ ký chỉ mục.

Giao diện và loại cạnh nhau

tính năng giao diện loại
Hình dạng đối tượng Có Có
Các công đoàn, chẳng hạn như "a" | "b" Không Có
Bộ dữ liệu và nguyên thủy Không Có
Các loại được ánh xạ và có điều kiện Không Có
Tuyên bố sáp nhập Có Không, đó là một lỗi
Mở rộng mở rộng & ngã tư
Thuộc tính xung đột Lỗi khi khai báo Có thể lặng lẽ trở thành never
Được sử dụng với implements Có Có, nếu đó là một loại đối tượng

Điều chỉ có loại mới có thể làm

Nhiều loại hữu ích hoàn toàn không phải là hình dạng đối tượng và chỉ có bí danh loại mới có thể đặt tên cho chúng:

type Status = "pending" | "paid" | "shipped";   // union
type Point = [number, number];                    // tuple
type Handler = (event: string) => void;           // function type
type Keys = keyof User;                           // computed type
type ReadonlyUser = Readonly<User>;               // utility type

Các kiểu kết hợp là một trong những tính năng có giá trị nhất của TypeScript, vì khi đó trình biên dịch sẽ từ chối bất kỳ giá trị nào ngoài danh sách:

let s: Status = "paid";
s = "refunded";

// error TS2322: Type '"refunded"' is not assignable to type 'Status'.

Điều này giúp phát hiện các lỗi chính tả và các giá trị lỗi thời tại thời điểm biên dịch, trước khi chúng được đưa vào sản xuất.

Tuyên bố sáp nhập

Nếu bạn khai báo một giao diện có cùng tên hai lần, TypeScript sẽ hợp nhất chúng thành một:

interface Settings {
  theme: string;
}
interface Settings {
  language: string;
}

const settings: Settings = { theme: "dark", language: "en" };   // no error

Hãy thử điều tương tự với bí danh loại và trình biên dịch từ chối:

type Config = { theme: string };
type Config = { language: string };

// error TS2300: Duplicate identifier 'Config'.

Hợp nhất là cách các thư viện cho phép bạn mở rộng loại của chúng. Ví dụ: bạn có thể thêm thuộc tính tùy chỉnh vào giao diện Window chung hoặc vào đối tượng yêu cầu của khung mà không cần sửa đổi thư viện. Điều đó thực sự hữu ích.

Mặt trái của nó là việc sáp nhập có thể xảy ra một cách tình cờ. Nếu hai tệp trong một dự án khai báo một giao diện có cùng tên trong cùng một phạm vi, chúng sẽ âm thầm kết hợp với nhau, điều này có thể gây nhầm lẫn. Thay vào đó, một bí danh loại làm cho xung đột đó trở thành một lỗi.

mở rộng vs giao lộ

Cả hai đều cho phép bạn xây dựng trên một loại hiện có:

interface Admin extends User {
  permissions: string[];
}

type AdminAlias = UserAlias & { permissions: string[] };

Đối với các thuộc tính tương thích, kết quả là như nhau. Sự khác biệt thể hiện qua xung đột. Khi một giao diện cố gắng thay đổi loại thuộc tính không tương thích, trình biên dịch sẽ báo cáo điều đó ngay lập tức:

interface Base { id: number }
interface Broken extends Base { id: string }

// error TS2430: Interface 'Broken' incorrectly extends interface 'Base'.
//   Types of property 'id' are incompatible.
//     Type 'string' is not assignable to type 'number'.

Giao lộ không phàn nàn khi được khai báo. Thay vào đó, thuộc tính xung đột trở thành never, một loại không có giá trị nào có thể có. Lỗi chỉ xuất hiện sau này, khi bạn cố gắng sử dụng nó:

type Mixed = { id: number } & { id: string };
const m: Mixed = { id: 1 };

// error TS2322: Type 'number' is not assignable to type 'never'.

Lỗi sau đó còn khó hiểu hơn vì nó chỉ ra lỗi thực sự. Đây là một lý do thực tế để ưu tiên extends khi xây dựng các loại đối tượng.

Lớp học và dụng cụ

Một lớp có thể hứa khớp một hình dạng bằng cách sử dụng implements và điều này hoạt động với giao diện hoặc bí danh loại đối tượng:

class Account implements User {
  constructor(public id: number, public name: string) {}
}

Điều này được biên dịch không có lỗi. Lưu ý rằng implements chỉ kiểm tra xem lớp đó có khớp không. Nó không thêm bất kỳ thuộc tính hoặc mã nào. Một lớp không thể triển khai kiểu kết hợp vì không có hình dạng duy nhất nào phù hợp.

Các loại tiện ích và chỉ đọc

Các loại tiện ích như Readonly, Partial, Pick và Omit hoạt động với cả giao diện và bí danh. Kết quả là một loại mới, thường được đặt tên bằng bí danh loại:

const r: ReadonlyUser = { id: 3, name: "Dev" };
r.name = "Chitra";

// error TS2540: Cannot assign to 'name' because it is a read-only property.

Hãy nhớ rằng readonly chỉ là kiểm tra thời gian biên dịch. Trong thời gian chạy, đối tượng là JavaScript thông thường và nó vẫn có thể được thay đổi bằng mã bỏ qua các loại.

Mẹo: Các loại biến mất khi TypeScript biên dịch sang JavaScript. Dữ liệu đến từ bên ngoài, chẳng hạn như phản hồi API, không được kiểm tra trong thời gian chạy chỉ vì nó có một loại. Xác thực nó, ví dụ như với thư viện lược đồ. Hướng dẫn tìm nạp API của chúng tôi đề cập đến việc xử lý phản hồi API một cách an toàn.

Hiệu suất trình biên dịch

Hướng dẫn hiệu suất riêng của nhóm TypeScript đề xuất ưu tiên các giao diện sử dụng extends hơn các nút giao lớn, vì trình biên dịch có thể lưu trữ các mối quan hệ giao diện hiệu quả hơn. Trong các dự án vừa và nhỏ, bạn khó có thể nhận thấy bất kỳ sự khác biệt nào. Nó có thể quan trọng trong các cơ sở mã rất lớn với các kiểu được soạn thảo sâu. Chúng tôi đã không tự đo lường điều này.

Bạn nên sử dụng cái nào?

Một quy tắc được sử dụng rộng rãi, phù hợp với hướng dẫn từ sổ tay TypeScript, là:

  • Sử dụng interface cho hình dạng đối tượng, đặc biệt là các API công khai và bất kỳ thứ gì mà các lớp triển khai hoặc các lớp khác có thể mở rộng.
  • Sử dụng type cho các hợp nhất, bộ dữ liệu, loại hàm và loại được ánh xạ hoặc tính toán.
Bạn đang xác định sử dụng
Một mô hình dữ liệu, chẳng hạn như Người dùng hoặc Đơn hàng giao diện
Tập hợp các giá trị được phép type công đoàn
Một cặp hoặc ba chiều dài cố định type bộ dữ liệu
Chữ ký gọi lại loại
Mở rộng các loại của thư viện interface, thông qua việc sáp nhập
Một biến thể của một loại hiện có type với một loại tiện ích

Một số đội chỉ cần sử dụng type ở mọi nơi và điều đó cũng hoạt động tốt. Điều quan trọng nhất là tính nhất quán, vì vậy hãy chọn một quy ước, viết nó vào hướng dẫn phong cách của bạn và để một kẻ nói dối thực thi nó. Quy tắc consistent-type-definitions của ESLint từ Typescript-eslint có thể thực hiện việc này.

Những lỗi thường gặp

  • Cuộc tranh luận bất tận. Cả hai đều phù hợp với đồ vật, vì vậy hãy giải quyết quy tắc một lần và tiếp tục.
  • Sự hợp nhất ngẫu nhiên. Việc sử dụng lại tên giao diện trong cùng phạm vi sẽ âm thầm kết hợp chúng.
  • Các giao lộ xung đột. Họ tạo ra các loại never với các lỗi khó hiểu sau này.
  • Các loại tin cậy trong thời gian chạy. Chúng bị xóa, vì vậy hãy xác thực dữ liệu bên ngoài.
  • Sử dụng any để tắt tiếng các lỗi. Nó sẽ tắt tính năng kiểm tra. Thích unknown và thu hẹp nó.

Chuyển đổi giữa chúng trong cơ sở mã hiện có

Thay đổi interface thành type hoặc ngược lại, thường an toàn đối với hình dạng đối tượng đơn giản, vì TypeScript so sánh các loại theo cấu trúc của chúng chứ không phải theo tên của chúng. Một số điều vẫn cần kiểm tra trước khi bạn chuyển đổi:

  • Việc chuyển đổi giao diện thành bí danh loại sẽ loại bỏ việc hợp nhất khai báo, do đó, bất kỳ khai báo thứ hai nào có cùng tên đều trở thành lỗi định danh trùng lặp.
  • Việc chuyển đổi bí danh loại thành giao diện chỉ hoạt động đối với hình dạng đối tượng. Các liên kết, bộ dữ liệu và các loại ánh xạ phải ở dạng bí danh loại.
  • Thông báo lỗi và trình chỉnh sửa di chuột có thể hiển thị loại khác nhau, điều này có thể gây ngạc nhiên cho đồng đội nhưng không thay đổi hành vi.

Thực hiện thay đổi trong một yêu cầu kéo tập trung, chạy trình biên dịch ở chế độ nghiêm ngặt và bộ thử nghiệm của bạn, đồng thời tránh trộn lẫn nó với công việc tính năng để người đánh giá có thể thấy rằng không có gì khác thay đổi.

Lời kết

Câu hỏi về giao diện và loại có một câu trả lời bình tĩnh: chúng chồng lên nhau đối với các đối tượng, mỗi đối tượng có một vài khả năng riêng biệt và tính nhất quán quan trọng hơn sự lựa chọn. Sử dụng giao diện cho hình dạng và kiểu cho mọi thứ khác, trừ khi nhóm của bạn muốn khác. Để tự mình thấy được sự khác biệt, hãy sao chép các ví dụ trên vào một tệp và chạy tsc --noEmit --strict trên đó.

Câu hỏi thường gặp

Sự khác biệt giữa giao diện và kiểu chữ trong TypeScript là gì?

Cả hai đều có thể mô tả hình dạng đối tượng. Chỉ loại mới có thể đặt tên cho các hợp nhất, bộ dữ liệu và loại được tính toán, trong khi chỉ giao diện mới hỗ trợ hợp nhất khai báo.

Tôi có nên sử dụng giao diện hoặc kiểu cho đạo cụ React không?

Hoặc hoạt động tốt. Nhiều nhóm sử dụng giao diện cho các đạo cụ, trong khi những nhóm khác thích loại hình nhất quán với các hiệp hội. Chọn một và áp dụng nó trong suốt dự án.

Một loại có thể mở rộng một giao diện?

Có, bằng cách sử dụng giao điểm như loại Quản trị viên = Người dùng & { quyền: chuỗi [] }. Một giao diện cũng có thể mở rộng bí danh loại đối tượng.

Giao diện có nhanh hơn loại không?

Hướng dẫn hiệu suất của TypeScript ủng hộ giao diện mở rộng trên các điểm giao nhau lớn trong các cơ sở mã lớn. Trong các dự án điển hình, sự khác biệt khó có thể nhận thấy được.

Tuyên bố hợp nhất là gì?

Đó là khi hai giao diện có cùng tên trong cùng phạm vi kết hợp thành một. Bí danh loại không thể hợp nhất và tên trùng lặp sẽ gây ra lỗi.

Tiếp tục học hỏi

Làm việc với Stack Quora

Cần trợ giúp về kiểm tra, WordPress hoặc SEO?

Chúng tôi giúp các nhóm phát triển phần mềm tốt hơn và phát triển trang web của họ. Hãy cho chúng tôi biết bạn đang làm gì và chúng tôi sẽ đề xuất bước tiếp theo rõ ràng.

Tiếp tục đọc

Thêm hướng dẫn về thử nghiệm, AI và phát triển hiện đại

Bạn thích bài viết này? Chọn một chủ đề và chuyển ngay sang phần hướng dẫn mới nhất của chúng tôi.

Trí tuệ nhân tạo

Giải thích về ảo giác AI: Tại sao các mô hình ngôn ngữ lại tạo nên mọi thứ và cách giảm thiểu nó

Ảo giác AI đã giải thích: chúng là gì, tại sao mô hình ngôn ngữ tạo ra chúng, các loại phổ biến, cách giảm thiểu chúng bằng cách căn cứ và nhắc nhở …

Đọc bài viết →
DevOps

Giải thích về các biến môi trường: Tệp .env, bí mật và cấu hình được thực hiện đúng

Các biến môi trường được giải thích bằng các ví dụ Python mà chúng tôi đã chạy: đọc và thiết lập chúng, tại sao mọi giá trị đều là một chuỗi, tệp .e…

Đọc bài viết →
Phát triển phần mềm

Giải thích về giới hạn tỷ lệ API: Thuật toán, tiêu đề và xử lý lỗi 429

Giới hạn tốc độ API được giải thích bằng mô phỏng mà chúng tôi đã chạy: nhóm mã thông báo, cửa sổ cố định, cửa sổ trượt, 429 phản hồi, tiêu đề giới …

Đọc bài viết →
Tự động hóa kiểm thử

Chế giễu bằng Python: unittest.mock, patch và những sai lầm khiến bài kiểm tra nói dối

Mô phỏng bằng Python với unittest.mock, đã được thử nghiệm: Mock và return_value, side_effect, vá lỗi nơi sử dụng tên, thông số kỹ thuật và thông số…

Đọc bài viết →
Kiểm thử phần mềm

Giải thích về phạm vi bảo hiểm của mã: Phạm vi tuyến so với chi nhánh và ý nghĩa thực sự 100%

Phạm vi bảo hiểm mã được giải thích bằng cách chạy phạm vi bảo hiểm thực tế: phạm vi bảo hiểm dòng, chi nhánh và điều kiện, đọc báo cáo, lý do tại s…

Đọc bài viết →
Cơ sở dữ liệu & SQL

DELETE vs TRUNCATE vs DROP trong SQL: Sự khác biệt, khôi phục và thời điểm sử dụng mỗi loại

DELETE vs TRUNCATE vs DROP được giải thích bằng SQL đã được thử nghiệm: mỗi loại loại bỏ, khôi phục, kích hoạt, đặt lại danh tính, tốc độ và Postgre…

Đọc bài viết →
Cơ sở dữ liệu & SQL

Giải thích về Chế độ xem SQL: Tạo, sử dụng và tránh những cạm bẫy, kèm theo ví dụ

Chế độ xem SQL được giải thích bằng các truy vấn chúng tôi đã chạy: tạo chế độ xem, kết quả trực tiếp, ẩn cột, hiệu suất, chế độ xem có thể cập nhật…

Đọc bài viết →
Phát triển phần mềm

Giao diện TypeScript so với loại: Sự khác biệt thực sự, được thử nghiệm bằng trình biên dịch

Giao diện TypeScript so với loại, được kiểm tra bằng trình biên dịch thực: cả hai có thể làm gì, chỉ một người có thể làm gì, hợp nhất khai báo, mở …

Đọc bài viết →
Phát triển phần mềm

Giải thích về Python *args và **kwargs: Hàm linh hoạt với ví dụ rõ ràng

Python *args và **kwargs được giải thích bằng mã chúng tôi đã chạy: đóng gói và giải nén, thứ tự tham số, đối số chỉ từ khóa và chỉ vị trí, trình tr…

Đọc bài viết →
Phát triển phần mềm

Hàm Lambda của Python: Cú pháp, cách sử dụng thực tế và khi nào không nên sử dụng chúng

Các hàm lambda của Python được giải thích bằng mã chúng tôi đã chạy: cú pháp, sắp xếp bằng khóa, bản đồ và bộ lọc, các phần đóng, bẫy liên kết muộn …

Đọc bài viết →
Trí tuệ nhân tạo

Đại lý AI là gì? Cách chúng hoạt động, với vòng lặp tác nhân đơn giản, bạn có thể chạy

Tác nhân AI là gì? Hướng dẫn đơn giản về vòng lặp tác nhân, công cụ, bộ nhớ và lan can, với một vòng lặp Python nhỏ mà chúng tôi đã chạy, những rủi …

Đọc bài viết →
DevOps

Giám sát và Khả năng quan sát: Sự khác biệt thực sự, với nhật ký, số liệu và dấu vết

Giải thích về giám sát và khả năng quan sát: lỗi đã biết và chưa biết, nhật ký, số liệu và dấu vết, hướng dẫn về sự cố thanh toán, tín hiệu vàng, Op…

Đọc bài viết →
DevOps

Docker Volumes vs Bind Mounts: Duy trì dữ liệu vùng chứa đúng cách

Giải thích về khối lượng Docker và liên kết gắn kết: tại sao dữ liệu vùng chứa biến mất, khối lượng được đặt tên, liên kết gắn kết, tmpfs, ví dụ về …

Đọc bài viết →
Tự động hóa kiểm thử

TDD vs BDD: Key Differences, One Feature Built Both Ways and When to Use Each

TDD và BDD được giải thích bằng một tính năng giảm giá được xây dựng theo cả hai cách: tái cấu trúc màu đỏ-xanh với mã chúng tôi đã chạy, kịch bản C…

Đọc bài viết →
Phát triển phần mềm

Git merge vs rebase: khác biệt, ví dụ và khi nào dùng cái nào

Git merge vs rebase được giải thích bằng đầu ra log thực tế, bảng so sánh, quy tắc vàng của rebase và quy trình làm việc nhóm an toàn.

Đọc bài viết →
Phát triển phần mềm

List comprehension trong Python: cú pháp, ví dụ và khi nào nên tránh

List comprehension trong Python: cú pháp, lọc dữ liệu, if-else, vòng lặp lồng nhau, thời gian đo thực tế và khi nào vòng lặp for thông thường tốt hơn.

Đọc bài viết →
Cơ sở dữ liệu & SQL

Giải thích SQL JOIN kèm ví dụ (Inner, Left, Right, Full)

Hiểu SQL JOIN từng bước với bảng và truy vấn mẫu: INNER, LEFT, RIGHT, FULL, CROSS và SELF, cùng lỗi thường gặp và mẹo hiệu năng.

Đọc bài viết →