Language-tools: Eingabe von Svelte Component Props/Events/Slots

Erstellt am 11. Aug. 2020  ·  24Kommentare  ·  Quelle: sveltejs/language-tools

Es gibt bereits mehrere Probleme dazu (Nr. 424, Nr. 304, Nr. 273, Nr. 263), aber ich wollte eine konsolidierte machen, um eine Diskussion über die Ansätze zu führen, die wir wählen können.
Zugehörige PR: #437

Beachten Sie, dass es in diesem Problem NICHT um das Eingeben einer d.ts -Datei geht, dies wird später als separates Problem kommen.

Bezieht sich Ihre Feature-Anfrage auf ein Problem?
Momentan ist es unter bestimmten Umständen nicht möglich, die Ein-/Ausgänge einer Komponente zu typisieren:

  • Es ist nicht möglich, eine generische Beziehung zwischen Requisiten und Ereignissen/Slots (Generika) zu definieren.
  • Es ist nicht möglich, die Ereignisse und ihre Typen zu definieren
  • Es ist nicht möglich, die Slots und ihre Typen auf bestimmte Weise zu definieren

Beschreiben Sie die gewünschte Lösung
Eine Möglichkeit, Props/Events/Slots explizit einzugeben.

Vorschlag: Eine neue reservierte Schnittstelle ComponentDef , die, wenn sie definiert ist, als öffentliche API der Komponente verwendet wird, anstatt die Dinge aus dem Code abzuleiten.

Beispiel (mit Kommentaren dazu, was wahrscheinlich nicht möglich ist):

<script lang="ts"
   interface ComponentDef<T> { // <-- note that we can use generics as long as they are "defined" through props
      props: {  items: T[]  }
      events: {  itemClick: CustomEvent<T>  }
      slots: { default: { item: T } }
  }

   // generic type T is not usable here. Is that a good solution? Should it be possible?
   // Also: How do we make sure the types match the component definition?
  export items: any[];

   // ...

   // We cannot make sure that the dispatched event and its type are correct. Is that a problem?
   // Maybe enhance the type definitions in the core repo so createEventDispatcher accepts a record of eventname->type?
   dispatch('itemClick', item);
</script>
<!-- ... -->
<slot item={item}> <!-- again, we cannot make sure this actually matches the type definition -->

Wenn jetzt jemand die Komponente verwendet, bekommt er die richtigen Typen aus den Props/Events/Slots und auch eine Fehlerdiagnose, wenn etwas nicht stimmt:

<Child
     items="{['a', 'string']}"
     on:itemClick="{event => event.detail === 1 /* ERROR, number not comparable to type string */}"
/>

Wenn dies funktioniert und ein wenig getestet wird, könnten wir es mit anderen reservierten Schnittstellen wie ComponentEvents erweitern, um nur eines der Dinge einzugeben.

Ich möchte dazu so viel Feedback wie möglich erhalten, da dies wahrscheinlich eine große Änderung ist, die allgemein verwendet werden könnte. Außerdem würde ich gerne einige Mitglieder des Kernteams bitten, sich das anzusehen, wenn es für sie gut aussieht oder etwas einführt, mit dem sie nicht einverstanden sind.

@jasonlyu123 @orta @Conduitry @antony @pngwn

enhancement

Hilfreichster Kommentar

Vielleicht verwende ich hier die falschen Begriffe ... oder ich habe hoffentlich nur etwas falsch gemacht.

Beispiel für das, was ich gerne tun könnte:

```html


{option.myLabelProp}

{#each Optionen als Option} {/jede einzelne}

Alle 24 Kommentare

Scheint vernünftig, es gibt tatsächlich ein Svelte-Problem, Slots zur Laufzeit zu injizieren. https://github.com/sveltejs/svelte/issues/2588 und eine PR, die es implementiert, https://github.com/sveltejs/svelte/pull/4296 , hat das Gefühl, dass es Überschneidungen geben könnte oder zumindest eine Gelegenheit dazu Angleichung der Schnittstellen (falls es Konsens gibt, gibt es noch einige offene Fragen zu obigem PR).

Danke für den PR-Link, scheint nur irgendwie mit uns zu tun zu haben, dass wir den Konstruktor dann etwas anders eingeben müssen, aber ich denke, das ist unabhängig von der Typprüfung auf Template-Ebene.

interessant.
Ich frage mich, ob wir so etwas machen könnten

<script lang="ts" generic="T"> 
    type T = unknown
    export let items: T[]
    let item:T = items[0]
</script>
<slot b={item}></slot>

Dadurch würde das type T = unknown während der Typprüfung entfernt und stattdessen als generisches Argument zur Komponente hinzugefügt.

//...
render<T>() {
    export let items: T[]
    let item:T = items[0]
}

Gute Idee, es der Funktion render hinzuzufügen!

Ich denke, wir können damit die gleichen Ergebnisse erzielen

<script lang="ts">
    interface ComponentDef<T> {
       ...
    } 
    type T = unknown
    export let items: T[]
    let item:T = items[0]
</script>
<slot b={item}></slot>

durch Extrahieren von T aus der Schnittstellendefinition.

Obwohl Sie mit Ihrer Lösung im Fall von "Ich möchte nur eine generische Beziehung zwischen meinen Requisiten und Slots" weniger tippen müssen, was schön ist. Einerseits denke ich "ja, wir könnten einfach beides hinzufügen", andererseits fühlt es sich ein bisschen so an, als würde man die API-Oberfläche zu sehr erweitern (man kann die gleichen Dinge auf verschiedene Arten tun) - nicht sicher.

Ich möchte wirklich nicht das interface ComponentDef<T>{ props: {} } und es mit jedem meiner Exporte ausrichten müssen. Svelte ist so gut darin, die eingegebenen Zeichen im Vergleich zu React zu reduzieren, und das fühlt sich wie ein Rückschritt an. Insbesondere müssen die Typen jedes Exports in die Requisiten dupliziert werden, was keinen Spaß macht (und zwangsläufig zu häufigen Problemen führt).

Ich mag @halfnelsons Denkweise. Exporte sollten als Requisiten erkannt werden. Ich weiß nicht, wie das Modul einmal aussieht

Noch ein kurzes, wie ich es in einigen verwandten Themen gelesen habe: Ich hatte endlose Probleme mit JSDoc-Kommentaren, um Dinge einzugeben, einschließlich der Option @template .

Obwohl wir JSDoc gegenüber aufgeschlossen sein sollten (auch wenn JSDoc innerhalb von HTML verwendet wird), muss ich warnen, dass es als TypeScript einfach nicht ausdrucksstark ist, unabhängig davon, ob es über WebStorm oder VS Code verwendet wird. zB implementiert es nicht den Typ true ; Ich bin sicher, es macht keine Indextypen; und wenn ich mich recht erinnere, kannst du auch kein Record<keyof X, any> haben. Ich renne damit immer wieder gegen Wände. @template hat funktioniert, war aber auch ziemlich begrenzt. Und ich denke, es funktioniert auch je nach IDE unterschiedlich gut.

Das Übergeben des generischen Typs des (jsx)-Elements ist für mich ein No-Go, da es sich nicht um eine gültige Svelte-Vorlagensyntax handelt. Es sollte auch nicht benötigt werden, da Generika von Eigenschaften gesteuert werden. Ich kann mir keine Möglichkeit vorstellen, eine generische Abhängigkeit nur für Slots/Events einzuführen. Wenn sie von Eingabeeigenschaften gesteuert werden, ist die Übergabe von Generika an jsx-Elemente nicht erforderlich, da wir svelte2tsx -Code so generieren können, dass TS ihn für uns ableitet.

Über den Tippaufwand: Dies ist korrekt, wird aber nur in Situationen benötigt, in denen Sie Generics verwenden und/oder Ereignisse/Slots explizit eingeben möchten. Theoretisch könnten wir das alles selbst ableiten, aber das fühlt sich super schwer umzusetzen an. Beispiele für solche schwer zu implementierenden Probleme sind die Unterstützung für fortgeschrittene Slot-Szenarien wie in #263 und das Sammeln aller möglichen Komponentenereignisse (was einfach ist, wenn der Benutzer nur createEventDispatcher verwenden würde, aber er könnte auch eine Funktion importieren, die wickelt createEventDispatcher Sachen ein).

Im Allgemeinen denke ich, dass es viele Situationen gibt, in denen die Leute nur eines dieser Dinge definieren möchten, aber nicht die anderen, also ist es vielleicht wirklich notwendig, all die verschiedenen Optionen anzubieten, um den „typlosen“ Geist von Svelte zu bewahren.

Dies würde bedeuten:

  • ComponentDef ist das Alles-in-einem-wenn-du-es-braucht
  • ComponentEvents dient nur zum Eingeben von Ereignissen
  • ComponentSlots ist nur für Tipp-Slots
  • ein Konstrukt wie <script generic="T"> , wenn Sie nur Generika benötigen
  • eine Kombination aus ComponentEvents/ComponentSlots/generic="T"

@shirakaba Die JSDoc-Typunterstützung muss nicht so funktionsreich sein wie ein Typoskript. Ich bezweifle, dass viele Leute generische Komponenten damit eingeben würden. Da wir außerdem den Sprachdienst von Typoskript verwenden, können viele fortgeschrittene Schriften problemlos aus einer Typoskript-Datei importiert werden.

Über die Typprüfung von Slot-Requisiten habe ich einen Hack, weiß aber nicht, ob dies zu einer guten Entwicklererfahrung führen würde. Wenn der Benutzer eine Komponente wie folgt eingibt:

interface ComponentDef {
      slots: { default: { item: string } }
  }

Wir können eine Klasse generieren

class DefaultSlot extends Svelte2TsxComponent<ComponentDef['slots']['default']>{ }

und verwandeln Sie den Standardsteckplatz in

<DeafaultSlot />

(nur als Benutzer einklinken) Für mich ist das zusätzliche Tippen kein Problem, da ich sowieso viel davon erledigen muss, während ich mit TypeScript arbeite.

Was die Schnittstelle im Vergleich zur generic -Prop betrifft, da es Fälle gibt, in denen Variablen nur für den Verbraucher verfügbar sind, wäre es besser, wenn DX die Prop unterstützen würde. Aber in diesem Sinne, wäre es möglich, export type T statt generic="T" zu unterstützen?

Dies ist eine ungültige TS-Syntax, die die Leute verwirren könnte. Außerdem müssten wir einen zusätzlichen Transformationsschritt durchführen, der nicht durchgeführt würde, falls sich die Svelte-Komponente in einem nicht kompilierbaren Zustand befindet. In diesem Fall greifen die Sprachwerkzeuge auf das zurück, was im Skript steht, das dann diese ungültige Syntax hätte. Da bin ich dagegen.

Ich verstehe. Um nicht zu verweilen, aber ist es eine ungültige Syntax oder eher ein Fehler "undefinierter/unbekannter Typ"? Ich frage, weil ich nicht weiß, wie der Compiler von TypeScript die beiden Fälle handhabt. Ich habe nur Vorbehalte gegen das Hinzufügen eines benutzerdefinierten Attributs zu einem script -Tag, insbesondere bei einem Namen, der so generisch wie "generisch" ist :)

Alles in allem ist dies für mich ein Nice-to-have und vielleicht ist es eine gute Sache, den Benutzer zu zwingen, die exportierten Variablen einzugeben, wenn er die Komponente verwendet (besser lesbarer Code).

Wenn Sie export type T; eingeben, gibt TS einen Syntaxfehler aus. Und wie formuliert man Constraints? export type T extends string; wirft weitere Syntaxfehler auf Sie. Ich verstehe Ihren Vorbehalt gegen ein benutzerdefiniertes Attribut vollkommen, aber ich denke, es ist der am wenigsten störende Weg. Andere Möglichkeiten wären reservierte Typnamen wie T1 oder T2 , aber das ist nicht flexibel genug (wie fügt man Einschränkungen hinzu?).

Die Alternative wäre, die gesamte Komponente über ComponentDef , wo man Generika frei hinzufügen kann, wie im Beispiel im Startpost. Dies geht jedoch auf Kosten von mehr Tipparbeit.

Für mich wird aus der Diskussion deutlich, dass die Leute sehr unterschiedliche Meinungen dazu haben und mehr Optionen angelegt zu haben ( ComponentDef , generische Requisiten als Attribute, nur ComponentEvents eingeben) ist am flexibelsten machen die meisten von ihnen glücklich, obwohl sie verschiedene Wege einführen, dasselbe zu tun.

Ich habe das vielleicht übersehen, aber ist es eine Option, es aufzuteilen?

<script>
  import type { Foo } from './foo';
  export let items: Foo[];
  interface ComponentSlots<T> {}
  interface ComponentEvents<T> {}
</script>

Würde das den Schreibaufwand für die meisten Fälle reduzieren?

Ja das wäre möglich.

@dummdidumm ist es möglich, in der Zwischenzeit an der Unterstützung interface ComponentSlots {} (mit oder ohne Unterstützung für Generika) zu arbeiten? Oder würde die Einführung dem widersprechen, was hier diskutiert wird?

Das würde es nicht, aber bevor ich mit der Implementierung fortfahre, möchte ich zuerst einen RFC schreiben, um mehr Feedback von der Community und den anderen Betreuern zu der vorgeschlagenen Lösung zu erhalten. Sobald es eine Einigung gibt, werden wir mit der Umsetzung dieser Dinge beginnen.

@joelmukuthu Übrigens, warum möchtest du die Schnittstellenunterstützung haben? Wir haben die Typinferenz für Slots verbessert. Gibt es einen Fall, in dem es für Sie immer noch nicht funktioniert? Wenn ja, bin ich neugierig auf Ihren Fall.

Zu diesem Thema habe ich nun einen RFC erstellt: https://github.com/sveltejs/rfcs/pull/38
Die Diskussion über die API sollte dort fortgesetzt werden.

@dummdidumm Entschuldigung für die langsame Antwort. Ich habe gerade festgestellt, dass ich für meinen Anwendungsfall tatsächlich Unterstützung für Generika benötige. Type Inference für Slots funktioniert recht gut!

Das wäre riesig! Ich bin derzeit traurig, dass die Verwendung von Slot-Eigenschaften immer ein Beliebiges ist.

Das ist seltsam, Slot Typed kann an dieser Stelle ganz gut abgeleitet werden, wenn die von Ihnen verwendete Komponente in Ihrem Projekt ist und auch TS verwendet.

Vielleicht verwende ich hier die falschen Begriffe ... oder ich habe hoffentlich nur etwas falsch gemacht.

Beispiel für das, was ich gerne tun könnte:

```html


{option.myLabelProp}

{#each Optionen als Option} {/jede einzelne}

Ok, ich verstehe, ja im Moment ist das nicht möglich, also musst du auf any[] zurückgreifen. Wenn dies nur string[] zulassen würde, würde Ihr Slot als string eingegeben werden, was ich mit "Typ kann abgeleitet werden" meinte.

Eine nette vorübergehende Lösung für Typprüfungskomponenten, auf die ich in „Svelte and Sapper in Action“ von Mark Volkmann gestoßen bin, ist die Verwendung der React-Requisitentypenbibliothek neben $$props.

Dies ist beispielsweise der Code für ein Todo-Element:

import PropTypes from "prop-types/prop-types";

const propTypes = {
    text: PropTypes.string.isRequired,
    checked: PropTypes.bool,
};

// Interface for Todo items
export interface TodoInterface {
    id: number;
    text: string;
    checked: boolean;
}

PropTypes.checkPropTypes(propTypes, $$props, "prop", "Todo");

Während ich die Requisiten von Todo validiere und Fehler erhalte, wenn etwas nicht stimmt, möchte ich auch eine Schnittstelle für Todo-Elemente haben, damit ich statische Typprüfungen in VSCode sowie automatische Vervollständigungen haben kann. Dies wird durch die Verwendung der oben definierten Schnittstelle erreicht. Der offensichtliche und signifikante Nachteil davon ist, dass ich hier doppelten Code haben muss. Ich kann keine Schnittstelle aus dem propTypes-Objekt definieren oder umgekehrt.

Ich beginne immer noch mit Javascript, also bitte korrigieren Sie mich, wenn irgendetwas davon falsch ist.

Ich denke, Typüberprüfung ist ein Muss für jede produktive Codebasis, sowohl statische Überprüfung als auch Requisiten-Typüberprüfung zur Laufzeit.

(Beachten Sie, dass die Schnittstelle in context='module' definiert wird, sodass sie neben dem Standardexport der Komponente exportierbar ist, aber diesen Teil der Kürze halber weggelassen hat.)

Bearbeiten: Ich habe dies nicht für etwas anderes als die Requisitenvalidierung versucht, lassen Sie mich wissen, ob es auch für Slots/Events funktionieren würde!

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen