Language-tools: Digitando Adereços/Eventos/Slots de Componentes Svelte

Criado em 11 ago. 2020  ·  24Comentários  ·  Fonte: sveltejs/language-tools

Já existem várias questões sobre isso (#424, #304, #273, #263), mas eu queria fazer um consolidado para ter uma discussão sobre as abordagens que podemos tomar.
PR relacionado: #437

Observe que este problema NÃO é sobre digitar um arquivo d.ts , isso virá depois como um problema separado.

Sua solicitação de recurso está relacionada a um problema?
No momento não é possível digitar as entradas/saídas de um componente em determinadas circunstâncias:

  • Não é possível definir uma relação genérica entre adereços e eventos/slots (genéricos)
  • Não é possível definir os eventos e seus tipos
  • Não é possível definir os slots e seus tipos de maneira específica

Descreva a solução que você deseja
Uma maneira de digitar props/events/slots explicitamente.

Proposta: Uma nova interface reservada ComponentDef que, quando definida, é usada como a API pública do componente ao invés de inferir as coisas do código.

Exemplo (com comentários sobre o que provavelmente não é possível):

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

Quando alguém agora usa o componente, ele obterá os tipos corretos dos adereços/eventos/slots e também diagnósticos de erro quando algo estiver errado:

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

Se isso funcionar e for testado um pouco, poderíamos aprimorá-lo com outras interfaces reservadas como ComponentEvents para digitar apenas uma das coisas.

Eu gostaria de obter o máximo de feedback possível sobre isso, porque essa é provavelmente uma grande mudança que pode ser usada amplamente. Além disso, gostaria que alguns dos membros da equipe principal dessem uma olhada nisso, se isso lhes parece bom ou apresenta algo com o qual não concordam.

@jasonlyu123 @orta @Conduitry @antony @pngwn

enhancement

Comentários muito úteis

Talvez eu esteja usando os termos errados aqui... ou espero ter feito algo errado.

Exemplo do que eu gostaria de fazer:

```html


{option.myLabelProp}

{#cada opções como opção} {/cada}

Todos 24 comentários

Parece razoável, na verdade existe um problema Svelte sobre poder injetar slots em tempo de execução. https://github.com/sveltejs/svelte/issues/2588 e um PR que o implementa https://github.com/sveltejs/svelte/pull/4296 , parece que pode haver sobreposição, ou pelo menos alguma oportunidade de alinhe as interfaces (se houver algum consenso, ainda há algumas questões pendentes com o PR acima).

Obrigado pelo link PR, parece que está apenas relacionado a nós que temos que digitar o construtor um pouco diferente, mas acho que isso é um recuo da verificação de tipo em um nível de modelo.

interessante.
Eu me pergunto se poderíamos fazer algo como

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

que retiraria o type T = unknown durante a verificação de tipo e, em vez disso, o adicionaria como um argumento genérico ao componente.

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

Boa ideia sobre adicioná-lo à função render !

Acho que podemos obter os mesmos resultados com

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

extraindo o T da definição da interface.

Embora com sua solução você terá que digitar menos no caso de "Eu só quero uma relação genérica entre meus adereços e slots", o que é bom. Por um lado, estou pensando "sim, poderíamos adicionar os dois", por outro lado, parece um pouco como expandir demais a superfície da API (você pode fazer as mesmas coisas de maneiras diferentes) - não tenho certeza.

Eu realmente não quero ter que escrever o interface ComponentDef<T>{ props: {} } e alinhá-lo com cada uma das minhas exportações. O Svelte se sai tão bem na redução de caracteres digitados em comparação com o React, e isso parece um retrocesso. Em particular, requer duplicar os tipos de cada exportação para os adereços, o que não é divertido (e pode levar a problemas frequentes).

Eu gosto da linha de pensamento de @halfnelson . As exportações devem ser detectadas como adereços. Eu não sei como é o módulo uma vez

Outra rápida, como eu li em algumas das questões relacionadas: Eu não tive problemas para usar comentários JSDoc para digitar coisas, incluindo a opção @template .

Embora devamos manter a mente aberta ao JSDoc (mesmo usando JSDoc dentro do HTML), devo avisar que, seja usado através do WebStorm ou do VS Code, ele simplesmente não é expressivo como TypeScript. por exemplo, não implementa o tipo true ; Tenho certeza de que não faz tipos de índice; e se bem me lembro, você também não pode ter um Record<keyof X, any> . Eu continuo correndo em paredes com ele. @template funcionou, mas também foi bastante limitado. E acho que funciona de maneira diferente dependendo do IDE também.

Passar o tipo genérico do elemento (jsx) não é uma opção para mim porque não é uma sintaxe de modelo svelte válida. Também não deve ser necessário, pois os genéricos são orientados por propriedades, não consigo pensar em uma maneira de introduzir um dependente genérico apenas para slots/eventos. Se eles são conduzidos por propriedades de entrada, passar genéricos para elementos jsx não é necessário porque podemos gerar código svelte2tsx de tal forma que o TS o inferirá para nós.

Sobre a sobrecarga de digitação: Isso está correto, mas só seria necessário em situações em que você deseja usar genéricos e/ou eventos/slots de tipo eplicitamente. Em teoria, poderíamos inferir tudo isso sozinhos, mas isso parece super difícil de implementar. Exemplos de problemas tão difíceis de implementar são o suporte para cenários de slots avançados como em #263, e para coletar todos os eventos de componentes possíveis (o que é fácil se o usuário usar apenas createEventDispatcher , mas ele também pode importar uma função que envolve createEventDispatcher coisas).

Em geral, sinto que existem muitas situações em que as pessoas gostariam de definir apenas uma dessas coisas, mas não as outras, então talvez seja realmente necessário fornecer todas as opções diferentes para manter o espírito "tipo menos" de Svelte.

Isso significaria:

  • ComponentDef é o tudo-em-um-se-você-precisar
  • ComponentEvents é apenas para digitar eventos
  • ComponentSlots é apenas para slots de digitação
  • uma construção como <script generic="T"> se você precisar apenas de genéricos
  • uma combinação de ComponentEvents/ComponentSlots/generic="T"

@shirakaba O suporte ao tipo JSDoc não precisa ser tão rico em recursos quanto um texto datilografado. Duvido que muitas pessoas digitariam componentes genéricos com ele. Também porque estamos usando o serviço de linguagem do typescript, muito do tipo avançado pode ser facilmente importado de um arquivo typescript.

Sobre a verificação de tipos de adereços de slot, tenho alguns hacks, mas não sei se isso levaria a uma boa experiência do desenvolvedor. Se o usuário digitar o componente como este:

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

podemos gerar uma classe

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

e transforme o slot padrão em

<DeafaultSlot />

(apenas entrando como usuário) Para mim, a digitação extra não é um problema porque tenho que fazer muito disso de qualquer maneira enquanto trabalho com o TypeScript.

Quanto à interface vs a prop generic , como há casos em que as variáveis ​​são expostas apenas ao consumidor, seria melhor DX suportar a prop. Mas nessa nota, seria possível suportar export type T em vez de generic="T" ?

Esta é uma sintaxe TS inválida que pode confundir as pessoas. Além disso, teríamos que fazer uma etapa de transformação adicional, o que não seria feito caso o componente Svelte estivesse em um estado não compilável. Nesse caso, as ferramentas de linguagem retornam ao que está no script, que teria essa sintaxe inválida. Então eu sou contra isso.

Eu entendo. Para não demorar, mas é uma sintaxe inválida ou mais um erro de "tipo indefinido/desconhecido"? Estou perguntando porque não sei como o compilador do TypeScript lida com os dois casos. Só tenho reservas contra adicionar um atributo personalizado a uma tag script , especialmente com um nome tão genérico quanto "genérico" :)

Em suma, para mim, isso é bom de se ter e talvez forçar o usuário a digitar as variáveis ​​exportadas ao usar o componente seja uma coisa boa (código mais legível).

Quando você digitar export type T; , o TS lançará um erro de sintaxe. Além disso, como formular restrições? export type T extends string; lançará mais erros de sintaxe em você. Entendo totalmente sua reserva em relação a um atributo personalizado, mas acho que é a maneira menos perturbadora. Outras maneiras seriam ter nomes de tipo reservados como T1 ou T2 mas isso não é flexível o suficiente (como adicionar restrições?).

A alternativa seria digitar todo o componente via ComponentDef , onde você pode adicionar genéricos livremente, como no exemplo do post inicial. Mas isso vem ao custo de mais digitação.

Para mim, fica claro pela discussão que as pessoas têm opiniões muito diferentes sobre isso e ter mais opções definidas ( ComponentDef , props genéricos como atributos, apenas digitando ComponentEvents ) é o mais flexível para fazem a maioria deles felizes, embora apresentem maneiras diferentes de fazer a mesma coisa.

Eu posso ter perdido isso, mas é dividi-lo em uma opção?

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

Isso reduziria a sobrecarga de digitação para a maioria dos casos?

Sim isso seria possível.

@dummdidumm é possível trabalhar no suporte a interface ComponentSlots {} (com ou sem suporte para genéricos) enquanto isso? Ou introduzir isso seria contraditório com as coisas discutidas aqui?

Não seria, mas antes de continuar a implementação, primeiro quero escrever uma RFC para obter mais feedback da comunidade e dos outros mantenedores sobre a solução proposta. Assim que houver um acordo, começaremos a implementar essas coisas.

@joelmukuthu btw por que você gostaria de ter o suporte de interface? Aprimoramos a inferência de tipo para slots. Existe algum caso em que ainda não esteja funcionando para você? Se sim, estou curioso para ouvir o seu caso.

Agora criei um RFC sobre este tópico: https://github.com/sveltejs/rfcs/pull/38
A discussão sobre a API deve continuar lá.

@dummdidumm desculpe a resposta lenta. Acabei de perceber que, para meu caso de uso, realmente preciso de suporte para genéricos. A inferência de tipos para slots funciona muito bem!

Isso seria enorme! Atualmente, estou triste que o uso de propriedades de slot é sempre um qualquer.

Isso é estranho, o tipo de slot pode ser inferido muito bem neste ponto, se o componente que você usa estiver dentro do seu projeto e usar o TS também.

Talvez eu esteja usando os termos errados aqui... ou espero ter feito algo errado.

Exemplo do que eu gostaria de fazer:

```html


{option.myLabelProp}

{#cada opções como opção} {/cada}

Ok, eu entendo, sim, agora isso não é possível, então você tem que voltar para any[] . Se isso permitisse apenas string[] , seu slot seria digitado como string , que é o que eu quis dizer com "tipo pode ser inferido".

Uma boa solução temporária para componentes de verificação de tipos que encontrei em "Svelte and Sapper in Action" de Mark Volkmann é o uso da biblioteca React prop-types juntamente com $$props.

Este é o código para um item Todo, por exemplo:

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

Enquanto estou validando os adereços de Todo e recebendo erros se algo estiver desativado, também quero ter uma interface para itens Todo, para que eu possa ter verificação de tipo estático no VSCode, bem como preenchimentos automáticos. Isso é obtido usando a interface que defini acima. A desvantagem óbvia e significativa disso é que eu preciso ter código duplicado aqui. Não consigo definir uma interface a partir do objeto propTypes, ou vice-versa.

Eu ainda estou começando em javascript então por favor corrija meu se alguma coisa estiver errada.

Acho que a verificação de tipos é obrigatória para qualquer base de código produtiva, tanto a verificação estática quanto a verificação de tipos de props em tempo de execução.

(Observe que a interface seria definida em context='module' para que seja exportável junto com a exportação padrão do componente, mas omitiu essa parte por brevidade)

Edit: Não tentei isso para nada além de validação de adereços, deixe-me saber se também funcionaria para slots/eventos!

Esta página foi útil?
0 / 5 - 0 avaliações