バグを説明する
ネストされた条件チェックに対して、TypeScriptエラー「オブジェクトがnullである可能性があります」が表示されます。
再現するには
簡単なテストケース:
<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}
、特にその行内のtest
ではなく、エラーObject is possibly null
をスローします。
予想される行動
エラーをスローしないことが期待されます。
システム(以下の情報を入力してください):
追加のコンテキスト
ネストされた条件ステートメントが削除され、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構文ですが、現在、テンプレートでは使用できません。
現在、マークアップで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
それでは。
オプションのチェーンを意味する場合、それは単なるtypescript機能ではなく、新しいjavascript機能です。 3.24.0以降はsvelteで利用可能です
ありがとう! null以外のステートメントfoo!
似たTS機能だといつも思っていました。 それはそれをはるかに簡単にします。
常に新しいことを学ぶ:)
実行可能性の購入を試したことがない場合は、上位レベルの条件をコピーして、ネストされた条件の前に追加できます。
{() => {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-blockについても同じことが言えます。
小さな更新/要約:
#await
、 #each
、 let:X
いずれかの構造内に入るとすぐに、制御フローがリセットされます。 これは、コードを変換する方法によるものです。 私たちはこの問題を忘れていませんが、これを良い方法で解決する方法をまだ探しています。現在、マークアップでtypescriptを使用することはできません。 マークアップを前処理したり、コンパイラにタイプスクリプト構文を解析させたりする方法はありません。 したがって、null以外のアサーションは不可能です。
実行可能性の購入を試したことがない場合は、上位レベルの条件をコピーして、ネストされた条件の前に追加できます。{() => {if (test && test.author){<> <div>Author: {test.author}</div> </>}}}
#493が$store
変数を宣言するように修正されたので、このアイデアを再検討できます。 私の考えは、次のようなすべてのif条件を前に付けることです(例として#each
):
に
{#if somecondition && anothercondition}
{#each ..}
..
{/each}
{/if}
でる
<>{__sveltets_each((true, items), (item) => (somecondition && anothercondition) && <>
...
</>)}</>
トリッキーな部分は、elseif / elseとネストロジックを正しくすることです。
これは、VSCodeバージョン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
は定数ではないため、変更される可能性があります」と言うこともできますが、「 value
がnull
場合、そのクリックハンドラーに到達することはありません」と言うこともできます。
これについては別の問題を開いて、個別に説明できるようにします。
TS自身の古い選択について話し合うのは時間の価値がないと思います:p
たとえば、親または親のレベルでアウトトランジションがある場合、ボタンは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でも同じランタイムエラーを作成できます。 したがって、「これは100%正しい」というよりも、DXのトレードオフになります。
ただし、ここでこれについてさらに説明する前に、この説明を#876に行ってください😄
最も参考になるコメント
小さな更新/要約:
#await
、#each
、let:X
いずれかの構造内に入るとすぐに、制御フローがリセットされます。 これは、コードを変換する方法によるものです。 私たちはこの問題を忘れていませんが、これを良い方法で解決する方法をまだ探しています。