Language-tools: TypeScript在检查变量的内部条件中错误地认为变量可能为null

创建于 2020-10-18  ·  20评论  ·  资料来源: sveltejs/language-tools

描述错误
对于嵌套条件检查,显示TypeScript错误“对象可能为空”。

重现
简单的测试用例:

<script lang="typescript">
    interface TestObject {
        author?: string;
    }
    export let test: TestObject | null;
</script>

{#if test}
    <div>
        {#if test.author}
            <div>Author: {test.author}</div>
        {/if}
    </div>
{/if}

TypeScript在{#if test.author}行上抛出错误Object is possibly null ,而不是在该行内的test上抛出错误。

预期行为
预期不会引发任何错误。

系统(请完成以下信息):

  • 作业系统:OSX 10.15.7
  • IDE:VSCode
  • 插件/软件包:“ Svelte for VSCode”

额外的背景
如果删除了嵌套的条件语句,并在TestObject接口中将author?替换为author (使其成为必需),则TypeScript为{test.author}抛出相同的错误是合乎逻辑的,但事实并非如此。 因此看起来错误是由嵌套条件语句触发的,没有它,TypeScript知道test不为null。

Fixed bug

最有用的评论

小更新/摘要:

  • 一旦代码位于以下构造之一中,控制流就会重置: #await#eachlet:X 。 这是由于我们必须转换代码的方式。 我们没有忘记这个问题,并且仍在寻找以良好方式解决此问题的方法。
  • 现在,只要不被第一个项目要点中提到的事情打断,嵌套的if条件现在应该可以按预期工作

所有20条评论

不知道我们该怎么办。 svelte2tsx将其转换为:

() => (<>

{() => {if (test){<>
    <div>
        {() => {if (test.author){<>
            <div>Author: {test.author}</div>
        </>}}}
    </div>
</>}}}</>);

因为它包装在函数中,所以控制流类型分析不会生效。 而且我们无法删除该函数或将其包装到立即调用的函数中,因为表达式在依赖项更改时会重新运行,因此我们肯定需要使某些控制流类型无效。

我们可以把它变成三元吗? 如果没有else块,则在else情况下使用一个空字符串。
但是更大的问题可能是我们不允许在任何转换中使用类似的函数-不确定现在是否是这种情况

也许允许! 句法? {#if test!.author} 。 这是有效的TypeScript语法,但目前无法在模板中使用。

现在,您不能在标记中使用打字稿。 无法预处理标记或让编译器解析打字稿语法。 因此,非null断言是不可能的。
没有尝试过可行性购买,也许我们可以复制上层条件并将其附加在嵌套条件之前

 {() => {if (test && test.author){<>
            <div>Author: {test.author}</div>
        </>}}}

临时解决方法:

<script lang="typescript">
    interface TestObject {
        author?: string;
    }
    export let test: TestObject | null;

    let requitedTest: Required<TestObject>;
    $: {
        requiredTest = test as unknown as Required<TestObject>;
    }
</script>

{#if test}
    <div>
        {#if requiredTest.author}
            <div>Author: {requiredTest.author}</div>
        {/if}
    </div>
{/if}

复制变量,分配不同的类型并在嵌套条件下使用它。 它很丑陋,需要小心使用,但确实可以消除警告。

现在更实用的解决方法是

{#if test}
    <div>
        {#if test?.author}
            <div>Author: {test.author}</div>
        {/if}
    </div>
{/if}

这很烦人,尤其是当它不仅在同一个对象中钻取而是制作单独的条件时:

{#if bigSwitch}
    <div>
        {#if smallSwitch}
            <MyComponent {bigSwitch} />
        {/if}
    </div>
{/if}

那行不通。 模板中不允许使用TypeScript。

似乎在这里工作。 test && test.author

如果您的意思是可选链接,那是一个新的javascript功能,而不仅仅是打字稿功能。 在3.24.0之后可用

谢谢! 我一直以为这是TS功能,类似于非null语句foo! 。 这使它变得更加简单。

总是学习新的东西:)

没有尝试过可行性购买,也许我们可以复制上层条件并将其附加在嵌套条件之前

 {() => {if (test && test.author){<>
          <div>Author: {test.author}</div>
      </>}}}

我朝那个方向做了一些测试,我认为这不适用于所有情况。 它可能会使#if错误消失,但是一旦您在其中使用#each#await块,它们就会重新出现。 如果变量是const ,则不会出现这些错误,因为TS控制流随后知道条件仍然必须为真,因为无法重新分配变量。 因此,解决此问题的最佳方法是将所有内容转换为const变量,或将所有变量重新分配给模板的临时其他变量。 尽管这会带来自己的问题,例如悬停信息,重命名,去声明等。也许可以通过使用逗号表达式来解决,并将{#if foo}{#if (foo, generatedConstFoo)} 。 底线:这将需要相当多的黑客。

也许“附加上层if块”是正确的。 假设每个/等待块中的条件仍然为真是不安全的。 例如,这将引发运行时错误( toUpperCase is not a function ):

<script>
    let name = 'name';
    function setNameToNumber(){
        name = 1
        return ['a']
    }
</script>

{#if name}
    <h1>Hello {name.toUpperCase()}!</h1>
    {#each setNameToNumber() as item}
        item
    {/each}
{/if}

可以为等待块构造相同的对象。

小更新/摘要:

  • 一旦代码位于以下构造之一中,控制流就会重置: #await#eachlet:X 。 这是由于我们必须转换代码的方式。 我们没有忘记这个问题,并且仍在寻找以良好方式解决此问题的方法。
  • 现在,只要不被第一个项目要点中提到的事情打断,嵌套的if条件现在应该可以按预期工作

现在,您不能在标记中使用打字稿。 无法预处理标记或让编译器解析打字稿语法。 因此,非null断言是不可能的。
没有尝试过可行性购买,也许我们可以复制上层条件并将其附加在嵌套条件之前

 {() => {if (test && test.author){<>
          <div>Author: {test.author}</div>
      </>}}}

现在,以声明$store -variables的方式固定了#493,我们可以重新考虑这个想法。 我的想法是在所有这样的if条件之前添加( #each为例):

{#if somecondition && anothercondition}
   {#each ..}
     ..
   {/each}
{/if}

出去

<>{__sveltets_each((true, items), (item) => (somecondition && anothercondition) && <>
    ...
</>)}</>

棘手的部分是正确设置elseif / else和嵌套逻辑。

这应该在VS Code版本104.6.3 / svelte-check版本1.2.4中修复。 请让我知道它现在是否适合您。

我发现了一个问题-该模板现在可以在模板中正常运行,但在事件监听器中却无法正常运行。 我不确定是否应该为此打开一个新书,但这是一个复制品:

<script lang="ts">
  let value: string | null = null;

  function acceptsString(value: string) {
    console.log(value);
  }
</script>

{#if value}
  <!-- Argument of type 'string | null' is not assignable to parameter of type 'string'.
  Type 'null' is not assignable to type 'string'. -->
  <button on:click={() => acceptsString(value)} />
{/if}

我发现了一个问题-该模板现在可以在模板中正常运行,但在事件监听器中却无法正常运行。 我不确定是否应该为此打开一个新书,但这是一个复制品:

<script lang="ts">
  let value: string | null = null;

  function acceptsString(value: string) {
    console.log(value);
  }
</script>

{#if value}
  <!-- Argument of type 'string | null' is not assignable to parameter of type 'string'.
  Type 'null' is not assignable to type 'string'. -->
  <button on:click={() => acceptsString(value)} />
{/if}

不过,这只是常规的TS行为。 您的值是在将来可能随时返回到null的函数中引用的。

两者都是有效的视图。 一个人可能会说“因为value不是const,它可能会改变”,但另一方面也可能会说:“如果valuenull ,我将永远不会进入该点击处理程序

我将为此开一个不同的问题,因此我们可以单独讨论。

我认为您不值得花时间讨论TS自己的旧选择:p
例如,如果在他们或他们的父母的级别上有一个out过渡,按钮是否会停留在value === null上?

严格来说,在进行这些放松的更改之前,控制流程是正确的,因为可以在每个循环或等待中修改其他变量。

<script lang="ts">
  let foo: string | null = 'bar';
  function getItems() {
     foo = null;
     return [''];
  }
</script>

{#if foo}
  {#each getItems() as item}
    {foo.toUpperCase()} <!-- boom -->
  {/each}
{/if}

..但这是谁? 您也可以在TS中伪造相同的运行时错误。 因此,在DX中,与其说“这是100%正确的”,不如说是一个权衡。

但是在这里我们进一步讨论之前,请先将讨论转到#876😄

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

相关问题

joelmukuthu picture joelmukuthu  ·  14评论

illright picture illright  ·  39评论

jasonlyu123 picture jasonlyu123  ·  28评论

orta picture orta  ·  15评论

benmccann picture benmccann  ·  14评论