Language-tools: 键入 Svelte 组件道具/事件/插槽

创建于 2020-08-11  ·  24评论  ·  资料来源: sveltejs/language-tools

已经有几个关于这个的问题(#424、#304、#273、#263),但我想制作一个合并的问题来讨论我们可以采取的方法。
相关公关:#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: {} }并将其与我的每个出口对齐。 与 React 相比,Svelte 在减少输入字符方面做得很好,这感觉像是倒退了一步。 特别是,它需要将每个导出的类型复制到道具中,这很不好玩(并且必然会导致频繁的问题)。

我喜欢@halfnelson的思路。 出口应被检测为道具。 我不知道模块曾经是什么样子

另一个快速的,当我在一些相关问题中读到它时:我在使用 JSDoc 注释键入内容时遇到了无穷无尽的麻烦,包括@template选项。

虽然我们应该对 JSDoc 持开放态度(即使在 HTML 中使用 JSDoc),但我必须警告说,无论是通过 WebStorm 还是 VS Code 使用,它都不像 TypeScript 那样具有表现力。 例如,它没有实现true类型; 我确定它不做索引类型; 如果我记得,你也不能有Record<keyof X, any> 。 我一直用它撞墙。 @template确实有效,但也非常有限。 而且我认为它的工作方式也因 IDE 而异。

传递泛型类型 (jsx) 元素对我来说是不行的,因为它不是有效的苗条模板语法。 它也不应该是必需的,因为泛型是由属性驱动的,我想不出一种方法来只为插槽/事件引入泛型依赖项。 如果它们由输入属性驱动,则不需要将泛型传递给 jsx 元素,因为我们可以生成svelte2tsx代码,TS 会为我们推断它。

关于打字开销:这是正确的,但仅在您想要使用泛型和/或专门键入事件/插槽的情况下才需要。 理论上,我们可以自己推断所有这些,但这感觉很难实现。 此类难以实现的问题的示例是支持高级插槽场景,如 #263,并收集所有可能的组件事件(如果用户只使用createEventDispatcher ,这很容易,但他也可以导入一个函数包装createEventDispatcher东西)。

总的来说,我觉得在很多情况下人们只想定义其中的一个,而不是其他的,所以也许确实有必要提供所有不同的选项来保持 Svelte 的“少类型”精神。

这意味着:

  • ComponentDef是万能的,如果你需要的话
  • ComponentEvents仅用于输入事件
  • ComponentSlots仅用于打字插槽
  • 如果您只需要泛型,则可以使用<script generic="T">之类的构造
  • ComponentEvents/ComponentSlots/generic="T"的组合

@shirakaba JSDoc 类型支持不必像打字稿那样功能丰富。 我怀疑很多人会用它输入通用组件。 另外因为我们使用的是 typescript 的语言服务,很多高级类型可以很容易地从 typescript 文件中导入。

关于插槽道具类型检查,我有一些技巧,但不知道这是否会带来良好的开发人员体验。 如果用户键入这样的组件:

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

我们可以生成一个类

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

并将默认插槽转换为

<DeafaultSlot />

(仅作为用户加入)对我来说,额外的输入不是问题,因为在使用 TypeScript 时我必须做很多事情。

至于接口 vs generic prop,由于存在变量只暴露给消费者的情况,因此最好 DX 支持 prop。 但在那一点上,是否可以支持export type T而不是generic="T"

这是无效的 TS 语法,可能会使人感到困惑。 此外,我们还必须进行额外的转换步骤,如果 Svelte 组件处于不可编译状态,则不会这样做。 在这种情况下,语言工具会退回到脚本中的内容,这样就会有这种无效的语法。 所以我反对。

我明白。 不要逗留,但它是无效的语法还是更多的“未定义/未知类型”错误? 我问是因为我不知道 TypeScript 的编译器如何处理这两种情况。 我只是对将自定义属性添加到script标记持保留意见,尤其是名称与“通用”一样通用:)

总而言之,对我来说,这是一个不错的选择,也许在使用组件时强制用户键入导出的变量是一件好事(更易读的代码)。

当你输入export type T;时,TS 会抛出一个语法错误。 另外,如何制定约束? export type T extends string;会给你带来更多的语法错误。 我完全理解您对自定义属性的保留,但我认为这是干扰最小的方式。 其他方法是保留类型名称,如T1T2但这不够灵活(如何添加约束?)。

另一种方法是通过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很抱歉响应缓慢。 我刚刚意识到,对于我的用例,我确实需要对泛型的支持。 插槽的类型推断效果很好!

这将是巨大的! 我目前很难过使用插槽属性始终是任何。

这很奇怪,如果您使用的组件在您的项目中并且也使用 TS,那么此时可以很好地推断出插槽类型。

也许我在这里使用了错误的术语......或者希望我只是做错了什么。

我希望我能做的例子:

```html


{option.myLabelProp}

{#每个选项作为选项} {/每个}

好的,我明白了,是的,现在这是不可能的,所以你必须退回到any[] 。 如果这仅允许string[] ,则您的插槽将输入为string ,这就是我所说的“可以推断类型”。

我在 Mark Volkmann 的“Svelte and Sapper in Action”中遇到的一个很好的类型检查组件的临时解决方案是使用 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 的 props 并在出现问题时得到错误,但我还希望有一个 Todo 项的接口,以便我可以在 VSCode 中进行静态类型检查以及自动完成。 这是通过使用我上面定义的接口来实现的。 这样做的明显和重要的缺点是我需要在这里有重复的代码。 我无法从 propTypes 对象定义接口,反之亦然。

我仍然从 javascript 开始,所以如果有任何错误,请纠正我。

我认为类型检查对于任何生产性代码库都是必须的,包括运行时的静态检查和 props 类型检查。

(请注意,接口将在 context='module' 中定义,因此它可以与组件默认导出一起导出,但为简洁起见省略了该部分)

编辑:除了道具验证之外,没有尝试过这个,让我知道它是否也适用于插槽/事件!

此页面是否有帮助?
0 / 5 - 0 等级