Language-tools: O TypeScript considera incorretamente a variável como possivelmente nula dentro da condição que verifica a variável

Criado em 18 out. 2020  ·  20Comentários  ·  Fonte: sveltejs/language-tools

Descreva o bug
Erro de TypeScript "O objeto é possivelmente nulo" é mostrado para verificações condicionais aninhadas.

Reproduzir
Caso de teste simples:

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

O TypeScript lança o erro Object is possibly null na linha {#if test.author} , não especificamente em test dentro dessa linha.

Comportamento esperado
Esperado para não gerar erros.

Sistema (preencha as seguintes informações):

  • OS: OSX 10.15.7
  • IDE: VSCode
  • Plugin / Pacote: "Svelte para VSCode"

Contexto adicional
Se a instrução condicional aninhada for removida e na interface TestObject author? for substituída por author (para torná-la obrigatória), seria lógico que o TypeScript gerasse o mesmo erro para {test.author} , mas isso não acontece. Portanto, parece que o erro é disparado por uma instrução condicional aninhada, sem ela o TypeScript sabe que test não é nulo.

Fixed bug

Comentários muito úteis

Pequena atualização / resumo:

  • o fluxo de controle é redefinido assim que o código está dentro de uma destas construções: #await , #each , let:X . Isso se deve à maneira como temos de transformar o código. Não nos esquecemos desse problema e ainda estamos procurando maneiras de resolvê-lo da melhor maneira.
  • as condições if aninhadas agora devem funcionar conforme o esperado, desde que não sejam interrompidas por uma das coisas mencionadas no primeiro ponto

Todos 20 comentários

Não sei o que podemos fazer para lidar com isso. svelte2tsx transformá-lo neste:

() => (<>

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

porque está envolvido em uma função para que a análise do tipo de fluxo de controle não tenha efeito. E não podemos remover a função ou envolvê-la em uma função invocada imediatamente porque a expressão seria executada novamente quando sua dependência fosse bloqueada. Definitivamente, precisamos invalidar alguns dos tipos de fluxo de controle.

Podemos transformar isso em ternários? Se não houver bloco else, usamos apenas uma string vazia para o caso else.
Mas o maior problema pode ser que não temos permissão para usar funções como essa em qualquer uma das transformações - não tenho certeza se esse é o caso agora

Talvez permita! sintaxe? {#if test!.author} . Essa é uma sintaxe válida do TypeScript, mas no momento não pode ser usada no modelo.

No momento, você não pode usar o texto digitado na marcação. Não há como pré-processar a marcação ou permitir que o compilador analise a sintaxe do typecript. Portanto, a asserção não nula não é possível.
Ainda não experimentamos a compra de viabilidade, talvez pudéssemos copiar a condição de nível superior e anexá-la antes da condição aninhada

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

Solução temporária:

<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 variável, atribuindo tipo diferente e usando-a em condição aninhada. É feio e precisa ser usado com cuidado, mas elimina os avisos.

Uma solução mais pragmática por enquanto seria

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

É muito chato, especialmente quando não se trata apenas de perfurar dentro do mesmo objeto, mas criando condições separadas:

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

Isso não vai funcionar. TypeScript não é permitido em modelos.

Parece funcionar aqui. test && test.author então.

se você quer dizer encadeamento opcional, esse é um novo recurso javascript, não apenas um recurso de texto digitado. Está disponível na versão esbelta após 3.24.0

Obrigado! Sempre achei que era um recurso de TS semelhante à instrução não nula foo! . Isso o torna muito mais simples.

Sempre aprendendo algo novo :)

Ainda não experimentamos a compra de viabilidade, talvez pudéssemos copiar a condição de nível superior e anexá-la antes da condição aninhada

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

Fiz alguns testes nesse sentido e acho que não vai funcionar para todos os casos. Pode silenciar erros para #if , mas eles reaparecerão assim que você usar um bloco #each ou #await dentro dele. Esses erros não aparecerão se a variável for const , porque o fluxo de controle do TS sabe que a condição ainda deve ser verdadeira porque a variável não pode ser reatribuída. Portanto, a melhor maneira de resolver isso seria transformar tudo em const variáveis ​​ou reatribuir todas as variáveis ​​a outras variáveis ​​temporárias para o modelo. Isso trará seus próprios problemas para informações de pairar, renomear, ir para declaração etc. Talvez isso pudesse ser contornado usando expressões de vírgula e transformar {#if foo} em {#if (foo, generatedConstFoo)} . Resumindo: isso vai exigir um pouco de hackeagem.

Talvez o "adicionar blocos if de nível superior" esteja correto, afinal. Não é seguro presumir que as condições ainda sejam verdadeiras dentro de cada bloco / await. Por exemplo, isso gerará um erro de tempo de execução ( 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}

O mesmo pode ser construído para um bloco de espera.

Pequena atualização / resumo:

  • o fluxo de controle é redefinido assim que o código está dentro de uma destas construções: #await , #each , let:X . Isso se deve à maneira como temos de transformar o código. Não nos esquecemos desse problema e ainda estamos procurando maneiras de resolvê-lo da melhor maneira.
  • as condições if aninhadas agora devem funcionar conforme o esperado, desde que não sejam interrompidas por uma das coisas mencionadas no primeiro ponto

No momento, você não pode usar o texto digitado na marcação. Não há como pré-processar a marcação ou permitir que o compilador analise a sintaxe do typecript. Portanto, a asserção não nula não é possível.
Ainda não experimentamos a compra de viabilidade, talvez pudéssemos copiar a condição de nível superior e anexá-la antes da condição aninhada

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

Agora que # 493 foi corrigido de uma forma que declara $store -variáveis, podemos revisitar essa ideia. Minha ideia é acrescentar todas as condições if como esta ( #each como exemplo):

Dentro

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

Fora

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

A parte complicada é acertar o elseif / else e a lógica de aninhamento.

Isso deve ser corrigido com o código VS versão 104.6.3 / svelte-check versão 1.2.4. Por favor, deixe-me saber se funciona para você agora.

Encontrei um problema - agora funciona corretamente dentro de modelos, mas não em ouvintes de eventos. Não tenho certeza se devo abrir um novo problema para ele, mas aqui está uma reprodução:

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

Encontrei um problema - agora funciona corretamente dentro de modelos, mas não em ouvintes de eventos. Não tenho certeza se devo abrir um novo problema para ele, mas aqui está uma reprodução:

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

No entanto, esse é apenas o comportamento normal do TS. Seu valor é referenciado em uma função que pode ser chamada a qualquer momento no futuro, onde o valor pode voltar a ser nulo.

Ambos são pontos de vista válidos. Pode-se dizer "já que value não é uma const, isso pode mudar", mas, por outro lado, também se pode dizer "Nunca chego a esse manipulador de cliques se value for null ". Complicado ..

Vou abrir uma edição diferente para que possamos discutir isso separadamente.

Não acho que vale a pena discutir as velhas e próprias escolhas de TS: p
O botão não permaneceria com o valor === null se houvesse uma transição para fora no nível deles ou de seus pais, por exemplo?

Estritamente falando, o fluxo de controle estava correto antes dessas mudanças relaxantes porque era possível modificar outras variáveis ​​dentro de cada loop ou de 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}

.. mas quem faz isso? Você também pode fabricar os mesmos erros de tempo de execução no TS, aliás. Portanto, é mais uma troca em DX do que "isso é 100% correto".

Mas antes de discutirmos isso aqui, leve esta discussão para # 876 😄

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

JAD3N picture JAD3N  ·  5Comentários

scippio picture scippio  ·  3Comentários

baileyherbert picture baileyherbert  ·  3Comentários

AlexGalays picture AlexGalays  ·  5Comentários

maximedupre picture maximedupre  ·  5Comentários