描述错误
对于嵌套条件检查,显示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
上抛出错误。
预期行为
预期不会引发任何错误。
系统(请完成以下信息):
额外的背景
如果删除了嵌套的条件语句,并在TestObject接口中将author?
替换为author
(使其成为必需),则TypeScript为{test.author}
抛出相同的错误是合乎逻辑的,但事实并非如此。 因此看起来错误是由嵌套条件语句触发的,没有它,TypeScript知道test
不为null。
不知道我们该怎么办。 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
, #each
, let:X
。 这是由于我们必须转换代码的方式。 我们没有忘记这个问题,并且仍在寻找以良好方式解决此问题的方法。现在,您不能在标记中使用打字稿。 无法预处理标记或让编译器解析打字稿语法。 因此,非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,它可能会改变”,但另一方面也可能会说:“如果value
是null
,我将永远不会进入该点击处理程序
我将为此开一个不同的问题,因此我们可以单独讨论。
我认为您不值得花时间讨论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😄
最有用的评论
小更新/摘要:
#await
,#each
,let:X
。 这是由于我们必须转换代码的方式。 我们没有忘记这个问题,并且仍在寻找以良好方式解决此问题的方法。