Language-tools: TypeScript betrachtet eine Variable fälschlicherweise als möglicherweise null innerhalb einer Bedingung, die die Variable überprüft

Erstellt am 18. Okt. 2020  ·  20Kommentare  ·  Quelle: sveltejs/language-tools

Beschreibe den Fehler
Der TypeScript-Fehler "Objekt ist möglicherweise null" wird für verschachtelte bedingte Prüfungen angezeigt.

Fortpflanzen
Einfacher Testfall:

<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 löst den Fehler Object is possibly null in Zeile {#if test.author} , nicht speziell in test innerhalb dieser Zeile.

Erwartetes Verhalten
Erwartet, keine Fehler zu werfen.

System (bitte folgende Angaben machen):

  • Betriebssystem: OSX 10.15.7
  • IDE: VSCode
  • Plugin / Paket: "Svelte for VSCode"

Zusätzlicher Kontext
Wenn verschachtelte bedingte Anweisungen entfernt und in der TestObject-Schnittstelle author? durch author (um dies erforderlich zu machen), ist es für TypeScript logisch, denselben Fehler für {test.author} , aber das tut es nicht. Es sieht also so aus, als würde ein Fehler durch eine verschachtelte bedingte Anweisung ausgelöst, ohne die TypeScript weiß, dass test nicht null ist.

Fixed bug

Hilfreichster Kommentar

Kleines Update / Zusammenfassung:

  • Der Kontrollfluss wird zurückgesetzt, sobald sich der Code in einem dieser Konstrukte befindet: #await , #each , let:X . Dies liegt an der Art und Weise, wie wir den Code transformieren müssen. Wir haben dieses Problem nicht vergessen und suchen immer noch nach Möglichkeiten, dies auf gute Weise zu lösen.
  • Verschachtelte if-Bedingungen sollten jetzt wie erwartet funktionieren, solange sie nicht durch eines der im ersten Aufzählungspunkt genannten Dinge unterbrochen werden

Alle 20 Kommentare

Ich weiß nicht, was wir tun können, um damit umzugehen. svelte2tsx transformiere es in folgendes:

() => (<>

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

weil es in eine Funktion eingebunden ist, damit die Analyse des Kontrollflusstyps nicht wirksam wird. Und wir können die Funktion nicht entfernen oder in eine sofort aufgerufene Funktion einschließen, da der Ausdruck erneut ausgeführt wird, wenn die Abhängigkeit unterbrochen wird. Wir müssen definitiv einen Teil des Kontrollflusstyps ungültig machen.

Könnten wir das in ternäre verwandeln? Wenn es keinen anderen Block gibt, verwenden wir nur eine leere Zeichenfolge für den else-Fall.
Das größere Problem könnte jedoch sein, dass wir in keiner der Transformationen solche Funktionen verwenden dürfen - nicht sicher, ob dies derzeit der Fall ist

Vielleicht erlauben! Syntax? {#if test!.author} . Das ist eine gültige TypeScript-Syntax, die derzeit jedoch nicht in Vorlagen verwendet werden kann.

Im Moment können Sie kein Typoskript im Markup verwenden. Es gibt keine Möglichkeit, Markups vorzuverarbeiten oder den Compiler die Typoskript-Syntax analysieren zu lassen. Eine Nicht-Null-Behauptung ist also nicht möglich.
Ich habe den Kauf der Lebensfähigkeit noch nicht ausprobiert. Vielleicht könnten wir die Bedingung der oberen Ebene kopieren und vor der verschachtelten Bedingung anhängen

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

Temporäre Problemumgehung:

<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}

Variable kopieren, anderen Typ zuweisen und in verschachteltem Zustand verwenden. Es ist hässlich und muss vorsichtig verwendet werden, aber es beseitigt Warnungen.

Eine pragmatischere Problemumgehung wäre vorerst

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

Es ist ziemlich ärgerlich, besonders wenn es nicht nur darum geht, in dasselbe Objekt zu bohren, sondern separate Bedingungen zu schaffen:

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

Das wird nicht funktionieren. TypeScript ist in Vorlagen nicht zulässig.

Scheint hier zu arbeiten. test && test.author dann.

Wenn Sie optionale Verkettung meinen, ist dies eine neue Javascript-Funktion, nicht nur eine Typoskript-Funktion. Es ist in schlank nach 3.24.0 verfügbar

Vielen Dank! Ich habe immer angenommen, dass es sich um eine TS-Funktion handelt, die der Nicht-Null-Anweisung foo! ähnelt. Das macht es viel einfacher.

Immer etwas Neues lernen :)

Ich habe den Kauf der Lebensfähigkeit noch nicht ausprobiert. Vielleicht könnten wir die Bedingung der oberen Ebene kopieren und vor der verschachtelten Bedingung anhängen

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

Ich habe einige Tests in diese Richtung durchgeführt und ich denke, dass dies nicht in allen Fällen funktionieren wird. Es kann Fehler für #if Schweigen bringen, aber sie werden wieder angezeigt, sobald Sie einen #each oder #await Block darin verwenden. Diese Fehler werden nicht angezeigt, wenn die Variable const , da der TS-Steuerungsfluss dann weiß, dass die Bedingung weiterhin erfüllt sein muss, da die Variable nicht neu zugewiesen werden kann. Der beste Weg, dies zu lösen, besteht darin, alles in const -Variablen umzuwandeln oder alle Variablen temporären anderen Variablen für die Vorlage zuzuweisen. Dies bringt jedoch seine eigenen Probleme mit sich, wenn es um Hover-Informationen, Umbenennen, Go-to-Deklaration usw. geht. Vielleicht könnte dies mithilfe von Kommaausdrücken umgangen und {#if foo} in {#if (foo, generatedConstFoo)} . Fazit: Dies erfordert einige Hackerangriffe.

Vielleicht ist das "Anhängen von If-Blöcken der oberen Ebene" doch richtig. Es ist nicht sicher anzunehmen, dass die Bedingungen in jedem / warte-Block immer noch zutreffen. Dies löst beispielsweise einen Laufzeitfehler aus ( 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}

Das gleiche kann für einen Warteblock konstruiert werden.

Kleines Update / Zusammenfassung:

  • Der Kontrollfluss wird zurückgesetzt, sobald sich der Code in einem dieser Konstrukte befindet: #await , #each , let:X . Dies liegt an der Art und Weise, wie wir den Code transformieren müssen. Wir haben dieses Problem nicht vergessen und suchen immer noch nach Möglichkeiten, dies auf gute Weise zu lösen.
  • Verschachtelte if-Bedingungen sollten jetzt wie erwartet funktionieren, solange sie nicht durch eines der im ersten Aufzählungspunkt genannten Dinge unterbrochen werden

Im Moment können Sie kein Typoskript im Markup verwenden. Es gibt keine Möglichkeit, Markups vorzuverarbeiten oder den Compiler die Typoskript-Syntax analysieren zu lassen. Eine Nicht-Null-Behauptung ist also nicht möglich.
Ich habe den Kauf der Lebensfähigkeit noch nicht ausprobiert. Vielleicht könnten wir die Bedingung der oberen Ebene kopieren und vor der verschachtelten Bedingung anhängen

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

Jetzt, da # 493 so festgelegt ist, dass $store -Variablen deklariert werden, können wir diese Idee erneut aufgreifen. Meine Idee ist es, alle if-Bedingungen wie diese voranzustellen ( #each als Beispiel):

Im

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

aus

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

Der schwierige Teil besteht darin, elseif / else und die Verschachtelungslogik richtig zu machen.

Dies sollte mit VS Code Version 104.6.3 / svelte-check Version 1.2.4 behoben werden. Bitte lassen Sie mich wissen, ob es jetzt für Sie funktioniert.

Ich habe ein Problem gefunden - Dies funktioniert jetzt in Vorlagen korrekt, jedoch nicht in Ereignis-Listenern. Ich bin mir nicht sicher, ob ich eine neue Ausgabe dafür eröffnen soll, aber hier ist ein Repro:

<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}

Ich habe ein Problem gefunden - Dies funktioniert jetzt in Vorlagen korrekt, jedoch nicht in Ereignis-Listenern. Ich bin mir nicht sicher, ob ich eine neue Ausgabe dafür eröffnen soll, aber hier ist ein Repro:

<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}

Das ist jedoch nur das normale TS-Verhalten. Ihr Wert wird in einer Funktion referenziert, die jederzeit in der Zukunft aufgerufen werden kann, wenn der Wert möglicherweise wieder auf Null gesetzt ist.

Beides sind gültige Ansichten. Man könnte sagen "da value keine Konstante ist, könnte es sich ändern", aber andererseits könnte man auch sagen "Ich komme nie zu diesem Klick-Handler, wenn value null ". Tricky ..

Ich werde dafür ein anderes Thema eröffnen, damit wir dies separat diskutieren können.

Ich denke nicht, dass es Ihre Zeit wert ist, über TS 'eigene, alte Entscheidungen zu diskutieren: p
Würde die Schaltfläche nicht bei value === null bleiben, wenn es zum Beispiel einen Out-Übergang auf der Ebene der Eltern gibt?

Genau genommen war der Kontrollfluss vor diesen entspannenden Änderungen korrekt, da man andere Variablen innerhalb jeder Schleife oder eines Wartens ändern konnte.

<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}

.. aber wer macht das? Sie können übrigens auch in TS dieselben Laufzeitfehler herstellen. Es ist also eher ein Kompromiss in DX als "das ist 100% richtig".

Bevor wir dies hier weiter diskutieren, bringen Sie diese Diskussion bitte zu # 876 😄

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen