Language-tools: Ввод реквизитов/событий/слотов компонента Svelte

Созданный на 11 авг. 2020  ·  24Комментарии  ·  Источник: sveltejs/language-tools

По этому поводу уже есть несколько вопросов (№ 424, № 304, № 273, № 263), но я хотел сделать общий, чтобы обсудить подходы, которые мы можем использовать.
Связанный PR: # 437

Обратите внимание, что эта проблема НЕ связана с вводом файла d.ts , она появится позже как отдельная проблема.

Ваш запрос функции связан с проблемой?
На данный момент невозможно ввести входы/выходы компонента при определенных обстоятельствах:

  • Невозможно определить общую связь между реквизитами и событиями/слотами (дженериками).
  • Невозможно определить события и их типы
  • Невозможно определить слоты и их типы определенным образом.

Опишите желаемое решение
Способ явного ввода реквизитов/событий/слотов.

Предложение: новый зарезервированный интерфейс ComponentDef , который при определении используется как общедоступный API компонента вместо того, чтобы делать выводы из кода.

Пример (с комментариями о том, что, скорее всего, невозможно):

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

Когда кто-то сейчас использует компонент, он получит правильные типы из props/events/slots, а также диагностику ошибок, когда что-то не так:

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

Если это работает и немного протестировано, мы могли бы улучшить его с помощью других зарезервированных интерфейсов, таких как ComponentEvents , чтобы вводить только одну из вещей.

Я хотел бы получить как можно больше отзывов об этом, потому что это, вероятно, большое изменение, которое можно было бы использовать широко. Кроме того, я хотел бы, чтобы некоторые из членов основной команды взглянули на это, если это выглядит хорошо для них или представляет что-то, с чем они не согласны.

@jasonlyu123 @orta @Conduitry @antony @pngwn

enhancement

Самый полезный комментарий

Возможно, я использую неправильные термины здесь... или, надеюсь, я просто сделал что-то не так.

Пример того, что я хотел бы сделать:

```html


{option.myLabelProp}

{#каждый вариант как вариант} {/каждый}

Все 24 Комментарий

Кажется разумным, но на самом деле существует проблема Svelte, связанная с возможностью внедрения слотов во время выполнения. https://github.com/sveltejs/svelte/issues/2588 и PR, который его реализует https://github.com/sveltejs/svelte/pull/4296 , кажется, что может быть совпадение или, по крайней мере, какая-то возможность выровняйте интерфейсы (если есть консенсус, есть еще некоторые нерешенные вопросы с вышеуказанным PR).

Спасибо за PR-ссылку, похоже, это связано только с тем, что нам нужно вводить конструктор немного по-другому, но я думаю, что это не зависит от проверки типов на уровне шаблона.

интересно.
Интересно, могли бы мы сделать что-то вроде

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

который удалит type T = unknown во время проверки типа и вместо этого добавит его в качестве общего аргумента к компоненту.

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

Хорошая идея добавить его в функцию render !

Я думаю, что мы можем получить те же результаты с

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

путем извлечения T из определения интерфейса.

Хотя с вашим решением вам придется печатать меньше в случае «Я просто хочу, чтобы между моими реквизитами и слотами была общая связь», что приятно. С одной стороны, я думаю: «Да, мы могли бы просто добавить оба», с другой стороны, это похоже на слишком большое расширение поверхности API (вы можете делать одни и те же вещи по-разному) - не уверен.

Я действительно не хочу выписывать interface ComponentDef<T>{ props: {} } и сопоставлять его с каждым из моих экспортов. Svelte так хорошо справляется с сокращением количества набираемых символов по сравнению с React, и это похоже на шаг назад. В частности, это требует дублирования типов каждого экспорта в пропсы, что не очень весело (и неизбежно приводит к частым проблемам).

Мне нравится ход мыслей @halfnelson . Экспорт должен быть обнаружен как реквизит. Я не знаю, как выглядит модуль один раз

Еще одно быстрое замечание, как я читал в некоторых связанных выпусках: у меня не было конца проблемам с использованием комментариев JSDoc для ввода вещей, включая параметр @template .

Хотя мы должны непредвзято относиться к JSDoc (даже используя JSDoc в HTML), я должен предупредить, что независимо от того, используется ли он через WebStorm или VS Code, он просто не выразителен, как TypeScript. например, он не реализует тип true ; Я уверен, что он не работает с индексными типами; и, насколько я помню, у вас тоже не может быть Record<keyof X, any> . Я продолжаю натыкаться на стены с ним. @template работало, но тоже было довольно ограничено. И я думаю, что это работает по-разному в зависимости от IDE.

Передача универсального типа элемента (jsx) для меня недопустима, потому что это недопустимый синтаксис шаблона svelte. Это также не должно быть необходимо, поскольку дженерики управляются свойствами, я не могу придумать способ ввести универсальную зависимость только для слотов/событий. Если они управляются входными свойствами, передача дженериков в jsx-элементы не требуется, потому что мы можем сгенерировать код svelte2tsx таким образом, что TS выведет его за нас.

О накладных расходах на ввод: это правильно, но потребуется только в ситуациях, когда вы хотите использовать дженерики и/или явно указывать события/слоты. Теоретически мы могли бы сделать все это сами, но это кажется очень сложным для реализации. Примерами таких сложных для реализации проблем являются поддержка расширенных сценариев слотов, таких как в #263, и сбор всех возможных событий компонентов (что легко, если пользователь будет использовать только createEventDispatcher , но он также может импортировать функцию, которая обертывает вещи createEventDispatcher ).

В общем, я чувствую, что есть много ситуаций, когда люди хотели бы определить только одну из этих вещей, но не другие, поэтому, возможно, действительно необходимо предоставить все различные варианты, чтобы сохранить дух «меньшего типа» Svelte.

Это будет означать:

  • ComponentDef — это все в одном, если вам это нужно
  • ComponentEvents предназначен только для ввода событий
  • ComponentSlots только для слотов ввода
  • конструкция типа <script generic="T"> , если вам нужны только дженерики
  • комбинация ComponentEvents/ComponentSlots/generic="T"

@shirakaba Поддержка типов JSDoc не обязательно должна быть такой многофункциональной, как машинописный текст. Я сомневаюсь, что многие люди будут набирать с ним общие компоненты. Кроме того, поскольку мы используем языковую службу машинописного текста, многие расширенные шрифты можно легко импортировать из файла машинописного текста.

Что касается проверки типа реквизита слота, у меня есть кое-какой хак, но я не знаю, приведет ли это к хорошему опыту разработчика. Если компонент пользовательского типа выглядит следующим образом:

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

мы можем сгенерировать класс

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

и преобразовать слот по умолчанию в

<DeafaultSlot />

(только как пользователь) Для меня лишний набор текста не проблема, потому что мне все равно приходится делать много этого при работе с TypeScript.

Что касается интерфейса по сравнению с реквизитом generic , поскольку бывают случаи, когда переменные доступны только потребителю, было бы лучше, чтобы DX поддерживал реквизит. Но в этой заметке, возможно ли поддерживать export type T вместо generic="T" ?

Это недопустимый синтаксис TS, который может запутать людей. Кроме того, нам пришлось бы выполнить дополнительный шаг преобразования, который не будет выполняться, если компонент Svelte находится в некомпилируемом состоянии. В этом случае языковые инструменты возвращаются к тому, что есть в скрипте, который тогда будет иметь этот недопустимый синтаксис. Так что я против этого.

Я понимаю. Не задерживаться, но это неверный синтаксис или ошибка «неопределенного/неизвестного типа»? Я спрашиваю, потому что не знаю, как компилятор TypeScript обрабатывает эти два случая. У меня просто есть оговорки против добавления пользовательского атрибута в тег script , особенно с таким общим именем, как «общий» :)

В целом, для меня это приятно иметь и, возможно, заставить пользователя вводить экспортированные переменные при использовании компонента — это хорошо (более читаемый код).

Когда вы набираете export type T; , TS выдает синтаксическую ошибку. Кроме того, как сформулировать ограничения? export type T extends string; выдаст вам больше синтаксических ошибок. Я полностью понимаю вашу оговорку против пользовательского атрибута, но я думаю, что это наименее разрушительный способ. Другими способами могут быть зарезервированные имена типов, такие как T1 или T2 , но это недостаточно гибко (как добавить ограничения?).

Альтернативой может быть ввод всего компонента через ComponentDef , где вы можете свободно добавлять дженерики, как в примере в стартовом посте. Но это происходит за счет большего набора текста.

Для меня становится ясно из обсуждения, что люди имеют очень разные мнения по этому поводу, и иметь больше опций ( ComponentDef , общие реквизиты в качестве атрибутов, только ввод ComponentEvents ) является наиболее гибким для делают большинство из них счастливыми, хотя и предлагают разные способы делать одно и то же.

Я, возможно, пропустил это, но разделить его вариант?

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

Снизит ли это накладные расходы на ввод в большинстве случаев?

Да, это было бы возможно.

@dummdidumm можно ли тем временем работать над поддержкой interface ComponentSlots {} (с поддержкой дженериков или без нее)? Или введение этого будет противоречить обсуждаемым здесь вещам?

Это не так, но прежде чем продолжить реализацию, я сначала хочу написать RFC, чтобы получить больше отзывов от сообщества и других сопровождающих о предлагаемом решении. Как только будет достигнуто соглашение, мы начнем реализовывать эти вещи.

@joelmukuthu кстати, почему вы хотите иметь поддержку интерфейса? Мы улучшили вывод типов для слотов, есть ли случаи, когда у вас все еще не работает? Если так, мне любопытно услышать ваше дело.

Теперь я создал RFC по этой теме: https://github.com/sveltejs/rfcs/pull/38 .
Обсуждение API следует продолжить там.

@dummdidumm извините за медленный ответ. Я только что понял, что для моего варианта использования мне действительно нужна поддержка дженериков. Вывод типа для слотов работает очень хорошо!

Это было бы грандиозно! В настоящее время я опечален тем, что использование свойств слота всегда имеет значение any.

Это странно, типизированный слот может быть довольно хорошо выведен на этом этапе, если используемый вами компонент находится в вашем проекте и также использует TS.

Возможно, я использую неправильные термины здесь... или, надеюсь, я просто сделал что-то не так.

Пример того, что я хотел бы сделать:

```html


{option.myLabelProp}

{#каждый вариант как вариант} {/каждый}

Хорошо, я понимаю, да, сейчас это невозможно, поэтому вам нужно вернуться к any[] . Если бы это позволяло только string[] , ваш слот был бы напечатан как string , что я и имел в виду под «тип может быть выведен».

Хорошее временное решение для проверки типов компонентов, с которым я столкнулся в «Svelte and Sapper в действии» Марка Фолькманна, — это использование библиотеки React prop-types вместе с $$props.

Это код для элемента Todo, например:

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

Пока я проверяю реквизиты Todo и получаю сообщения об ошибках, если что-то не так, я также хочу иметь интерфейс для элементов Todo, чтобы иметь статическую проверку типов в VSCode, а также автозаполнение. Это достигается с помощью интерфейса, который я определил выше. Очевидным и существенным недостатком этого является то, что здесь мне нужно дублировать код. Я не могу определить интерфейс из объекта propTypes или наоборот.

Я все еще начинаю работать с javascript, поэтому, пожалуйста, исправьте меня, если что-то не так.

Я думаю, что проверка типов обязательна для любой продуктивной кодовой базы, как для статической проверки, так и для проверки типов свойств во время выполнения.

(Обратите внимание, что интерфейс будет определен в context='module', поэтому его можно будет экспортировать вместе с экспортом компонента по умолчанию, но для краткости эта часть опущена)

Изменить: не пробовал это ни для чего, кроме проверки реквизита, дайте мне знать, будет ли это работать для слотов/событий!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги