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):
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.
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:
#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.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 😄
Comentários muito úteis
Pequena atualização / resumo:
#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.