¿Cómo puedo hacer un componente Reaccionar "If" que actúe como un verdadero "if" en Typecript?

11

Hice un <If />componente de función simple usando 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;
}

Me permite escribir un código más limpio, como:

render() {

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

En la mayoría de los casos, funciona bien, pero cuando quiero verificar, por ejemplo, si una variable dada no está definida y luego pasarla como propiedad, se convierte en un problema. Daré un ejemplo:

Digamos que tengo un componente llamado <Animal />que tiene los siguientes accesorios:

interface AnimalProps {
  animal: Animal;
}

y ahora tengo otro componente que representa el siguiente 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 comenté, aunque de hecho animal no está definido, no tengo forma de decirle a Typecript que ya lo he comprobado. La afirmación de animal!funcionaría, pero eso no es lo que estoy buscando.

¿Algunas ideas?

Eliya Cohen
fuente
1
No estoy seguro de si es posible en mecanografiado decirle que ya ha revisado la seguridad nula. Tal vez {animal !== undefined && <Anibal animal={animal} />}funcionaría
Olivier Boissé
1
¿Funciona el lanzamiento? <Animal animal={animal as Animal} />
Paul
@ OlivierBoissé Estoy restringido de usar esa sintaxis
Eliya Cohen
@paul sí, pero ¿no sería similar poner "!" ¿en el final?
Eliya Cohen

Respuestas:

3

Parece imposible.

Motivo: si cambiamos Ifel contenido del componente de

if (props.condition) {
  ...
}

a lo contrario

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

Descubrirá que esta vez desea que el tipo diff se procese de la manera opuesta.

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

Lo que significa que no es independiente, lo que lleva a la conclusión de que no es posible diferenciar ese tipo en esta condición, sin tocar ninguno de los dos componentes.


No estoy seguro de cuál es el mejor enfoque, pero este es uno de mis pensamientos.

Puede definir Animal componentlos accesorios animalcon los
tipos condicionales distributivos del mecanografiado : no anulables .

Documento

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

Uso

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

No se genera por la Ifcondición del componente, pero dado que solo usa el child componentinterior de esa condición, tiene sentido diseñar los child componentaccesorios de la unidad none nullable, bajo la condición de que

tipo Animalcontiene undefined.

keikai
fuente
No estoy seguro de cómo resuelve mi problema. El componente animal no tiene nada relacionado con el componente If. No debe adaptarse para que coincida con el componente If. Además, no estoy seguro de cómo NonNullable tiene algo que ver con mi problema
Eliya Cohen
@EliyaCohen Actualizado, ¿hay que agregar algo para esta respuesta?
keikai
Solicité su respuesta, aunque no puedo aceptarla, ya que no es una solución. Probablemente se resolverá en el futuro cuando TS y React hagan posible tal cosa.
Eliya Cohen
1

¿Respuesta corta?

No puedes

Como lo has definido animalcomo Animal | undefined, la única forma de eliminarlo undefinedes crear un guardia o refundirlo animalcomo otra cosa. Ha ocultado la protección de tipo en su propiedad de condición, y TypeScript no tiene forma de saber qué está sucediendo allí, por lo que no puede saber que está eligiendo entre Animaly undefined. Tendrás que lanzarlo o usarlo !.

Considere, sin embargo: esto puede sentirse más limpio, pero crea un fragmento de código que necesita ser entendido y mantenido, quizás por otra persona más adelante. En esencia, está creando un nuevo lenguaje, hecho de componentes React, que alguien necesitará aprender además de TypeScript.

Un método alternativo para generar JSX condicionalmente es definir variables renderque contengan su contenido condicional, como

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

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

Este es un enfoque estándar que otros desarrolladores reconocerán instantáneamente y no tendrán que buscar.

Michael Landis
fuente
0

Al usar una devolución de llamada de representación, puedo escribir que el parámetro de retorno no es anulable.

No puedo modificar su Ifcomponente original , ya que no sé qué conditionafirma y qué variable afirmó.condition={animal !== undefined || true}

Desafortunadamente, hice un nuevo componente IsDefinedpara manejar 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á una devolución de llamada, que pasará un NonNullable de tipo T que es del mismo tipo que check.

Con esto, obtendremos una devolución de llamada de renderizado, que pasará una variable de verificación nula.

  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>
  );

Tengo un ejemplo de trabajo aquí https://codesandbox.io/s/blazing-pine-2t4sm

usuario2340824
fuente
Ese es un buen enfoque, pero desafortunadamente frustra el propósito de usar Ifcomponentes (para que pueda verse limpio)
Eliya Cohen