Sự khác biệt giữa interface và type
Bằng cách sử dụng type alias, bạn có thể định nghĩa tương tự như interface.
tsinterfaceAnimal {name : string;bark (): string;}typeAnimal = {name : string;bark (): string;};
tsinterfaceAnimal {name : string;bark (): string;}typeAnimal = {name : string;bark (): string;};
Chương này sẽ giải thích chi tiết sự khác biệt giữa interface và type alias.
Sự khác biệt giữa interface và type alias
| Nội dung | Interface | Type alias |
|---|---|---|
| Kế thừa | Có thể | Không thể. Tuy nhiên có thể biểu diễn bằng intersection type |
| Override khi kế thừa | Override hoặc error | Intersection type được tính cho mỗi field |
| Khai báo cùng tên | Định nghĩa được merge | Error |
| Mapped Types | Không sử dụng được | Sử dụng được |
Kế thừa
Interface có thể kế thừa interface hoặc type alias.
tsinterfaceAnimal {name : string;}typeCreature = {dna : string;};interfaceDog extendsAnimal ,Creature {dogType : string;}
tsinterfaceAnimal {name : string;}typeCreature = {dna : string;};interfaceDog extendsAnimal ,Creature {dogType : string;}
Mặt khác, type alias không thể kế thừa. Thay vào đó, bằng cách sử dụng intersection type (&), có thể thực hiện điều tương tự như kế thừa.
tstypeAnimal = {name : string;};typeCreature = {dna : string;};typeDog =Animal &Creature & {dogType : string;};
tstypeAnimal = {name : string;};typeCreature = {dna : string;};typeDog =Animal &Creature & {dogType : string;};
Override property
Khi override property trong quá trình kế thừa interface, type của property từ nguồn kế thừa sẽ bị ghi đè.
ts// OKinterfaceAnimal {name : any;price : {yen : number;};legCount : number;}interfaceDog extendsAnimal {name : string;price : {yen : number;dollar : number;};}// Định nghĩa cuối cùng của DoginterfaceDog {name : string;price : {yen : number;dollar : number;};legCount : number;}
ts// OKinterfaceAnimal {name : any;price : {yen : number;};legCount : number;}interfaceDog extendsAnimal {name : string;price : {yen : number;dollar : number;};}// Định nghĩa cuối cùng của DoginterfaceDog {name : string;price : {yen : number;dollar : number;};legCount : number;}
Tuy nhiên, để override, phải có thể gán được vào type gốc. Ví dụ sau là trường hợp cố gắng override field có type number bằng type string.
tsinterfaceA {numberField : number;price : {yen : number;dollar : number;};}interfaceInterface 'B' incorrectly extends interface 'A'. Types of property 'numberField' are incompatible. Type 'string' is not assignable to type 'number'.2430Interface 'B' incorrectly extends interface 'A'. Types of property 'numberField' are incompatible. Type 'string' is not assignable to type 'number'.extends B A {numberField : string;price : {yen : number;euro : number;};}
tsinterfaceA {numberField : number;price : {yen : number;dollar : number;};}interfaceInterface 'B' incorrectly extends interface 'A'. Types of property 'numberField' are incompatible. Type 'string' is not assignable to type 'number'.2430Interface 'B' incorrectly extends interface 'A'. Types of property 'numberField' are incompatible. Type 'string' is not assignable to type 'number'.extends B A {numberField : string;price : {yen : number;euro : number;};}
Mặt khác, trong trường hợp type alias, không phải là ghi đè mà intersection type của type của field được tính toán. Ngoài ra, ngay cả khi có mâu thuẫn trong intersection type và không thể tính toán, cũng không xảy ra compile error.
tstypeAnimal = {name : number;price : {yen : number;dollar : number;};};typeDog =Animal & {name : string;price : {yen : number;euro : number;};};// Định nghĩa cuối cùng của DogtypeDog = {name : never; // Khi không thể tạo intersection type, thành type never thay vì compile errorprice : {yen : number;dollar : number;euro : number;};};
tstypeAnimal = {name : number;price : {yen : number;dollar : number;};};typeDog =Animal & {name : string;price : {yen : number;euro : number;};};// Định nghĩa cuối cùng của DogtypeDog = {name : never; // Khi không thể tạo intersection type, thành type never thay vì compile errorprice : {yen : number;dollar : number;euro : number;};};
Khai báo cùng tên
Type alias không thể định nghĩa nhiều type cùng tên, sẽ xảy ra compile error.
tstypeDuplicate identifier 'SameNameTypeWillError'.2300Duplicate identifier 'SameNameTypeWillError'.= { SameNameTypeWillError message : string;};typeDuplicate identifier 'SameNameTypeWillError'.2300Duplicate identifier 'SameNameTypeWillError'.= { SameNameTypeWillError detail : string;};
tstypeDuplicate identifier 'SameNameTypeWillError'.2300Duplicate identifier 'SameNameTypeWillError'.= { SameNameTypeWillError message : string;};typeDuplicate identifier 'SameNameTypeWillError'.2300Duplicate identifier 'SameNameTypeWillError'.= { SameNameTypeWillError detail : string;};
Mặt khác, trong trường hợp interface, có thể định nghĩa interface cùng tên, và sẽ trở thành interface tổng hợp tất cả các định nghĩa cùng tên.
Tuy nhiên, nếu field cùng tên nhưng định nghĩa type khác nhau, sẽ xảy ra compile error.
tsinterfaceSameNameInterfaceIsAllowed {myField : string;sameNameSameTypeIsAllowed : number;sameNameDifferentTypeIsNotAllowed : string;}interfaceSameNameInterfaceIsAllowed {newField : string;sameNameSameTypeIsAllowed : number;}interfaceSameNameInterfaceIsAllowed {Subsequent property declarations must have the same type. Property 'sameNameDifferentTypeIsNotAllowed' must be of type 'string', but here has type 'number'.2717Subsequent property declarations must have the same type. Property 'sameNameDifferentTypeIsNotAllowed' must be of type 'string', but here has type 'number'.: number; sameNameDifferentTypeIsNotAllowed }
tsinterfaceSameNameInterfaceIsAllowed {myField : string;sameNameSameTypeIsAllowed : number;sameNameDifferentTypeIsNotAllowed : string;}interfaceSameNameInterfaceIsAllowed {newField : string;sameNameSameTypeIsAllowed : number;}interfaceSameNameInterfaceIsAllowed {Subsequent property declarations must have the same type. Property 'sameNameDifferentTypeIsNotAllowed' must be of type 'string', but here has type 'number'.2717Subsequent property declarations must have the same type. Property 'sameNameDifferentTypeIsNotAllowed' must be of type 'string', but here has type 'number'.: number; sameNameDifferentTypeIsNotAllowed }
Mapped Types
Mapped Types sẽ được giải thích chi tiết ở trang khác, ở đây chỉ giải thích có thể sử dụng với type alias hay interface.
📄️ Mapped Types
Với index type, bạn có thể tự do thiết lập bất kỳ key nào khi gán giá trị, nhưng khi truy cập phải kiểm tra undefined mỗi lần. Nếu format input đã được xác định rõ ràng, bạn có thể cân nhắc sử dụng Mapped Types.
Mapped Types là cơ chế cho phép chỉ định key của type một cách động, và chỉ có thể sử dụng với type alias.
Ví dụ sau tạo type mới với danh sách union type làm key.
typescripttypeSystemSupportLanguage = "en" | "fr" | "it" | "es";typeButterfly = {[key inSystemSupportLanguage ]: string;};
typescripttypeSystemSupportLanguage = "en" | "fr" | "it" | "es";typeButterfly = {[key inSystemSupportLanguage ]: string;};
Nếu sử dụng Mapped Types với interface sẽ xảy ra error.
typescripttypeSystemSupportLanguage = "en" | "fr" | "it" | "es";interfaceButterfly {[A mapped type may not declare properties or methods.7061A mapped type may not declare properties or methods.key inSystemSupportLanguage ]: string;}
typescripttypeSystemSupportLanguage = "en" | "fr" | "it" | "es";interfaceButterfly {[A mapped type may not declare properties or methods.7061A mapped type may not declare properties or methods.key inSystemSupportLanguage ]: string;}
Phân biệt sử dụng interface và type alias
Vậy khi thực tế định nghĩa type, nên sử dụng interface hay type alias? Rất tiếc, không có câu trả lời chính xác rõ ràng cho vấn đề này.
Cả interface và type alias đều có thể định nghĩa type, nhưng có những điểm khác nhau về khả năng mở rộng và khả năng sử dụng Mapped Types, vì vậy hãy cân nhắc những ưu nhược điểm này để quyết định quy tắc trong dự án và tuân thủ nó.
Làm ví dụ tham khảo, trong mục Type Aliases vs Interfaces của style guide TypeScript mà Google công khai, khuyến nghị sử dụng type alias khi định nghĩa type cho primitive value, union type hoặc tuple, và sử dụng interface khi định nghĩa type cho object.
Nếu việc phân biệt sử dụng interface và type alias gây khó khăn và làm chậm tốc độ phát triển, cũng có cách nghĩ là thống nhất viết bằng type alias.
Ví dụ sử dụng interface
Khi tạo library mà cấu trúc của type được định nghĩa phụ thuộc vào phía application, việc sử dụng interface là phù hợp.
Type định nghĩa của process.env trong Node.js được implement trong @types/node/process.d.ts như sau.
tsdeclare module "process" {global {namespaceNodeJS {interfaceProcessEnv extendsDict <string> {TZ ?: string;}}}}
tsdeclare module "process" {global {namespaceNodeJS {interfaceProcessEnv extendsDict <string> {TZ ?: string;}}}}
Vì được định nghĩa bằng interface, phía sử dụng package có thể tự do mở rộng type.
Nếu ProcessEnv được định nghĩa bằng type alias, sẽ không thể mở rộng type và trở nên rất khó phát triển. Như vậy, khi nhiều user không xác định tham chiếu type, hãy định nghĩa type bằng interface để cân nhắc khả năng mở rộng.
ts// src/types/global.d.tsdeclare module "process" {global {namespaceNodeJS {interfaceProcessEnv {NODE_ENV : "development" | "production";}}}}
ts// src/types/global.d.tsdeclare module "process" {global {namespaceNodeJS {interfaceProcessEnv {NODE_ENV : "development" | "production";}}}}
Thông tin liên quan
📄️ Interface
Interface là kiểu định nghĩa field và method mà class cần implement. Class implement interface để có thể kiểm tra xem có tuân theo tên method và kiểu tham số mà interface yêu cầu hay không.
📄️ Type alias
TypeScript cho phép đặt tên cho kiểu. Kiểu có tên được gọi là type alias.