Создание экземпляра интерфейса typeScript/универсального

В следующем примере создается экземпляр интерфейса EventEmitter, однако я считаю, что интерфейсы не могут быть созданы, а классы.

@Output() notify: EventEmitter<string> = new EventEmitter<string>();

Полный код ниже:

import { Component, EventEmitter, Output } from '@angular/core';

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styles: [
  ]
})
export class ChildComponent {

  @Output() sharedData: EventEmitter<string> = new EventEmitter<string>;

  shareData() {
    this.sharedData.emit('Hello, world!!!');
  }

}

Действительно, интерфейсы не могут быть созданы. Но EventEmitter — это класс, а не интерфейс.

kellermat 27.12.2022 12:18

@kellermat на самом деле VS CODE говорит, что это интерфейс, когда вы наводите на него указатель мыши, и показывает всплывающую подсказку: (псевдоним) интерфейс EventEmitter<T> import EventEmitter

Saad Ali 27.12.2022 12:21

Я знаю, что вы имеете в виду, и я тоже немного сбит с толку. На самом деле мой источник был: angular.io/api/core/EventEmitter

kellermat 27.12.2022 12:29

Когда вы пишете new XXX, вы имеете в виду значение с именем XXX, а не тип с именем XXX, даже если такой тип может быть в области видимости. Таким образом, EventEmitter предположительно является именем как значения конструктора, так и типа интерфейса. Подобные вещи происходят автоматически с объявлениями class (class Foo {} создает как значение с именем Foo, так и тип с именем Foo), но это можно сделать отдельно (например, interface Bar {}, а затем declare const Bar: new () => Bar).

jcalz 27.12.2022 17:30

Не могли бы вы отредактировать , чтобы предоставить минимальный воспроизводимый пример, демонстрирующий вашу проблему в автономной среде IDE, включая любые import утверждения? Если вы сможете это сделать, я смогу точно определить, где находятся два определения EventEmitter и почему вещи ведут себя так, как они себя ведут. Однако сейчас все, что у меня есть, это то, что EventEmitter не определено. Если вы сделаете такое редактирование и хотите, чтобы я взглянул еще раз, пожалуйста, упомяните @jcalz в комментарии, чтобы уведомить меня.

jcalz 27.12.2022 17:32

@jcalz спасибо за ваше время. Я обновил вопрос и поставил полный код. Это простой вложенный компонент, который генерирует событие.

Saad Ali 29.12.2022 14:25
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
1
6
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В дальнейшем важно понимать разницу в TypeScript между типами, которые существуют только во время компиляции и стираются при компиляции кода в JavaScript; и значения, которые передаются в JavaScript и, следовательно, существуют во время выполнения. Значение может иметь тип, но это не одно и то же, и они существуют в разных синтаксических контекстах в коде TypeScript. В чем-то вроде

const foo: Bar = baz<Qux>();

Bar и Qux — типы, а foo и baz — значения. Когда этот код скомпилируется в JavaScript, он, вероятно, станет

const foo = baz();

Поскольку они существуют в разных синтаксических контекстах, вы можете фактически иметь типы и значения, использующие одни и те же имена, не конфликтуя друг с другом. Итак, вы могли бы:

const A: B = B<A>();

где первый B и второй A — типы, а первый A и второй B — значения. Между значением с именем A и типом с именем A нет необходимой связи. Приведенный выше код в точности аналогичен const foo: Bar = baz<Qux>();, просто вещи переименованы. Это может сбить с толку человека, но компилятор не запутается и знает из контекста синтаксиса, какие из них являются значениями, а какие являются типами. Вышеприведенное будет скомпилировано в

const A = B();

где значения остаются, а типы исчезают.


Для вашего примера кода соответствующие определения находятся в event_emitter.ts Angular. Если вы наведете курсор на импортированный EventEmitter в IDE с поддержкой IntelliSense, например Площадка TS, вы должны увидеть что-то вроде этого:

/* 
(alias) interface EventEmitter<T> 
(alias) const EventEmitter: { 
    new (isAsync?: boolean | undefined): EventEmitter<any>;
    new <T>(isAsync?: boolean | undefined): EventEmitter<T>;
    readonly prototype: EventEmitter<any>;
}
*/

Это означает, что есть две отдельные вещи с именами EventEmitter. Первый — это interface EventEmitter<T>, названный общий тип. Если бы это было единственным объявлением EventEmitter, вы не смогли бы создать его экземпляр. Второй — const EventEmitter, именованное значение, тип которого содержит некоторые сигнатуры конструкций, одна из которых является универсальной. Если вы вызываете сигнатуру универсальной конструкции, возвращаемое значение имеет тип EventEmitter<T>;

Итак, в строке

@Output() sharedData: EventEmitter<string> = new EventEmitter<string>;
//                      ^                                  ^
// (alias) interface EventEmitter<T>                       |
// (alias) new EventEmitter<string>(isAsync?: boolean | undefined): EventEmitter<string>

первый EventEmitter<string> является универсальным типом интерфейса, созданным с помощью string, а последний EventEmitter<string> является частью вызова универсальной функции-конструктора.


Обратите внимание, что когда у вас есть объявление класса , оно вводит как именованный тип (тип экземпляра класса), так и именованное значение (значение конструктора класса) с тем же именем. Это полезно, но создает ошибочное впечатление, что типы и значения с общими именами неразрывно связаны друг с другом.

В любом случае, отношение именованного значения и типа объявления class по сути такое же, как отношение с EventEmitter в Angular. В самом деле, они могли бы предоставить по существу те же типы с чем-то вроде:

declare class EventEmitter<T = any> {
    constructor(isAsync?: boolean | undefined);
    new(isAsync?: boolean): EventEmitter<T>;
    emit(value?: T): void;
    subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void):
        Subscription;
    subscribe(observerOrNext?: any, error?: any, complete?: any): Subscription;
}

И тогда код вроде

const xxx: EventEmitter<string> = new EventEmitter<string>;
//           ^                               ^
//        class EventEmitter<T = any>        |
// constructor EventEmitter<string>(isAsync?: boolean | undefined): EventEmitter<string>

будет делать то же самое, за исключением того, что IntelliSense будет использовать class и constructor для ссылки на тип и значение вместо interface и new.

Затем вы можете спросить, если это одно и то же, почему они не использовали объявление class вместо пары interface/const? Это хороший вопрос, но, вероятно, не в тему. В общем, есть вещи, которые вы не можете адекватно описать с помощью объявления class, и в этих случаях была бы необходима пара тип/значение, так что, возможно, это было сделано для большей гибкости.

Площадка ссылка на код

Другие вопросы по теме