Language-tools: TypeScript considera incorrectamente la variable como posiblemente una condición interna nula que verifica la variable

Creado en 18 oct. 2020  ·  20Comentarios  ·  Fuente: sveltejs/language-tools

Describe el error
Se muestra el error de TypeScript "El objeto es posiblemente nulo" para las comprobaciones condicionales anidadas.

Reproducir
Caso de prueba simple:

<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 arroja el error Object is possibly null en la línea {#if test.author} , no específicamente en test dentro de esa línea.

Comportamiento esperado
Se espera que no arroje ningún error.

Sistema (complete la siguiente información):

  • SO: OSX 10.15.7
  • IDE: VSCode
  • Complemento / Paquete: "Svelte para VSCode"

Contexto adicional
Si se elimina la declaración condicional anidada y en la interfaz TestObject author? se reemplaza con author (para que sea necesario), sería lógico que TypeScript arrojara el mismo error para {test.author} , pero no es así. Entonces, parece que el error se desencadena por una declaración condicional anidada, sin ella, TypeScript sabe que test no es nulo.

Fixed bug

Comentario más útil

Pequeña actualización / resumen:

  • el flujo de control se restablece tan pronto como el código está dentro de una de estas construcciones: #await , #each , let:X . Esto se debe a la forma en que tenemos que transformar el código. No nos hemos olvidado de este problema y todavía estamos buscando formas de resolverlo de una buena manera.
  • Las condiciones if anidadas ahora deberían funcionar como se esperaba siempre que no sean interrumpidas por una de las cosas mencionadas en la primera viñeta.

Todos 20 comentarios

No sé qué podemos hacer para solucionarlo. svelte2tsx lo transforma en esto:

() => (<>

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

porque está envuelto en una función para que el análisis del tipo de flujo de control no surta efecto. Y no podemos eliminar la función o envolverla en una función invocada inmediatamente porque la expresión se volvería a ejecutar cuando su dependencia cambiara, definitivamente necesitamos invalidar algunos de los tipos de flujo de control.

¿Podríamos convertir esto en ternaries? Si no hay otro bloque, usamos una cadena vacía para el caso else.
Pero el mayor problema podría ser que no se nos permite usar funciones como esa en ninguna de las transformaciones, no estoy seguro si ese es el caso en este momento.

¡Quizás permitir! ¿sintaxis? {#if test!.author} . Esa es una sintaxis válida de TypeScript, pero en este momento no se puede usar en una plantilla.

En este momento no puede usar mecanografiado en el marcado. No hay forma de preprocesar el marcado o permitir que el compilador analice la sintaxis de mecanografiado. Entonces, la aserción no nula no es posible.
No he probado la compra de viabilidad, tal vez podríamos copiar la condición de nivel superior y agregarla antes de la condición anidada.

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

Solución temporal:

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

Copiando variable, asignando un tipo diferente y usándola en condición anidada. Es feo y debe usarse con cuidado, pero elimina las advertencias.

Una solución alternativa más pragmática por ahora sería

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

Es bastante molesto, especialmente cuando no solo se perfora dentro del mismo objeto, sino que se crean condiciones separadas:

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

Eso no funcionará. TypeScript no está permitido en plantillas.

Parece funcionar aquí. test && test.author entonces.

si te refieres al encadenamiento opcional, esa es una nueva característica de javascript, no solo una característica mecanografiada. Está disponible en svelte después de 3.24.0

¡Gracias! Siempre asumí que era una característica de TS similar a la declaración no nula foo! . Eso lo hace mucho más sencillo.

Siempre aprendiendo algo nuevo :)

No he probado la compra de viabilidad, tal vez podríamos copiar la condición de nivel superior y agregarla antes de la condición anidada.

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

Hice algunas pruebas en esa dirección y creo que esto no funcionará en todos los casos. Puede silenciar los errores de #if , pero reaparecerán tan pronto como use un bloque #each o #await dentro de él. Estos errores no aparecerán si la variable es const , porque el flujo de control de TS sabe que la condición aún debe ser verdadera porque la variable no se puede reasignar. Entonces, la mejor manera de resolver esto sería transformar todo en const variables, o reasignar todas las variables a otras variables temporales para la plantilla. Sin embargo, eso traerá sus propios problemas para la información de desplazamiento, el cambio de nombre, la declaración de ir a, etc. Quizás eso podría solucionarse usando expresiones de coma y transformar {#if foo} en {#if (foo, generatedConstFoo)} . En pocas palabras: esto requerirá bastante piratería.

Tal vez el "agregar bloques if de nivel superior" -algo sea correcto después de todo. No es seguro asumir que las condiciones siguen siendo verdaderas dentro de cada bloque / await. Por ejemplo, esto arrojará un error de tiempo de ejecución ( 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}

Lo mismo se puede construir para un bloque de espera.

Pequeña actualización / resumen:

  • el flujo de control se restablece tan pronto como el código está dentro de una de estas construcciones: #await , #each , let:X . Esto se debe a la forma en que tenemos que transformar el código. No nos hemos olvidado de este problema y todavía estamos buscando formas de resolverlo de una buena manera.
  • Las condiciones if anidadas ahora deberían funcionar como se esperaba siempre que no sean interrumpidas por una de las cosas mencionadas en la primera viñeta.

En este momento no puede usar mecanografiado en el marcado. No hay forma de preprocesar el marcado o permitir que el compilador analice la sintaxis de mecanografiado. Entonces, la aserción no nula no es posible.
No he probado la compra de viabilidad, tal vez podríamos copiar la condición de nivel superior y agregarla antes de la condición anidada.

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

Ahora que # 493 está arreglado de una manera que declara $store -variables, podemos revisar esta idea. Mi idea es anteponer todas las condiciones if como esta ( #each como ejemplo):

En

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

Fuera

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

La parte complicada es obtener elseif / else y la lógica de anidamiento correctamente.

Esto debería solucionarse con VS Code versión 104.6.3 / svelte-check versión 1.2.4. Por favor, avíseme si le funciona ahora.

Encontré un problema: ahora funciona correctamente dentro de las plantillas, pero no en los detectores de eventos. No estoy seguro de si debería abrir una nueva edición, pero aquí hay una reproducción:

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

Encontré un problema: ahora funciona correctamente dentro de las plantillas, pero no en los detectores de eventos. No estoy seguro de si debería abrir una nueva edición, pero aquí hay una reproducción:

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

Sin embargo, ese es solo el comportamiento habitual de TS. Se hace referencia a su valor en una función que podría llamarse en cualquier momento en el futuro donde el valor podría volver a ser nulo.

Ambos son puntos de vista válidos. Se podría decir "dado que value no es una constante, podría cambiar", pero por otro lado también se podría decir "Nunca llego a ese controlador de clics si value es null ". Difícil ..

Abriré un tema diferente para que podamos discutir esto por separado.

No creo que valga la pena dedicar tiempo a hablar sobre las antiguas elecciones de TS: p
¿No se quedaría el botón con el valor === null si hay una transición de salida a su nivel o al de sus padres, por ejemplo?

Estrictamente hablando, el flujo de control era correcto antes de estos cambios relajantes porque se podían modificar otras variables dentro de cada ciclo o en espera.

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

.. pero quien hace esto? También puede fabricar los mismos errores de tiempo de ejecución en TS, por cierto. Así que es más una compensación en DX que "esto es 100% correcto".

Pero antes de discutir esto más a fondo aquí, lleve esta discusión al # 876 😄

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

baileyherbert picture baileyherbert  ·  3Comentarios

johanbissemattsson picture johanbissemattsson  ·  4Comentarios

matthewmueller picture matthewmueller  ·  3Comentarios

non25 picture non25  ·  5Comentarios

canadaduane picture canadaduane  ·  5Comentarios