Typescript: Ermöglichen Sie einem Modul, eine Schnittstelle zu implementieren

Erstellt am 10. Aug. 2014  ·  34Kommentare  ·  Quelle: microsoft/TypeScript

Dies ist nützlich, wenn ein Modul eine Schnittstelle mit dem Schlüsselwort implements implementieren kann. Syntax: module MyModule implements MyInterface { ... } .

Beispiel:

interface Showable {
    show(): void;
}
function addShowable(showable: Showable) {

}

// This works:
module Login {
    export function show() {
        document.getElementById('login').style.display = 'block';
    }
}
addShowable(Login);

// This doesn't work (yet?)
module Menu implements Showable {
    export function show() {
        document.getElementById('menu').style.display = 'block';
    }
}
addShowable(Menu);
Suggestion help wanted

Hilfreichster Kommentar

Ein Anwendungsfall wären Frameworks und Tools, die beim Start der Anwendung ein Verzeichnis nach Modulen durchsuchen und erwarten, dass alle diese Module eine bestimmte Form exportieren.

Beispielsweise durchsucht Next.js ./pages/**/*.{ts,tsx} nach Ihren Seitenmodulen und generiert Routen basierend auf Ihren Dateinamen. Es liegt an Ihnen, sicherzustellen, dass jedes Modul die richtigen Dinge exportiert (ein NextPage als Standardexport und ein optionaler PageConfig Export mit dem Namen config ):

import { NextPage, PageConfig } from 'next'

interface Props { userAgent?: string }

const Home: NextPage<Props> = ({ userAgent }) => (<main>...</main>)

Page.getInitialProps = async ({ req }) => {
  const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
  return { userAgent }
}

export default Page

export const config: PageConfig = {
  api: { bodyParser: false }
}

Es wäre schön, wenn Sie stattdessen die Exportform des gesamten Moduls in einer Zeile oben deklarieren könnten, z. B. implements NextPageModule<Props> .

Ein anderer Gedanke: Es wäre interessant, wenn es eine Möglichkeit gäbe, in einer TypeScript-Konfiguration anzugeben, dass alle Dateien, die einem bestimmten Muster entsprechen (wie ./pages/**/*.{ts,tsx} ), eine bestimmte Exportform implementieren müssen, damit ein Modul seinen Exporttyp haben kann. Nur überprüft, weil es sich zum Beispiel im Verzeichnis pages . Ich bin mir jedoch nicht sicher, ob es einen Präzedenzfall für diesen Ansatz gibt, und es könnte verwirrend werden.

Alle 34 Kommentare

Wie würde das mit externen Modulen funktionieren? Sobald die Leute es für interne Zwecke verwenden können, möchten sie es wahrscheinlich auch für externe Zwecke verwenden.

Das ist eine gute Frage. Ich weiß nicht, welche Syntax die beste wäre, aber hier einige Vorschläge:

implements Showable; // I would prefer this one.
module implements Showable;
export implements Showable;

Dies sollte nur für externe Module zulässig sein, die keine Exportzuweisung verwenden. Wenn Sie eine Exportzuweisung verwenden, kann das zu exportierende Objekt bereits an einer anderen Stelle ein implements haben.

Genehmigt. Wir bevorzugen die Syntax

export implements Showable;

und stimmte zu, dass dies für Zuweisungen von Dateien export = erforderlich ist.

Noch ein paar Fragen:

  • Da wir zulassen, dass Module Typen an Deklarationssites haben, sollten wir ihnen nicht erlauben, Typen auch an Verwendungssites zu haben. z.B:
declare module "Module" implements Interface { }

import i : Interface = require("Module");
  • Was machen Sie mit zusammengeführten Deklarationen? Sollten Sie die Schnittstelle für das Aggregat aller Deklarationen erzwingen? und was passiert, wenn sie in der Sichtbarkeit nicht übereinstimmen?
    z.B:
module Foo {
    export interface IBar {
        (a:string): void;
    }

    export module Bar implements IBar {  // should this be an error?
        export interface Interface {}
    }    

    function Bar(a: string) : void { }  // not exported
}

var bar: Foo.IBar = Foo.Bar;

Dies sollte für externe Umgebungsmodule zulässig sein. Für diese Module sollten meiner Meinung nach zwei Syntaxen zulässig sein:

declare module "first" implements Foo { }
declare module "second"  {
  interface Bar { }
  export implements Bar; // this syntax is necessary, with the first syntax you can't reference Bar.  
}

Oder sollte Bar in einer Implementierungsklausel vor dem Öffnen von { im Geltungsbereich stehen?

Das Hinzufügen von Typinformationen zu einer Importanweisung ist meiner Meinung nach nicht wirklich nützlich, da Sie die Typinformationen zum Modul selbst hinzufügen können.

Und für zusammengeführte Deklarationen würde ich sagen, dass der Modulblock, der die implementierungsklausel enthält, die Schnittstelle implementieren sollte. Dies verhindert auch Probleme mit der Sichtbarkeit.

Wie würde das mit # 2159 zusammenhängen? Ein Namespace implementiert eine Schnittstelle?

@jbondc Wenn wir dies hätten, würde es auch für Namespaces gelten. Sie sollten sich interne Module und Namespaces als isomorph vorstellen.

Sind Sie sicher, dass Sie einen Implementierungspfad einschlagen möchten, in dem "Namespaces" Schnittstellen implementieren können?

Oh wow, das ist schon eine ganze Weile genehmigt worden. @RyanCavanaugh , @DanielRosenwasser , @mhegazy Wenn Sie keine zweiten Gedanken oder

Ich ziehe meine bisherige Skepsis zurück und bin tatsächlich auf die neuen strukturellen Möglichkeiten gestoßen, die sich daraus ergeben würden.

In diesem Sinne sollten Sie in Betracht ziehen, die Schnittstelle des Aggregats der Schnittstelle anstelle nur des Blocks zu erzwingen, der die Implementierung deklariert. - Die Art der Namespaces / Module muss verteilt sein und viele nicht triviale Komponenten enthalten. Ich würde dies gerne verwenden können, aber ich möchte auf keinen Fall meinen gesamten Namespace / Modul in derselben Datei definieren. Warum nicht einfach eine Klasse in diesem Fall verwenden?

@ Elephant-Vessel Ich bin mir nicht sicher, ob es sich um Module, Namespaces, Pakete, Funktionen oder ...

@aluanhaddad Was meinst du?

Ich meine, dass zu der Zeit, als diese Diskussion begann, das Modul nicht bedeutete, was es heute bedeutet. Wir verwenden jetzt den Begriff Namespace, um auf das zu verweisen, was im OP als Modul beschrieben wird, während das Modul eine genauere und inkompatiblere Bedeutung angenommen hat. Wenn Sie also über mehrere Dateien sprechen, die an dieser Implementierung teilnehmen, beziehen Sie sich auf Namespaces oder Module?

Ich beziehe mich auf Namespaces. Ich glaube, ich wollte mich nur an die Geschichte dieses Threads anpassen, tut mir leid, dass ich mich nicht losgerissen habe :) Oder wenn ich daran denke, hatte ich vielleicht den Oberbegriff 'Modul' im Kopf, der eine übergeordnete Einheit beschreibt, die aus einem Satz besteht von Unterkomponenten, die zusammengesetzt sind, um bestimmte Funktionen auf hoher Ebene in einem System bereitzustellen. Aber ich kann nur mit 'Namespaces' arbeiten.

Daher möchte ich in der Lage sein, Einschränkungen und Erwartungen für [_generische Module_] zu beschreiben und zu setzen, die andere [_generische Module_] oder Klassen enthalten können, und dabei die Namespaces des strukturellen Konzepts in Typoskript nutzen.

Ich hoffe, dass wir übergeordnete strukturelle Erwartungen in einem System besser ausdrücken können. Klassen lassen sich nicht gut skalieren, sie eignen sich gut als atomare Komponenten in einem System, aber ich denke nicht, dass eine übergeordnete Organisationsstruktur in einem System sich gut mit Klassen ausdrücken lässt, da sie so konzipiert sind, dass sie instanziiert und vererbt werden und ähnliches . Es ist einfach zu aufgebläht.

Ich würde eine einfache und saubere Möglichkeit schätzen, die Struktur höherer Ordnung des Systems zu beschreiben, ohne viel Aufhebens. Vorzugsweise mit dem einzigen Aufwand sind optionale Richtungssichtbarkeitsbeschränkungen. Als ob es unmöglich wäre, _MySystem.ClientApplication_ von _MySystem.Infrastructure_ zu referenzieren, aber umgekehrt. Dann würden wir anfangen, an einen aufregenden Ort zu gehen.

@ Elephant-Vessel danke für die Klarstellung. Ich bin damit einverstanden, dass dies äußerst wertvoll wäre und dass Klassentypen hier nicht der richtige Ansatz sind. Ich denke, Sie haben den Nagel auf den Kopf getroffen, wenn Sie über Instanziierung sprechen, weil Namespaces Dinge darstellen, die auf Bibliotheksebene konzeptionell Singletons sind. Obwohl dies nicht erzwungen werden kann, wäre es konzeptionell nützlich, etwas zu haben, das nicht mehrere Instanziierungen impliziert.

Ich stimme @ Elephant-Vessel zu. Während es leicht ist, TypeScript mit einem anderen Java zu verwechseln, bei dem alle Einschränkungen mit einer einzigen Klassenstruktur ausgedrückt werden, verfügt TS über ein viel umfassenderes "Shape" -Konzept, das sehr leistungsfähig ist und semantischen Kontortonismus eliminiert. Leider zwingt die Unfähigkeit, das Modul einzuschränken, Entwickler dazu, für Dinge, die viel besser als Modul ausgedrückt werden könnten, in ein Klassenmuster zurückzukehren.

Zum Beispiel wäre es für Unit-Tests sehr hilfreich, einige "Formen" (dh Einschränkungen) für Module ausdrücken zu können, damit wir eine alternative Implementierung für einen bestimmten laufenden Kontext bereitstellen können. Nun scheint es die einzige Möglichkeit zu sein, dies auf strukturierte / überprüfte Weise zu tun, indem Sie zu klassenbasiertem DI (als la Spring) zurückkehren und alles zu einer Klasse machen (und daher instanziierbar machen).

Wie auch immer, ich paraphrasiere @ Elephant-Vessel, aber wenn ich einen einzigen Wunsch für TS habe, wäre es dieser.

Irgendein Wort zu diesem Vogel? Ich habe auch dieses Problem

Soooo, ähm, wäre es nicht ein einfacher Fall von:

export {} as IFooBar;

Was ist falsch an dieser Syntax? Ich denke, die Syntax wurde bereits genehmigt, vielleicht als

export implements IFooBar

trotzdem freue ich mich darauf

Ist das schon immatrikuliert / gelandet? Dies wird eine coole Funktion sein

Wie können wir das vorantreiben? Es ist unglaublich mächtig. Gerne helfen wir Ihnen weiter!

Irgendein Wurm auf dieser Birke? Eine Frage, die ich momentan habe, ist, wie ich eine Schnittstelle für den Standardexport deklarieren kann. Zum Beispiel:

export default {}

Ich nehme an, ich kann einfach tun:

const x: MyInterface = {}
export default x;

Das würde für die meisten TS-Dateien funktionieren. Das Problem dabei ist jedoch, dass dies nicht so gut funktioniert, wenn Sie zuerst für JS codieren und später auf TS umsteigen möchten.

Eine andere Sache, an die ich dachte, was ist mit Namespaces, die implementiert werden? Etwas wie:

export namespace Foo implements Bar {

}

Ich denke, Bar wäre ein abstrakter Namespace lol idk

Ich habe gesehen, wie diese Frage so oft auftauchte, und ich denke, wir alle suchen nur nach einer Sache:
Unterstützt statische Mitglieder in einer Schnittstelle.
In diesem Fall könnten Sie einfach eine Klasse mit statischen Elementen und einer Schnittstelle verwenden, was fast das gleiche ist, was Sie hier versuchen, oder?

In beiden Fällen ist es dringend erforderlich, Schnittstellen statische Unterstützung hinzuzufügen ODER Schnittstellenunterstützung für Module hinzuzufügen.

@shiapetel nah nicht so.

Wir können das schaffen:

export default <T>{
  foo: Foo,
  bar: Bar
}

aber das ist nicht das, wonach wir suchen. wir suchen speziell nach:

export const foo : Foo = {};
export const bar : Bar = {};

Derzeit gibt es jedoch keinen Mechanismus, um das Modul zum Exportieren von foo und bar zu erzwingen. Tatsächlich gibt es auch keinen Mechanismus, um zu erzwingen, dass das Modul den richtigen Standardwert exportiert.

Wenn Schnittstellen statische Mitglieder unterstützen, können Sie eine Klasse mit statischem foo / bar verwenden, die geerbt hat von:
Schnittstelle ILoveFooBar {
statisches foo: FooType;
statische Leiste: BarType;
}}

Recht?
Das habe ich gemeint, ich denke, es würde in Ihrer Situation helfen - ich weiß, es würde definitiv in meiner helfen.

@shaipetel statische Mitglieder von Schnittstellen könnten definitiv nützlich sein, aber vielleicht nicht für diesen Anwendungsfall.

Wartet dieses Problem nur darauf, dass jemand die Implementierung versucht?

Ein Anwendungsfall wären Frameworks und Tools, die beim Start der Anwendung ein Verzeichnis nach Modulen durchsuchen und erwarten, dass alle diese Module eine bestimmte Form exportieren.

Beispielsweise durchsucht Next.js ./pages/**/*.{ts,tsx} nach Ihren Seitenmodulen und generiert Routen basierend auf Ihren Dateinamen. Es liegt an Ihnen, sicherzustellen, dass jedes Modul die richtigen Dinge exportiert (ein NextPage als Standardexport und ein optionaler PageConfig Export mit dem Namen config ):

import { NextPage, PageConfig } from 'next'

interface Props { userAgent?: string }

const Home: NextPage<Props> = ({ userAgent }) => (<main>...</main>)

Page.getInitialProps = async ({ req }) => {
  const userAgent = req ? req.headers['user-agent'] : navigator.userAgent
  return { userAgent }
}

export default Page

export const config: PageConfig = {
  api: { bodyParser: false }
}

Es wäre schön, wenn Sie stattdessen die Exportform des gesamten Moduls in einer Zeile oben deklarieren könnten, z. B. implements NextPageModule<Props> .

Ein anderer Gedanke: Es wäre interessant, wenn es eine Möglichkeit gäbe, in einer TypeScript-Konfiguration anzugeben, dass alle Dateien, die einem bestimmten Muster entsprechen (wie ./pages/**/*.{ts,tsx} ), eine bestimmte Exportform implementieren müssen, damit ein Modul seinen Exporttyp haben kann. Nur überprüft, weil es sich zum Beispiel im Verzeichnis pages . Ich bin mir jedoch nicht sicher, ob es einen Präzedenzfall für diesen Ansatz gibt, und es könnte verwirrend werden.

Ich bin oft versucht, eine Singleton-Klasse zu erstellen, wenn ich nur ein einfaches Modul benötige, das eine Schnittstelle implementiert. Irgendwelche Tipps, wie man das am besten angeht?

@ RyanCavanaugh
Ich möchte an diesem Thema arbeiten. Können Sie mir bitte ein paar Tipps für die Lösung geben oder wo ich mich umschauen soll?

Wenn ich aus der Perspektive von 2020 darüber nachdenke, frage ich mich, ob wir anstelle von export implements Showable type wiederverwenden und export als Kennung zulassen? Heutzutage ist diese Syntax ungültig, sodass es unwahrscheinlich ist, dass jemand auf die vorhandene Codebasis zugreift.

Dann erhalten wir die Importsyntax:

// Can re-use the import syntax
type export = import("webpack").Config

Erklärungen sind dann einfach zu schreiben:

// Can use normal literals
type export = { test: () => string, description: string }

// Generics are easy
type export = (props: any) => React.SFC<MyCustomModule>

Es lohnt sich auch zu überlegen, was das JSDoc-Äquivalent auch sein sollte, vielleicht:

/** <strong i="17">@typedef</strong> {import ("webpack").Config} export */

Es gibt einige Anmerkungen in ^ - eine interessante Sache, die aus dem Meeting hervorging, war die Idee, dass wir ein allgemeineres Tool erstellen könnten, für das dies ein Anwendungsfall ist, und nicht das einzige, was es tut.

Wenn wir beispielsweise einen Typzusicherungsoperator für die Typkompatibilität hätten, könnte dieser sowohl für die Modulexporte als auch generisch verwendet werden, um zu überprüfen, ob die Typen Ihren Wünschen entsprechen. Zum Beispiel:

type assert is import("webpack").Config

const path = require('path');

export default {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  }
};

Wenn das Fehlen eines Ziels bedeutet, dass es auf der obersten Ebene angewendet wird. Dies kann verwendet werden, um eine kontextbezogene Eingabe bereitzustellen (z. B. würden Sie in export default { en| automatisch vervollständigt

Kann aber auch bei der Validierung Ihrer eigenen Typen hilfreich sein:

import {someFunction} from "./example"

type assert ReturnType<typeof someFunction> is string

Es lohnt sich auch zu überlegen, was das JSDoc-Äquivalent auch sein sollte, vielleicht:
js /** <strong i="7">@typedef</strong> {import ("webpack").Config} export */

Ich würde denken, @module wäre das JSDoc-Äquivalent. Der Anfang der Datei sollte haben:
js /** <strong i="12">@module</strong> {import("webpack").Config} moduleName */

Siehe: https://jsdoc.app/tags-module.html

Storybook v6 wurde in einen Ansatz geändert, der auf strukturierten Modulen basiert, die als Component Story Format bezeichnet werden . Es wird erwartet, dass alle .stories.js/ts -Module in einer Codebasis einen Standardexport vom Typ Meta .

Da die Möglichkeit, diese Erwartung nicht global auszudrücken, in Verbindung mit dem bestehenden Mangel bei der Eingabe von Standardexporten die Verwendung von Storybook v6 mit TypeScript wesentlich weniger reibungslos macht, als dies der Fall sein könnte.

Wenn Sie die Punkte von default mit einem bestimmten type , das ein module repliziert, zu Problemen beim Baumschütteln.

Webpack hat kein Problem damit, import * as Foo schütteln. Wenn Sie jedoch versuchen, dasselbe mit einem export default const = {} oder export default class ModuleName { mit allen statischen Mitgliedern zu tun, werden nicht verwendete Importe nicht entfernt.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen