В чем разница между «объявить класс» и «интерфейс» в TypeScript?

116

Что предпочтительнее в TypeScript при создании файлов декларации источника .d.ts и почему?

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

или

interface Example {
    Method(): void;
}

Различия, которые я могу сказать, заключаются в том, что интерфейсы не могут иметь статических методов, поэтому вы должны использовать для этого класс. Оба не производят никакого JS-вывода, так что, возможно, это не имеет значения?

Крис
источник
См. Stackoverflow.com/a/14323673/1704166
Райан Кавано,
Я не думаю, что какой-либо из них действительно помогает описать, что вы использовали бы один над другим, поскольку они оба могут выполнять одно и то же без вывода JS.
Крис
1
Кратко: с помощью declare вы должны убедиться, что реализация класса существует во время выполнения, а с интерфейсом вам не нужно.
Hakim

Ответы:

163

interfaceпредназначен для случаев, когда вы просто хотите описать форму объекта. Для интерфейсов никогда не существует генерации кода - они всего лишь артефакт в системе типов. Вы не увидите разницы в генерации кода для класса в зависимости от того, есть ли у него implementsпредложение.

declare classпредназначен для случаев, когда вы хотите описать существующий класс (обычно класс TypeScript, но не всегда), который будет присутствовать извне (например, у вас есть два файла .ts, которые компилируются в два файла .js, и оба включаются с помощью scriptтегов на веб-странице). Если вы наследуете от classusing extends(независимо от того, был ли базовый тип a declare classили обычным class), компилятор сгенерирует весь код для подключения цепочки прототипов и конструкторов пересылки, а что нет.

Если вы попытаетесь наследовать от, declare classкоторый должен был быть интерфейсом, у вас будет ошибка времени выполнения, потому что этот сгенерированный код будет ссылаться на объект без проявления времени выполнения.

И наоборот, если вы просто implementинтерфейс, который должен был быть declare class, вам придется заново реализовать все члены самостоятельно и не будете использовать преимущества какого-либо повторного использования кода из потенциального базового класса, а функции которые проверяют цепочку прототипов во время выполнения, отклонят ваш объект как не являющийся экземпляром базового класса.

Для того, чтобы получить действительно всезнайка, если у вас есть C ++ фона, вы можете примерно думать , interfaceкак typedefи в declare classкачестве externдекларации конструктора , который строго отсутствует определение в этой компиляции единицы.

С точки зрения чистого потребления (написание императивного кода, а не добавление новых типов) единственная разница между interfaceи declare classзаключается в том, что вы не можете создавать newинтерфейс. Тем не менее, если вы собираетесь extend/ implementодин из этих типов в новом class, Вы абсолютно должны быть выбраны правильно между interfaceи declare class. Только один из них будет работать.

Два правила, которые вам пригодятся:

  • Согласуется ли имя типа с функцией-конструктором (чем-то вызываемым new), которая действительно присутствует во время выполнения (например, Dateесть, но JQueryStaticнет)? Если нет , то точно хочешьinterface
  • Имею ли я дело со скомпилированным классом из другого файла TypeScript или с чем-то достаточно похожим? Если да , используйтеdeclare class
Райан Кавано
источник
На самом деле вы можете создать новый интерфейс в машинописном тексте. Единственное ограничение - это наследование.
Олег Михайлик
3
Вы не можете вызвать newоператор для типа интерфейса. Однако интерфейсы могут иметь конструктивные сигнатуры, что означает, что вы можете вызывать newоператор для значения типа интерфейса. Это сильно отличается от того class, как работает, когда подпись конструкции находится в самом имени типа, а не в выражении этого типа.
Райан Кавано
Если вы выберете путь добавления конструктора к интерфейсу, он должен быть ЕДИНСТВЕННЫМ членом интерфейса, за исключением «статики» класса. Не совмещайте интерфейс функции-конструктора с интерфейсом созданного объекта. Если вы это сделаете, система типов допускает такие глупости, как: new (new x ()), где x: Интерфейс.
Джереми Белл
24

Вы можете реализовать интерфейс:

class MyClass implements Example {
    Method() {

    }
}

В то время как declare classсинтаксис действительно предназначен для использования для добавления определений типов для внешнего кода, который не написан на TypeScript, поэтому реализация находится «в другом месте».

Фентон
источник
Итак, вы предлагаете использовать класс declare для описания кода, написанного не на TypeScript? Я бы предположил, что это так, но в зарегистрированном файле jquery.d.ts JQueryStatic - это интерфейс, реализованный с помощью: declare var $: JQueryStatic. Я бы хотел, чтобы это было объявлено классом $ {public static ...}
Крис
Единственная причина, по которой я могу это придумать, - это если вы не хотите, чтобы люди расширяли класс - использование интерфейса означает, что вам придется предоставить всю реализацию.
Fenton
Имеет смысл. Возможно, в этом и была причина.
Крис
Итак, я пытаюсь указать объявления в другой библиотеке JS, поэтому мне определенно требуется объявление. Код использует статику для некоторых библиотечных функций (классов) - пока я еще не видел статического свойства или метода, выраженного в файле объявления. О, а следует ли использовать пространство имен или модуль?
jenson-button-event
13

С точки зрения непрофессионала, declareиспользуется в .ts/ d.tsfiles, чтобы сообщить компилятору, что мы должны ожидать, что ключевое слово, которое мы будем declaringсуществовать в этой среде, даже если оно не определено в текущем файле. Это затем позволит нам обеспечить безопасность типов при использовании объявленного объекта, поскольку компилятор Typescript теперь знает, что какой-то другой компонент может предоставить эту переменную.

Никк Вонг
источник
6

Разница между declareи interfaceв TS:

заявляет:

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

В приведенном выше коде declareкомпилятор TS знает, что где-то Exampleобъявлен класс . Это не значит, что класс включен волшебным образом. Вы как программист несете ответственность за доступность класса при его объявлении (с declareключевым словом).

интерфейс:

interface Example {
    Method(): void;
}

An interface- это виртуальная конструкция, которая существует только в машинописном тексте. Компилятор машинописного текста использует его исключительно для проверки типов. Когда код компилируется в javascript, вся эта конструкция будет удалена. Компилятор машинописного текста использует интерфейсы для проверки правильности структуры объектов.

Например, когда у нас есть следующий интерфейс:

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

Объекты, которые мы определяем с этим типом интерфейса, должны точно соответствовать интерфейсу:

// perfect match has all the properties with the right types, TS compiler will not complain.
  const obj1: test = {   
    foo: 5,
    bar: 'hey',
  }
Виллем ван дер Вин
источник