Language-tools: Escribir Svelte Component Props/Events/Slots

Creado en 11 ago. 2020  ·  24Comentarios  ·  Fuente: sveltejs/language-tools

Ya hay varios problemas sobre esto (#424, #304, #273, #263), pero quería hacer uno consolidado para tener una discusión sobre los enfoques que podemos tomar.
Relaciones públicas relacionadas: #437

Tenga en cuenta que este problema NO se trata de escribir un archivo d.ts , esto vendrá después como un problema separado.

¿Tu solicitud de función está relacionada con un problema?
Por el momento no es posible teclear las entradas/salidas de un componente bajo determinadas circunstancias:

  • No es posible definir una relación genérica entre props y eventos/slots (genéricos)
  • No es posible definir los eventos y sus tipos.
  • No es posible definir los slots y sus tipos de forma específica

Describa la solución que le gustaría
Una forma de escribir props/events/slots explícitamente.

Propuesta: una nueva interfaz reservada ComponentDef que, cuando se define, se usa como la API pública del componente en lugar de inferir las cosas del código.

Ejemplo (con comentarios sobre lo que probablemente no es posible):

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

Cuando alguien ahora use el componente, obtendrá los tipos correctos de los accesorios/eventos/ranuras y también diagnósticos de error cuando algo esté mal:

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

Si esto funciona y se prueba un poco, podríamos mejorarlo con otras interfaces reservadas como ComponentEvents para escribir solo una de las cosas.

Me gustaría obtener la mayor cantidad de comentarios posible sobre esto porque es probable que sea un gran cambio que podría usarse ampliamente. Además, me gustaría que algunos de los miembros del equipo central echaran un vistazo a esto si les parece bien o presenta algo con lo que no están de acuerdo.

@jasonlyu123 @orta @Conduitry @antony @pngwn

enhancement

Comentario más útil

Tal vez estoy usando los términos incorrectos aquí... o espero que haya hecho algo mal.

Ejemplo de lo que me gustaría poder hacer:

```html


{opción.myLabelProp}

{#cada opción como opción} {/cada}

Todos 24 comentarios

Parece razonable, en realidad hay un problema Svelte sobre poder inyectar ranuras en tiempo de ejecución. https://github.com/sveltejs/svelte/issues/2588 y un PR que lo implementa https://github.com/sveltejs/svelte/pull/4296 , parece que podría haber superposición, o al menos alguna oportunidad de alinear las interfaces (si hay algún consenso, todavía quedan algunas cuestiones pendientes con el RP anterior).

Gracias por el enlace PR, parece que solo está relacionado con nosotros de una manera que tenemos que escribir el constructor de manera un poco diferente, pero creo que esto es independiente de la verificación de tipo en un nivel de plantilla.

interesante.
Me pregunto si podríamos hacer algo como

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

lo que eliminaría el type T = unknown durante la verificación de tipo y, en su lugar, lo agregaría como un argumento genérico al componente.

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

¡Buena idea agregarlo a la función render !

Creo que podemos obtener los mismos resultados con

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

extrayendo el T de la definición de la interfaz.

Aunque con su solución tendrá que escribir menos en el caso de "Solo quiero una relación genérica entre mis accesorios y tragamonedas", lo cual es bueno. Por un lado, estoy pensando "sí, podríamos simplemente agregar ambos", por otro lado, se siente un poco como expandir demasiado la superficie de la API (puede hacer las mismas cosas de diferentes maneras), no estoy seguro.

Realmente no quiero tener que escribir el interface ComponentDef<T>{ props: {} } y alinearlo con cada una de mis exportaciones. Svelte lo hace muy bien en la reducción de caracteres escritos en comparación con React, y esto se siente como un paso atrás. En particular, requiere duplicar los tipos de cada exportación en los accesorios, lo cual no es divertido (y seguramente generará problemas frecuentes).

Me gusta la forma de pensar de @halfnelson . Las exportaciones deben detectarse como accesorios. No sé cómo se ve el módulo una vez

Otro rápido, como lo leí en algunos de los problemas relacionados: he tenido un sinfín de problemas para usar los comentarios de JSDoc para escribir cosas, incluida la opción @template .

Si bien debemos mantener la mente abierta a JSDoc (incluso si se usa JSDoc dentro del HTML), debo advertir que, ya sea que se use a través de WebStorm o VS Code, simplemente no es expresivo como TypeScript. por ejemplo, no implementa el tipo true ; Estoy seguro de que no hace tipos de índice; y si mal no recuerdo, tampoco puedes tener un Record<keyof X, any> . Sigo chocando contra las paredes con él. @template funcionó, pero también fue bastante limitado. Y creo que también funciona de manera diferente según el IDE.

Pasar el tipo genérico del elemento (jsx) no es válido para mí porque no es una sintaxis de plantilla esbelta válida. Tampoco debería ser necesario ya que los genéricos son impulsados ​​por propiedades, no puedo pensar en una manera de introducir un dependiente genérico solo para tragamonedas/eventos. Si están impulsados ​​por propiedades de entrada, no es necesario pasar genéricos a elementos jsx porque podemos generar código svelte2tsx de tal manera que TS lo inferirá por nosotros.

Acerca de la sobrecarga de escritura: esto es correcto, pero solo sería necesario en situaciones en las que desee usar genéricos y/o escribir eventos/ranuras de forma explícita. En teoría, podríamos inferir todo esto nosotros mismos, pero parece muy difícil de implementar. Ejemplos de problemas tan difíciles de implementar son la compatibilidad con escenarios de tragamonedas avanzados como en el n.° 263 y recopilar todos los eventos de componentes posibles (lo cual es fácil si el usuario solo usara createEventDispatcher , pero también podría importar una función que envuelve createEventDispatcher cosas).

En general, creo que hay muchas situaciones en las que las personas solo querrían definir una de esas cosas, pero no las otras, por lo que tal vez sea necesario proporcionar todas las diferentes opciones para mantener el espíritu de "tipo menos" de Svelte.

Esto significaría:

  • ComponentDef es el "todo en uno" si lo necesita
  • ComponentEvents es solo para escribir eventos
  • ComponentSlots es solo para máquinas tragamonedas
  • una construcción como <script generic="T"> si solo necesita genéricos
  • una combinación de ComponentEvents/ComponentSlots/generic="T"

@shirakaba El soporte de tipo JSDoc no tiene que ser tan rico en funciones como un mecanografiado. Dudo que mucha gente escriba componentes genéricos con él. Además, debido a que estamos usando el servicio de lenguaje de mecanografiado, gran parte del tipo avanzado se puede importar fácilmente desde un archivo de mecanografiado.

Acerca de la verificación de tipo de accesorios de tragamonedas, tengo algunos trucos, pero no sé si esto conduciría a una buena experiencia de desarrollador. Si el usuario escribe un componente como este:

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

podemos generar una clase

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

y transformar la ranura predeterminada en

<DeafaultSlot />

(solo interviniendo como usuario) Para mí, la escritura adicional no es un problema porque tengo que hacer mucho de todos modos mientras trabajo con TypeScript.

En cuanto a la interfaz frente a la propiedad generic , dado que hay casos en los que las variables solo están expuestas al consumidor, sería mejor DX para admitir la propiedad. Pero en ese sentido, ¿sería posible admitir export type T en lugar de generic="T" ?

Esta es una sintaxis de TS no válida que podría confundir a la gente. Además, tendríamos que hacer un paso de transformación adicional, que no se haría en caso de que el componente Svelte esté en un estado no compilable. En este caso, las herramientas de idioma recurren a lo que está en el script, que tendría esta sintaxis no válida. Así que estoy en contra de eso.

Entiendo. No me demore, pero ¿es una sintaxis no válida o más bien un error de "tipo indefinido/desconocido"? Lo pregunto porque no sé cómo el compilador de TypeScript maneja los dos casos. Solo tengo reservas en contra de agregar un atributo personalizado a una etiqueta script , especialmente con un nombre tan genérico como "genérico" :)

Considerándolo todo, para mí, es bueno tenerlo y tal vez obligar al usuario a escribir las variables exportadas cuando usa el componente es algo bueno (código más legible).

Cuando escribe export type T; , TS generará un error de sintaxis. Además, ¿cómo formular restricciones? export type T extends string; arrojará más errores de sintaxis. Entiendo totalmente su reserva contra un atributo personalizado, pero creo que es la forma menos disruptiva. Otras formas serían tener nombres de tipos reservados como T1 o T2 pero eso no es lo suficientemente flexible (¿cómo agregar restricciones?).

La alternativa sería escribir todo el componente a través ComponentDef , donde puede agregar genéricos libremente, como en el ejemplo de la publicación inicial. Pero esto tiene el costo de escribir más.

Para mí, queda claro a partir de la discusión que las personas tienen opiniones muy diferentes sobre esto y tener más opciones establecidas ( ComponentDef , accesorios genéricos como atributos, solo escribir ComponentEvents ) es lo más flexible para hacen felices a la mayoría, aunque introducen diferentes formas de hacer lo mismo.

Es posible que me haya perdido esto, pero ¿dividirlo es una opción?

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

¿Eso reduciría la sobrecarga de tipeo en la mayoría de los casos?

Sí, esto sería posible.

@dummdidumm , ¿es posible trabajar en la compatibilidad con interface ComponentSlots {} (con o sin compatibilidad con genéricos) mientras tanto? ¿O presentar eso sería contradictorio con las cosas discutidas aquí?

No lo haría, pero antes de continuar con la implementación, primero quiero escribir un RFC para obtener más comentarios de la comunidad y otros mantenedores sobre la solución propuesta. Una vez que haya un acuerdo, comenzaremos a implementar esas cosas.

@joelmukuthu por cierto, ¿por qué le gustaría tener soporte para la interfaz? Mejoramos la inferencia de tipos para tragamonedas, ¿hay algún caso en el que aún no te funcione? Si es así, tengo curiosidad por conocer tu caso.

Ahora creé un RFC sobre este tema: https://github.com/sveltejs/rfcs/pull/38
La discusión sobre la API debe continuar allí.

@dummdidumm lo siento por la respuesta lenta. Me acabo de dar cuenta de que para mi caso de uso, en realidad necesito soporte para genéricos. ¡La inferencia de tipos para tragamonedas funciona bastante bien!

¡Esto sería enorme! Actualmente me entristece que el uso de propiedades de tragamonedas sea siempre un problema.

Eso es extraño, el tipo de ranura se puede inferir bastante bien en este punto, si el componente que usa está dentro de su proyecto y también usa TS.

Tal vez estoy usando los términos incorrectos aquí... o espero que haya hecho algo mal.

Ejemplo de lo que me gustaría poder hacer:

```html


{opción.myLabelProp}

{#cada opción como opción} {/cada}

Ok, entiendo, sí, en este momento esto no es posible, por lo que debe recurrir a any[] . Si esto permitiera solo string[] , su ranura se escribiría como string , que es lo que quise decir con "el tipo se puede inferir".

Una buena solución temporal para escribir componentes de verificación que encontré en "Svelte and Sapper in Action" de Mark Volkmann es el uso de la biblioteca de tipos de accesorios React junto con $$props.

Este es el código para un elemento de Todo, por ejemplo:

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

Mientras valido los accesorios de Todo y obtengo errores si algo está mal, también quiero tener una interfaz para los elementos de Todo, de modo que pueda tener verificación de tipo estático en VSCode, así como autocompletados. Esto se logra mediante el uso de la interfaz que definí anteriormente. La desventaja obvia y significativa de esto es que necesito tener un código duplicado aquí. No puedo definir una interfaz desde el objeto propTypes o viceversa.

Todavía estoy comenzando en javascript, así que corríjame si algo de esto está mal.

Creo que la verificación de tipos es imprescindible para cualquier base de código productiva, tanto la verificación estática como la verificación de tipos de accesorios en tiempo de ejecución.

(Tenga en cuenta que la interfaz se definiría en contexto = 'módulo', por lo que se puede exportar junto con la exportación predeterminada del componente, pero omitió esa parte por brevedad)

Editar: no he probado esto para otra cosa que no sea la validación de accesorios, ¡avíseme si también funcionaría para tragamonedas/eventos!

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

arxpoetica picture arxpoetica  ·  3Comentarios

vatro picture vatro  ·  3Comentarios

matthewmueller picture matthewmueller  ·  3Comentarios

JAD3N picture JAD3N  ·  5Comentarios

Kingwl picture Kingwl  ·  6Comentarios