Sự khác biệt giữa “lớp khai báo” và “giao diện” trong TypeScript


116

Trong TypeScript, khi tạo tệp khai báo nguồn .d.ts, tệp nào thích hợp hơn và tại sao?

declare class Example {
    public Method(): void; 
}

hoặc là

interface Example {
    Method(): void;
}

Sự khác biệt mà tôi có thể nói là các giao diện không thể có các phương thức tĩnh, vì vậy bạn phải sử dụng một lớp cho điều đó. Cả hai đều không sản xuất bất kỳ đầu ra JS nào, vì vậy có lẽ nó không quan trọng?



Tôi không cảm thấy rằng một trong hai điều này thực sự giúp mô tả bạn sẽ sử dụng cái khác vì cả hai đều có thể hoàn thành cùng một thứ mà không cần đầu ra JS.
Chris

1
Ngắn gọn: Với khai báo, bạn phải đảm bảo rằng việc triển khai lớp tồn tại trong thời gian chạy mà bạn không cần phải làm vậy với giao diện.
Hakim

Câu trả lời:


163

interfacelà khi bạn chỉ muốn mô tả hình dạng của một đối tượng. Không bao giờ có việc tạo mã cho các giao diện - chúng chỉ là một tạo tác trong hệ thống kiểu. Bạn sẽ không thấy sự khác biệt trong việc tạo mã cho một lớp tùy thuộc vào việc nó có implementsmệnh đề hay không .

declare classlà khi bạn muốn mô tả một lớp hiện có (thường là một lớp TypeScript, nhưng không phải luôn luôn) sẽ hiện diện bên ngoài (ví dụ: bạn có hai tệp .ts biên dịch thành hai tệp .js và cả hai đều được bao gồm qua scriptthẻ trong một trang web). Nếu bạn kế thừa từ classsử dụng extends(bất kể loại cơ sở là một declare classhay mộtclass ), trình biên dịch sẽ tạo ra tất cả mã để kết nối chuỗi nguyên mẫu và các hàm tạo chuyển tiếp và những gì không.

Nếu bạn cố gắng kế thừa từ một declare class giao diện lẽ ra phải là một giao diện, bạn sẽ gặp phải lỗi thời gian chạy vì mã được tạo đó sẽ tham chiếu đến một đối tượng không có biểu hiện thời gian chạy.

Ngược lại, nếu bạn chỉ đơn giản là implementmột giao diện mà lẽ ra phải là một declare class, bạn sẽ phải tự mình triển khai lại tất cả các thành viên và sẽ không tận dụng được bất kỳ mã sử dụng lại nào từ lớp cơ sở và các hàm việc kiểm tra chuỗi nguyên mẫu trong thời gian chạy sẽ từ chối đối tượng của bạn vì không thực sự là một thể hiện của lớp cơ sở.

Để trở nên thực sự mọt sách, nếu bạn có nền tảng C ++, bạn có thể nghĩ đại khái interfacenhư typedefdeclare classnhư một externkhai báo của một hàm tạo hoàn toàn thiếu định nghĩa trong đơn vị biên dịch này.

Từ khía cạnh tiêu dùng thuần túy (viết mã mệnh lệnh, không thêm các loại mới), sự khác biệt duy nhất giữa interfacedeclare classlà bạn không thể có newgiao diện. Tuy nhiên, nếu bạn có ý định extend/ implementmột trong những loại này trong một cái mới class, bạn hoàn toàn phải chọn đúng giữa interfacedeclare class . Chỉ một trong số chúng sẽ hoạt động.

Hai quy tắc sẽ phục vụ bạn tốt:

  • Tên của kiểu có phù hợp với một hàm khởi tạo (một cái gì đó bất khả xâm phạm với new) thực sự hiện diện trong thời gian chạy không (ví dụ: Datelà, nhưng JQueryStatickhông)? Nếu không , bạn chắc chắn muốninterface
  • Tôi đang xử lý một lớp đã biên dịch từ một tệp TypeScript khác hoặc một thứ gì đó đủ tương tự? Nếu , hãy sử dụngdeclare class

Bạn có thể mới một giao diện trong bảng chữ trên thực tế. Hạn chế duy nhất là tính kế thừa.
Oleg Mihailik

3
Bạn không thể gọi newtoán tử trên một kiểu giao diện. Tuy nhiên, các giao diện có thể có chữ ký cấu trúc, có nghĩa là bạn có thể gọi newtoán tử trên một giá trị của kiểu giao diện. Điều này rất khác với cách classhoạt động, trong đó chữ ký cấu trúc nằm trên chính tên kiểu chứ không phải trên biểu thức của kiểu đó.
Ryan Cavanaugh

Nếu bạn đi theo lộ trình thêm một phương thức khởi tạo vào một giao diện, thì nó phải là thành viên DUY NHẤT của giao diện, ngoại trừ 'tĩnh' của lớp. Không kết hợp giao diện của hàm tạo với giao diện của đối tượng được xây dựng. Nếu bạn làm vậy, hệ thống loại cho phép silliness như: new (mới x ()), trong đó x: Giao diện.
Jeremy Bell

24

Bạn có thể triển khai giao diện:

class MyClass implements Example {
    Method() {

    }
}

Trong khi declare classcú pháp thực sự được sử dụng để thêm định nghĩa kiểu cho mã bên ngoài không được viết bằng TypeScript - vì vậy việc triển khai là ở "nơi khác".


Vì vậy, bạn đang gợi ý rằng nên sử dụng lớp khai báo để mô tả mã không được viết bằng TypeScript? Tôi sẽ giả sử trường hợp đó, nhưng trong tệp jquery.d.ts đã đăng ký, JQueryStatic là một giao diện được triển khai bởi: statement var $: JQueryStatic Tôi sẽ có mặc dù đây là khai báo lớp $ {public static ...}
Chris

Lý do duy nhất tôi có thể nghĩ đến cho điều này là nếu bạn không muốn mọi người mở rộng lớp học - sử dụng giao diện có nghĩa là bạn sẽ phải cung cấp toàn bộ việc triển khai.
Fenton

Có ý nghĩa. Có lẽ đó là lý do.
Chris

Được rồi, vì vậy tôi đang thử trỏ đến các khai báo trong một thư viện JS khác nên chắc chắn tôi yêu cầu khai báo. Mã đang sử dụng static trên một số hàm (lớp) thư viện - cho đến nay tôi vẫn chưa thấy thuộc tính hoặc phương thức tĩnh được thể hiện trong tệp khai báo. Ồ, và tôi nên sử dụng không gian tên hay mô-đun?
jenson-button-event, 24/07/19

13

Theo thuật ngữ của giáo dân, declaređược sử dụng trong .ts/ d.tsfiles để nói với trình biên dịch rằng chúng ta nên mong đợi từ khóa mà chúng ta declaringtồn tại trong môi trường đó, ngay cả khi nó không được định nghĩa trong tệp hiện tại. Điều này sau đó sẽ cho phép chúng ta có được sự an toàn về kiểu khi sử dụng đối tượng đã khai báo, vì trình biên dịch Typecript bây giờ biết rằng một số thành phần khác có thể cung cấp biến đó.


6

Sự khác biệt giữa TS declareinterfaceTS:

khai báo:

declare class Example {
    public Method(): void; 
}

Trong đoạn mã trên declarecho phép trình biên dịch TS biết rằng ở đâu đó lớp Exampleđược khai báo. Điều này không có nghĩa là lớp được bao gồm một cách kỳ diệu. Bạn với tư cách là một lập trình viên có trách nhiệm có sẵn lớp khi bạn khai báo nó (với declaretừ khóa).

giao diện:

interface Example {
    Method(): void;
}

An interfacelà một cấu trúc ảo chỉ tồn tại trong bảng chữ. Trình biên dịch typecript sử dụng nó cho mục đích duy nhất là kiểm tra kiểu. Khi mã được biên dịch sang javascript, toàn bộ cấu trúc này sẽ bị loại bỏ. Trình biên dịch typecript sử dụng các giao diện để kiểm tra xem các đối tượng có cấu trúc phù hợp hay không.

Ví dụ khi chúng ta có giao diện sau:

interface test {
  foo: number,
  bar: string,
}

Các đối tượng mà chúng tôi xác định có kiểu giao diện này cần phải khớp chính xác với giao diện:

// perfect match has all the properties with the right types, TS compiler will not complain.
  const obj1: test = {   
    foo: 5,
    bar: 'hey',
  }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.