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.
- Đố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ỉ
typemớ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ỉ
interfacehỗ 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,
typecho 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
interfacecho 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
typecho 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
nevervớ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íchunknownvà 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
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.
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.
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 →DevOpsGiả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ềmGiả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ềmGiả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 & SQLDELETE 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 & SQLGiả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ềmGiao 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ềmGiả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ềmHà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 →DevOpsGiá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 →DevOpsDocker 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ềmGit 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ềmList 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 & SQLGiả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 →