Menu

extends trong TypeScript: mở rộng interface hoặc type

Từ khóa extends xây dựng một kiểu từ một kiểu khác. Tìm hiểu cách mở rộng một interface (từ một hoặc nhiều interface), mở rộng type alias bằng &, ghi đè kiểu của thuộc tính, thay thế thuộc tính bằng Omit, và ý nghĩa của extends trong lớp, ràng buộc generic và conditional type.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

interface Child extends Parent tạo một interface mới có mọi thành viên của Parent cộng với thành viên của riêng nó. Giá trị có kiểu con dùng được ở bất cứ đâu cần kiểu cha, vì chúng có đủ mọi thuộc tính của kiểu cha.

Cùng một từ khóa này xuất hiện ở bốn chỗ trong TypeScript, với ý nghĩa liên quan nhưng khác nhau:

Ở đâuVí dụÝ nghĩa
Interfaceinterface Dog extends Animal {}kế thừa các thành viên của interface cha
Lớpclass Dog extends Animal {}kế thừa các trường và phần cài đặt phương thức (JavaScript lúc chạy)
Tham số generic<T extends Animal>ràng buộc: đối số kiểu phải gán được cho Animal
Conditional typeT extends string ? A : Bphép thử: T có gán được cho string không?

Mở rộng nhiều interface

Liệt kê nhiều interface cha sau extends, ngăn cách bằng dấu phẩy. Kết quả có thành viên của tất cả các interface đó:

interface Identified {
  id: number;
}

interface Timestamped {
  createdAt: Date;
  updatedAt: Date;
}

interface Post extends Identified, Timestamped {
  title: string;
}

// Post requires id, createdAt, updatedAt and title

Nếu hai interface cha khai báo cùng một thuộc tính với các kiểu không giống hệt nhau, interface con gây lỗi TS2320 (Interface 'Post' cannot simultaneously extend types 'A' and 'B'), trừ khi interface con khai báo lại thuộc tính đó với một kiểu gán được cho cả hai.

Mở rộng một type alias

Type alias type không có mệnh đề extends. Bạn xây dựng tiếp từ nó bằng phép giao &, kết hợp thành viên của cả hai vế. Một interface cũng có thể mở rộng trực tiếp một type alias kiểu đối tượng, và một type alias có thể giao với một interface: hai loại này kết hợp với nhau thoải mái.

Hai cách này hoạt động như nhau cho đến khi các thuộc tính xung đột: extends báo thuộc tính xung đột ngay tại khai báo, còn & âm thầm biến nó thành never. Phần so sánh nằm ở trang interface và type, còn chi tiết về & nằm ở trang intersection type.

Ghi đè kiểu của thuộc tính

Một interface con có thể khai báo lại một thuộc tính của interface cha, nhưng chỉ với một kiểu gán được cho kiểu của cha. Thu hẹp string thành một literal, hoặc bỏ null khỏi một union, là được phép. Đổi sang một kiểu không liên quan thì không:

index.ts(13,11): error TS2430: Interface 'Label' incorrectly extends interface 'Shape'.
  Types of property 'kind' are incompatible.
    Type 'number' is not assignable to type 'string'.

Quy tắc này tồn tại để một Label vẫn dùng được ở mọi nơi cần Shape. Nếu kind có thể là số, code viết cho Shape gọi shape.kind.toUpperCase() sẽ bị hỏng.

Thay thế thuộc tính bằng Omit

Khi thật sự cần một kiểu khác cho thuộc tính được kế thừa, hãy loại nó khỏi interface cha bằng Omit trước, rồi khai báo thuộc tính mới. Một trường hợp phổ biến là chuyển đổi dữ liệu từ API, nơi id và ngày tháng được gửi về dưới dạng chuỗi:

Omit<ApiUser, "id" | "createdAt"> là { name: string }, nên id và createdAt mới không xung đột với thứ gì. Pick, Partial và các utility type khác cũng mở rộng được theo cùng cách.

extends với lớp

class Dog extends Animal là kế thừa lớp (class) của JavaScript, và khác với extends của interface, nó tồn tại lúc chạy: Dog nhận hàm khởi tạo, các trường và phần cài đặt phương thức của Animal, và new Dog(...) instanceof Animal là true. TypeScript bổ sung thêm phép kiểm tra kiểu, như từ khóa override:

Một lớp chỉ extends được một lớp, nhưng có thể implements bao nhiêu interface cũng được. implements không sao chép gì: nó chỉ kiểm tra rằng lớp khai báo đủ những gì interface yêu cầu. super, thành viên protected và override được trình bày trong kế thừa.

extends trong generic và conditional type

Bên trong dấu ngoặc nhọn, extends là một ràng buộc. <T extends { length: number }> nhận mọi đối số kiểu có length kiểu số, và cho phép thân hàm dùng .length. Trong conditional type, T extends U ? X : Y hỏi xem T có gán được cho U không.

Ở cả hai chỗ, extends nghĩa là "gán được cho", cũng chính là quan hệ mà dạng interface kiểm tra. Ràng buộc được trình bày kỹ trong ràng buộc generic, còn các phép thử ở mức kiểu nằm trong conditional type.

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

Làm sao để mở rộng một interface trong TypeScript?

Viết interface Child extends Parent { ... }. Interface con có mọi thuộc tính của interface cha cộng với những thuộc tính nó tự khai báo. Để mở rộng nhiều interface cùng lúc, ngăn cách chúng bằng dấu phẩy: interface C extends A, B { ... }.

Làm sao để mở rộng một type alias trong TypeScript?

Type alias không dùng được extends; thay vào đó hãy kết hợp các kiểu bằng phép giao (intersection): type Admin = User & { permissions: string[] }. Một interface cũng có thể mở rộng trực tiếp một type alias kiểu đối tượng: interface Admin extends User { permissions: string[] }.

Có thể ghi đè kiểu của thuộc tính khi mở rộng interface không?

Chỉ với một kiểu tương thích và hẹp hơn: kind: "circle" có thể thay cho kind: string. Một kiểu khác hẳn, như number cho một thuộc tính string, gây lỗi TS2430 (Interface 'X' incorrectly extends interface 'Y'). Để thay một thuộc tính bằng kiểu không liên quan, hãy loại nó ra trước: interface User extends Omit<ApiUser, "id"> { id: number }.

extends và implements khác nhau thế nào?

extends kế thừa: interface nhận các thành viên của interface cha, còn lớp nhận các trường và phần cài đặt phương thức của lớp cha. implements chỉ kiểm tra: một lớp implement một interface phải tự khai báo mọi thành viên, và không nhận được gì từ interface đó.

extends trong generic như <T extends string> có nghĩa là gì?

Đó là một ràng buộc, không phải kế thừa. T extends string nghĩa là đối số kiểu phải gán được cho string, nên bên trong hàm bạn dùng được các phương thức chuỗi trên T. Trong conditional type, T extends U ? X : Y là một phép thử: nếu T gán được cho U thì kết quả là X, ngược lại là Y.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu