Language-tools: 引用块范围变量时的插槽道具类型

创建于 2020-07-02  ·  16评论  ·  资料来源: sveltejs/language-tools

描述错误
slot props引用块作用域变量时,它在消费者中具有任何类型

再现
重现行为的步骤:

组件.svelte

<script>
    let list = ['']
</script>

{#each list as value}
  <slot props={value} />
{/each}

消费者

<Component let:name></Component>

其中name的类型为 any

预期行为
有正确的类型

截图
如果适用,请添加屏幕截图以帮助解释您的问题。
圖片
圖片

系统(请填写以下信息):

  • 操作系统:Windows
  • IDE:VSCode
  • 插件/包:Svelte for VSCode,svelte2tsx

附加上下文
得到转变为

<></>;function render() { 
 let list = [''];
 ; <>

 {(list).map((value) => <>

 <slot props={value} />
 </>)}</> return { props: {}, slots: {default: {props:value}} }} export default class { $$prop_def = __sveltets_partial(render().props) $$slot_def = render().slots }
Fixed bug

最有用的评论

也许这真的可以以通用的方式工作:

  • 如果您输入#each ,请执行(__svelte_ts_unwrappAttr(list))
  • 如果您输入#await ,请执行相同操作,但使用__svelte_ts_unwrapPromiseLike(promise)
  • 如果他们使用解构,请使用建议的立即调用的函数@jasonlyu123包装它
  • 如果需要,递归地执行此操作

所有16条评论

声明变量的块可能必须返回 props 定义。 它必须通过多个块。 像这样:

declare function unwrap<T>(value: Promise<T>| T[]): T
const __$$_default_0_prop = unwrap((list).map((value) =>{ <>

 <slot props={value} />
</>; return { props: value }}));<></>; return { props: {}, slots: { default: { ...__$$_default_0_prop }}}

但最大的问题是它需要向上移动,直到它没有嵌套在元素或组件中,以便我们可以事件声明任何变量。

我们还需要检查表达式是否引用了另一个插槽道具。 整个过程太乏味了,我觉得还是不支持比较好,提供一种方法让用户手动定义它的类型。

也许像

interface ComponentDef {
   props?: ... // optional
   slots?: ... // optional
}

这样你就可以明确地定义 props,同时定义 props 和 slot 之间的连接,就像#273 中想要的那样:

interface ComponentDef<T>{
   props: { items: T[]; };
   slots: { item: T };
}

svelte2tsx将检查是否存在名为ComponentDef的接口定义,如果存在,将使用它而不是构建自己的 props/slots 定义。

剩下的一件事是:如何在组件的其余部分中使用该泛型类型?

interface ComponentDef<T>{
   props: { items: T[]; };
   slots: { item: T };
}

export let items: T[]; // <<-- ??? should that be possible?

声明变量的块可能必须返回 props 定义。 它必须通过多个块。 像这样:

declare function unwrap<T>(value: Promise<T>| T[]): T
const __$$_default_0_prop = unwrap((list).map((value) =>{ <>

 <slot props={value} />
</>; return { props: value }}));<></>; return { props: {}, slots: { default: { ...__$$_default_0_prop }}}

但最大的问题是它需要向上移动,直到它没有嵌套在元素或组件中,以便我们可以事件声明任何变量。

为什么允许Promise<T>
我不知道{#each promise as value}是一回事。

那不行吗?

declare function unwrap<T>(value:  T[]): T
function render() {
    let list = ['', 123];

    <>
        {(list).map((item) => {
            return <>
                <slot item={item} />
            </>;
        })}
    </>;

    return {
        props: {},
        slots: {
            default: { item: unwrap(list) }
        }
    };
}

如果我将鼠标悬停在unwrap(list) vscode 会显示string | number

@dummdidumm那么你必须指定道具类型两次? 🤔

我知道,但是如何定义它们之间的关系呢?

我们还可以保留接口名称ComponentPropsComponentSlots ,因此如果插槽和道具之间没有要建模的关系,您可以只定义ComponentSlots - 没有重复。

我在想这样的事情:

declare function unwrap<T>(value:  T[]): T
function render<T>() {
    let list: T[];

    <>
        {(list).map((item) => {
            return <>
                <slot item={item} />
            </>;
        })}
    </>;

    return {
        props: {},
        slots: {
            default: { item: unwrap(list) }
        }
    };
}

但是,您必须使用传递给组件的事物的类型调用渲染函数。
我还没有找到它在哪里被调用。 它在苗条检查中吗? 还是因为 tsx 使渲染功能保持原样?

它被称为无处,这是问题的一部分。

编辑:也许它是通过 jsx 语法间接调用的,但我不知道具体是如何调用的。

我认为即使@PatrickG的解决方案更容易实现,我们仍然可以提供手动键入组件的方法。 它可以作为边缘情况、错误的回退,甚至可以手动输入事件。

interface ComponentEvent {
    on(event: 'click', handler: (e: MouseEvent) => any): void
    on(event: 'some-event', handler: (e: CustomEvent<number>) => void): void
}

如果我们采用接口定义解决方案,这是一个很大的变化。 其他人对此有意见吗?

至于帕特里克的解决方案,我认为我们需要在走 AST 时跟踪变量范围。 每当我们发现插槽道具引用块范围变量时,我们必须将变量和变量的初始值设定项添加到Map 。 然后在更高的范围内。 检查变量初始值设定项及其初始值设定项是否引用块作用域变量。

例如:

<script>
    export let keys = [];
    export let items = [];
</script>

<table>
{#each items as item}
    <tr>
        {#each keys as key}
            <slot value={item[key]}></slot>
        {/each}
    </tr>
{/each}
</table>
  1. keys每块
    键是key ,值是unwrap(keys)
  2. items每块
    键是item ,值是unwrap(items)
<script>
    export let items = [ [ ] ];
</script>

<table>
{#each items as item}
    <tr>
        {#each item as innerItem}
            <slot value={innerItem}></slot>
        {/each}
    </tr>
{/each}
</table>
  1. item每块
    键是innerItem ,值是unwrap(innerItem)
  2. items每块
    键是item ,值是unwrap(items)unwrap(innerItem)变成了unwrap(unwrap(items))

还有一些类型缩小的情况需要考虑:

<script lang="ts">
   export let bla: string | null;
</script>
{#if bla !== null}
   <slot value={bla}></slot> // <--- bla is now of type string
{/if}

---> 我认为这些情况对我们来说很难动态推断/需要做太多工作而收益太少。 我认为在这种情况下让用户明确输入插槽/道具会更好。

编辑:我突然想到:如果我们采用接口方法,我们如何确保接口正确实现? 因此,例如,如果有人输入interface ComponentSlots { slot1: number }但输入<slot slot1={'a string'}></slot> ,我们如何捕获该类型错误?

我们不需要变量本身的 Map,只需要变量在<slot />

我发现https://www.typescriptlang.org/docs/handbook/jsx.html#attribute -type-checking - 也许这会有所帮助。

在浏览 Htmlx AST 时,没有关于变量类型的信息。 即使它是一个打字稿 AST,也没有类型信息,只有与它关联的类型节点。

一种疯狂的转换方式,这样我们就不必关心解构了

function render() {
    let list = [];

    <>
        {(list).map(({ a }) => {
            return <>
                <slot props={a} />
            </>;
        })}
    </>;

    return {
        props: {},
        slots: {
            default: { props: (({ a }) => a)(__sveltets_unwrapArr(list)) }
        }
    };
}

也许这真的可以以通用的方式工作:

  • 如果您输入#each ,请执行(__svelte_ts_unwrappAttr(list))
  • 如果您输入#await ,请执行相同操作,但使用__svelte_ts_unwrapPromiseLike(promise)
  • 如果他们使用解构,请使用建议的立即调用的函数@jasonlyu123包装它
  • 如果需要,递归地执行此操作
此页面是否有帮助?
0 / 5 - 0 等级