Language-tools: Taper des accessoires/événements/emplacements de composants Svelte

Créé le 11 août 2020  ·  24Commentaires  ·  Source: sveltejs/language-tools

Il y a déjà plusieurs problèmes à ce sujet (#424, #304, #273, #263), mais je voulais en faire un consolidé pour avoir une discussion sur les approches que nous pouvons adopter.
RP connexe : # 437

Notez que ce problème ne concerne PAS la saisie d'un fichier d.ts , cela viendra ensuite comme un problème distinct.

Votre demande de fonctionnalité est-elle liée à un problème ?
Pour l'instant il n'est pas possible de saisir les entrées/sorties d'un composant sous certaines conditions :

  • Il n'est pas possible de définir une relation générique entre les accessoires et les événements/slots (génériques)
  • Il n'est pas possible de définir les événements et leurs types
  • Il n'est pas possible de définir les créneaux et leurs types de manière spécifique

Décrivez la solution que vous souhaitez
Un moyen de saisir explicitement les props/events/slots.

Proposition : Une nouvelle interface réservée ComponentDef qui, une fois définie, est utilisée comme API publique du composant au lieu de déduire les choses du code.

Exemple (avec des commentaires sur ce qui n'est probablement pas possible) :

<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 -->

Quand quelqu'un utilise maintenant le composant, il obtiendra les types corrects des props/events/slots et aussi des diagnostics d'erreur quand quelque chose ne va pas :

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

Si cela fonctionne et est testé un peu, nous pourrions l'améliorer avec d'autres interfaces réservées comme ComponentEvents pour ne taper qu'une seule des choses.

J'aimerais avoir autant de commentaires que possible à ce sujet, car il s'agit probablement d'un changement important qui pourrait être largement utilisé. De plus, j'aimerais que certains membres de l'équipe principale examinent cela si cela leur semble bon ou introduit quelque chose avec lequel ils ne sont pas d'accord.

@jasonyu123 @orta @Conduitry @antony @pngwn

enhancement

Commentaire le plus utile

Peut-être que j'utilise les mauvais termes ici... ou j'espère que j'ai juste fait quelque chose de mal.

Exemple de ce que j'aimerais pouvoir faire :

```html


{option.myLabelProp}

{#chaque option comme option} {/chaque}

Tous les 24 commentaires

Cela semble raisonnable, il y a en fait un problème Svelte concernant la possibilité d'injecter des slots au moment de l'exécution. https://github.com/sveltejs/svelte/issues/2588 et un PR qui l'implémente https://github.com/sveltejs/svelte/pull/4296 , donne l'impression qu'il pourrait y avoir un chevauchement, ou du moins une opportunité de aligner les interfaces (s'il y a consensus, il reste encore des questions en suspens avec le PR ci-dessus).

Merci pour le lien PR, il semble que cela ne nous concerne que d'une manière que nous devons taper le constructeur un peu différemment, mais je pense que cela est indépendant de la vérification de type au niveau du modèle.

intéressant.
Je me demande si nous pourrions faire quelque chose comme

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

qui supprimerait le type T = unknown lors de la vérification de type et l'ajouterait à la place comme argument générique au composant.

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

Bonne idée de l'ajouter à la fonction render !

Je pense que nous pouvons obtenir les mêmes résultats avec

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

en extrayant le T de la définition de l'interface.

Bien qu'avec votre solution, vous devrez taper moins dans le cas de "Je veux juste une relation générique entre mes accessoires et mes emplacements", ce qui est bien. D'une part, je pense "oui, nous pourrions simplement ajouter les deux", d'autre part, cela ressemble un peu à trop étendre la surface de l'API (vous pouvez faire les mêmes choses de différentes manières) - pas sûr.

Je ne veux vraiment pas avoir à écrire le interface ComponentDef<T>{ props: {} } et à l'aligner avec chacune de mes exportations. Svelte réussit si bien à réduire les caractères tapés par rapport à React, et cela ressemble à un pas en arrière. En particulier, cela nécessite de dupliquer les types de chaque exportation dans les accessoires, ce qui n'est pas amusant (et entraînera forcément des problèmes fréquents).

J'aime la ligne de pensée de @halfnelson . Les exportations doivent être détectées comme des accessoires. Je ne sais pas à quoi ressemble le module une fois

Un autre rapide, comme je l'ai lu dans certains des problèmes connexes : j'ai eu des problèmes sans fin en utilisant les commentaires JSDoc pour taper des choses, y compris l'option @template .

Bien que nous devions garder l'esprit ouvert à JSDoc (même en utilisant JSDoc dans le HTML), je dois avertir que, qu'il soit utilisé via WebStorm ou VS Code, il n'est tout simplement pas expressif en tant que TypeScript. par exemple, il n'implémente pas le type true ; Je suis sûr qu'il ne fait pas de types d'index ; et si je me souviens bien, vous ne pouvez pas non plus avoir un Record<keyof X, any> . Je n'arrête pas de me heurter aux murs avec. @template a fonctionné, mais était assez limité aussi. Et je pense que cela fonctionne différemment selon l'IDE également.

Passer le type générique à l'élément (jsx) est un non-aller pour moi car ce n'est pas une syntaxe de modèle svelte valide. Cela ne devrait pas non plus être nécessaire puisque les génériques sont pilotés par des propriétés, je ne peux pas penser à un moyen d'introduire une dépendance générique pour les créneaux horaires/événements uniquement. S'ils sont pilotés par des propriétés d'entrée, il n'est pas nécessaire de transmettre des génériques aux éléments jsx car nous pouvons générer du code svelte2tsx de telle manière que TS le déduira pour nous.

À propos de la surcharge de frappe : c'est correct, mais cela ne serait nécessaire que dans les situations où vous souhaitez utiliser des génériques et/ou taper explicitement des événements/emplacements. En théorie, nous pourrions déduire tout cela nous-mêmes, mais cela semble très difficile à mettre en œuvre. Des exemples de ces problèmes difficiles à implémenter sont la prise en charge de scénarios d'emplacements avancés comme dans # 263 et la collecte de tous les événements de composants possibles (ce qui est facile si l'utilisateur n'utilisait que createEventDispatcher , mais il pourrait également importer une fonction qui enveloppe createEventDispatcher trucs).

En général, je pense qu'il existe de nombreuses situations où les gens ne voudraient définir qu'une de ces choses, mais pas les autres, alors peut-être qu'il est en effet nécessaire de fournir toutes les différentes options pour garder l'esprit "moins de type" de Svelte.

Cela signifierait :

  • ComponentDef est le tout-en-un-si-vous-en avez besoin
  • ComponentEvents sert uniquement à saisir des événements
  • ComponentSlots est réservé aux machines à sous
  • une construction comme <script generic="T"> si vous n'avez besoin que de génériques
  • une combinaison de ComponentEvents/ComponentSlots/generic="T"

@shirakaba La prise en charge du type JSDoc n'a pas besoin d'être aussi riche en fonctionnalités qu'un texte dactylographié. Je doute que beaucoup de gens tapent des composants génériques avec. De plus, comme nous utilisons le service de langage de dactylographie, une grande partie du type avancé peut être facilement importée à partir d'un fichier dactylographié.

À propos de la vérification du type d'accessoires de slot, j'ai un peu de piratage, mais je ne sais pas si cela conduirait à une bonne expérience de développeur. Si l'utilisateur tape un composant comme celui-ci :

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

nous pouvons générer une classe

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

et transformer l'emplacement par défaut en

<DeafaultSlot />

(uniquement en tant qu'utilisateur) Pour moi, la saisie supplémentaire n'est pas un problème car je dois en faire beaucoup de toute façon lorsque je travaille avec TypeScript.

En ce qui concerne l'interface par rapport à l'accessoire generic , puisqu'il existe des cas où les variables ne sont exposées qu'au consommateur, il serait préférable que DX prenne en charge l'accessoire. Mais sur cette note, serait-il possible de supporter export type T au lieu de generic="T" ?

Il s'agit d'une syntaxe TS invalide qui pourrait dérouter les gens. De plus, nous aurions à faire une étape de transformation supplémentaire, ce qui ne serait pas fait si le composant Svelte est dans un état non compilable. Dans ce cas, les outils de langage se replient sur ce qui est dans le script, qui aurait alors cette syntaxe invalide. Donc je suis contre ça.

Je comprends. Ne pas s'attarder, mais s'agit-il d'une syntaxe invalide ou plutôt d'une erreur "type indéfini/inconnu" ? Je demande parce que je ne sais pas comment le compilateur de TypeScript gère les deux cas. J'ai juste des réserves contre l'ajout d'un attribut personnalisé à une balise script , surtout avec un nom aussi générique que "générique" :)

Dans l'ensemble, pour moi, c'est un avantage et peut-être que forcer l'utilisateur à taper les variables exportées lors de l'utilisation du composant est une bonne chose (code plus lisible).

Lorsque vous tapez export type T; , TS génère une erreur de syntaxe. Aussi, comment formuler des contraintes ? export type T extends string; vous lancera plus d'erreurs de syntaxe. Je comprends tout à fait votre réserve à l'égard d'un attribut personnalisé, mais je pense que c'est la manière la moins dérangeante. D'autres moyens seraient d'avoir des noms de type réservés comme T1 ou T2 mais ce n'est pas assez flexible (comment ajouter des contraintes ?).

L'alternative serait de taper le composant entier via ComponentDef , où vous pouvez librement ajouter des génériques, comme dans l'exemple du post de départ. Mais cela se fait au prix de plus de frappe.

Pour moi, il ressort clairement de la discussion que les gens ont des opinions très différentes à ce sujet et avoir plus d'options présentées ( ComponentDef , des accessoires génériques comme attributs, en tapant seulement ComponentEvents ) est le plus flexible pour rendent la plupart d'entre eux heureux, bien qu'ils introduisent différentes manières de faire la même chose.

J'ai peut-être manqué cela, mais le diviser est-il une option ?

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

Cela réduirait-il la surcharge de frappe dans la plupart des cas ?

Oui ce serait possible.

@dummdidumm est-il possible de travailler sur le support interface ComponentSlots {} (avec ou sans support pour les génériques) en attendant ? Ou est-ce que l'introduction de cela serait en contradiction avec les choses discutées ici ?

Ce ne serait pas le cas, mais avant de poursuivre l'implémentation, je souhaite d'abord écrire une RFC pour obtenir plus de commentaires de la communauté et des autres responsables sur la solution proposée. Une fois qu'il y aura un accord, nous commencerons à mettre en œuvre ces choses.

@joelmukuthu btw pourquoi voudriez-vous avoir le support de l'interface ? Nous avons amélioré l'inférence de type pour les emplacements, y a-t-il un cas où cela ne fonctionne toujours pas pour vous ? Si oui, je suis curieux d'entendre votre cas.

J'ai maintenant créé un RFC sur ce sujet : https://github.com/sveltejs/rfcs/pull/38
La discussion sur l'API devrait se poursuivre là-bas.

@dummdidumm désolé pour la lenteur de la réponse. Je viens de réaliser que pour mon cas d'utilisation, j'ai en fait besoin d'un support pour les génériques. L'inférence de type pour les slots fonctionne plutôt bien !

Ce serait énorme ! Je suis actuellement attristé que l'utilisation des propriétés d'emplacement soit toujours un quelconque.

C'est étrange, le type de slot peut être assez bien déduit à ce stade, si le composant que vous utilisez se trouve dans votre projet et utilise également TS.

Peut-être que j'utilise les mauvais termes ici... ou j'espère que j'ai juste fait quelque chose de mal.

Exemple de ce que j'aimerais pouvoir faire :

```html


{option.myLabelProp}

{#chaque option comme option} {/chaque}

Ok je comprends, oui pour le moment ce n'est pas possible, donc vous devez vous rabattre sur any[] . Si cela n'autorisait que string[] , votre emplacement serait tapé comme string , ce que je voulais dire par "le type peut être déduit".

Une bonne solution temporaire pour taper les composants de vérification que j'ai rencontrés dans "Svelte and Sapper in Action" de Mark Volkmann est l'utilisation de la bibliothèque de types d'accessoires React aux côtés de $$props.

Voici le code d'un élément Todo par exemple :

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");

Pendant que je valide les accessoires de Todo et que j'obtiens des erreurs si quelque chose ne va pas, je souhaite également disposer d'une interface pour les éléments Todo, afin que je puisse avoir une vérification de type statique dans VSCode ainsi que des auto-complétions. Ceci est réalisé en utilisant l'interface que j'ai définie ci-dessus. L'inconvénient évident et significatif de ceci est que j'ai besoin d'avoir du code en double ici. Je ne peux pas définir une interface à partir de l'objet propTypes, ou vice-versa.

Je débute encore en javascript, alors corrigez-moi si quelque chose ne va pas.

Je pense que la vérification de type est indispensable pour toute base de code productive, à la fois la vérification statique et la vérification du type d'accessoires au moment de l'exécution.

(Notez que l'interface serait définie dans context='module' donc exportable avec l'exportation par défaut du composant, mais omise cette partie par souci de brièveté)

Edit : Je n'ai pas essayé cela pour autre chose que la validation des accessoires, faites-moi savoir si cela fonctionnerait également pour les créneaux horaires/événements !

Cette page vous a été utile?
0 / 5 - 0 notes