Como criar um componente React "If" que funcione como um "if" real no Texto Dactilografado?

11

Eu criei um <If />componente de função simples usando o React:

import React, { ReactElement } from "react";

interface Props {
    condition: boolean;
    comment?: any;
}

export function If(props: React.PropsWithChildren<Props>): ReactElement | null {
    if (props.condition) {
        return <>{props.children}</>;
    }

    return null;
}

Permite escrever um código mais limpo, como:

render() {

    ...
    <If condition={truthy}>
       presnet if truthy
    </If>
    ...

Na maioria dos casos, funciona bem, mas quando quero verificar, por exemplo, se uma determinada variável não está definida e depois passá-la como propriedade, ela se torna um problema. Vou dar um exemplo:

Digamos que eu tenho um componente chamado <Animal />que possui os seguintes adereços:

interface AnimalProps {
  animal: Animal;
}

e agora eu tenho outro componente que renderiza o seguinte DOM:

const animal: Animal | undefined = ...;

return (
  <If condition={animal !== undefined} comment="if animal is defined, then present it">
    <Animal animal={animal} /> // <-- Error! expected Animal, but got Animal | undefined
  </If>
);

Como comentei, apesar de o fato de animal não estar definido, não tenho como dizer ao Typescript que já o verifiquei. A afirmação de animal!funcionaria, mas não é isso que estou procurando.

Alguma ideia?

Eliya Cohen
fonte
11
não tenho certeza se é possível, em texto datilografado, dizer a ele que você já chechou a segurança nula. Talvez {animal !== undefined && <Anibal animal={animal} />}funcionaria
Olivier Boissé 25/02
11
A transmissão funciona? <Animal animal={animal as Animal} />
paul
@ OlivierBoissé Estou restrito a usar essa sintaxe
Eliya Cohen
@ Paul sim, mas não seria semelhante a colocar "!" No final?
Eliya Cohen

Respostas:

3

Parece impossível.

Motivo: se Ifalterarmos o conteúdo do componente de

if (props.condition) {
  ...
}

para o oposto

if (!props.condition) {
  ...
}

Você descobrirá que desta vez deseja que o diff de tipo seja processado da maneira oposta.

  <If condition={animal === undefined} comment="if animal is defined, then present it">
    <Animal animal={animal} /> // <-- Error! expected Animal, but got Animal | undefined
  </If>

O que significa que não é independente, levando à conclusão de que não é possível diferenciar esse tipo nessa condição, sem tocar em nenhum dos dois componentes.


Não tenho certeza de qual é a melhor abordagem, mas aqui está um dos meus pensamentos.

Você pode definir Animal componentos adereços animalcom os
tipos condicionais distributivos do texto datilografado : NonNullable .

Documento

type T34 = NonNullable<string | number | undefined>;  // string | number

Uso

interface AnimalProps {
  // Before
  animal: Animal;
  // After
  animal: NonNullable<Animal>;
}

Ele não é gerado pela Ifcondição do componente, mas como você usa apenas o child componentinterior dessa condição, faz algum sentido projetar os child componentadereços como none nullable, sob a condição que

tipo Animalcontém undefined.

keikai
fonte
Não tenho certeza de como isso resolve meu problema. O componente animal não tem nada relacionado ao componente If. Não deve se adaptar para corresponder ao componente If. Além disso, não sei ao certo como o NonNullable tem algo a ver com relação ao meu problema.
Eliya Cohen
@EliyaCohen Atualizado, algo precisa acrescentar para esta resposta?
keikai 26/02
Votei sua resposta, embora não possa aceitá-la, pois não é uma solução. Provavelmente será resolvido no futuro quando TS e React tornarem isso possível.
Eliya Cohen
1

Resposta curta?

Você não pode.

Como você definiu animalcomo Animal | undefined, a única maneira de remover undefinedé criar uma proteção ou reformular animalcomo outra coisa. Você ocultou a proteção de tipo em sua propriedade de condição, e o TypeScript não tem como saber o que está acontecendo lá; portanto, não pode saber se você está escolhendo entre Animale undefined. Você precisará transmiti-lo ou usá-lo !.

Considere, no entanto: isso pode parecer mais limpo, mas cria um código que precisa ser entendido e mantido, talvez por outra pessoa mais adiante. Em essência, você está criando uma nova linguagem, composta de componentes React, que alguém precisará aprender além do TypeScript.

Um método alternativo para gerar JSX condicionalmente é definir variáveis renderque contenham seu conteúdo condicional, como

render() {
  const conditionalComponent = condition ? <Component/> : null;

  return (
    <Zoo>
      { conditionalComponent }
    </Zoo>
  );
}

Essa é uma abordagem padrão que outros desenvolvedores reconhecerão instantaneamente e não precisarão procurar.

Michael Landis
fonte
0

Usando um retorno de chamada de renderização, sou capaz de digitar que o parâmetro de retorno não é nulo.

Não consigo modificar o seu Ifcomponente original , pois não sei o que o seu conditionafirma e também que variável ele afirmoucondition={animal !== undefined || true}

Infelizmente, criei um novo componente IsDefinedpara lidar com este caso:

interface IsDefinedProps<T> {
  check: T;
  children: (defined: NonNullable<T>) => JSX.Element;
}

function nonNullable<T>(value: T): value is NonNullable<T> {
  return value !== undefined || value !== null;
}

function IsDefined({ children, check }: IsDefinedProps<T>) {
  return nonNullable(check) ? children(check) : null;
}

Indicando que childrenserá um retorno de chamada, que receberá um NonNullable do tipo T, que é do mesmo tipo que check.

Com isso, obteremos um retorno de chamada de renderização, que receberá uma variável verificada com nulo.

  const aDefined: string | undefined = mapping.a;
  const bUndefined: string | undefined = mapping.b;

  return (
    <div className="App">
      <IsDefined check={aDefined}>
        {aDefined => <DoSomething message={aDefined} />} // is defined and renders
      </IsDefined>
      <IsDefined check={bUndefined}>
        {bUndefined => <DoSomething message={bUndefined} />} // is undefined and doesn't render
      </IsDefined>
    </div>
  );

Eu tenho um exemplo de trabalho aqui https://codesandbox.io/s/blazing-pine-2t4sm

user2340824
fonte
Essa é uma boa abordagem, mas infelizmente derrota todo o propósito de usar o Ifcomponente (para que possa parecer limpo).
Eliya Cohen