Come posso creare un componente React "If" che si comporta come un vero "if" in Typescript?


11

Ho creato un semplice <If />componente di funzione 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;
}

Mi permette di scrivere un codice più pulito, come ad esempio:

render() {

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

Nella maggior parte dei casi, funziona bene, ma quando voglio verificare, ad esempio, se una determinata variabile non è definita e quindi passarla come proprietà, diventa un problema. Faccio un esempio:

Diciamo che ho un componente chiamato <Animal />che ha i seguenti puntelli:

interface AnimalProps {
  animal: Animal;
}

e ora ho un altro componente che rende il seguente 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>
);

Come ho commentato, sebbene in realtà l'animale non sia definito, non ho modo di dire a Typescript che l'ho già controllato. L'asserzione di animal!avrebbe funzionato, ma non è quello che sto cercando.

Qualche idea?


1
non sono sicuro se sia possibile in dattiloscritto dirgli che hai già la sicurezza nulla. Forse {animal !== undefined && <Anibal animal={animal} />}avrebbe funzionato
Olivier Boissé il

1
Il casting funziona? <Animal animal={animal as Animal} />
paul

@ OlivierBoissé Sono limitato a usare quella sintassi
Eliya Cohen il

@paul sì, ma non sarebbe simile a mettere "!" alla fine?
Eliya Cohen,

Risposte:


3

Sembra impossibile

Motivo: se cambiamo Ifil contenuto del componente da

if (props.condition) {
  ...
}

al contrario

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

Scoprirai che questa volta desideri che il tipo diff sia stato elaborato in modo opposto.

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

Ciò significa che non è indipendente, portando alla conclusione che non è possibile diffondere tale tipo in questa condizione, senza toccare nessuno dei due componenti.


Non sono sicuro di quale sia l'approccio migliore, ma ecco uno dei miei pensieri.

Puoi definire Animal componentgli oggetti animaldi scena con i
tipi condizionali Distributive del dattiloscritto : Non null .

Documento

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

uso

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

Non è generato dalla Ifcondizione del componente, ma poiché usi solo child componentquella condizione interna, ha senso progettare gli child componentoggetti di scena come none nullable, nella condizione che

tipo Animalcontiene undefined.


Non sono sicuro di come risolva il mio problema. Il componente animale non ha nulla a che fare con il componente If. Non dovrebbe adattarsi per abbinare il componente If. Inoltre, non sono sicuro di come NonNullable abbia qualcosa a che fare con il mio problema
Eliya Cohen,

@EliyaCohen Aggiornato, c'è qualcosa da aggiungere per questa risposta?
keikai,

Ho approvato la tua risposta, anche se non posso accettarla poiché non è una soluzione. Probabilmente verrà risolto in futuro quando TS e React renderanno possibile una cosa del genere.
Eliya Cohen,

1

Risposta breve?

Non puoi.

Poiché hai definito animalcome Animal | undefined, l'unico modo per rimuovere undefinedè creare una guardia o rifondere animalcome qualcos'altro. Hai nascosto la protezione del tipo nella proprietà della tua condizione e TypeScript non ha modo di sapere cosa sta succedendo lì, quindi non può sapere che stai scegliendo tra Animale undefined. Dovrai lanciarlo o usarlo !.

Considera, tuttavia: questo può sembrare più pulito, ma crea un pezzo di codice che deve essere compreso e mantenuto, forse da qualcun altro più avanti. In sostanza stai creando un nuovo linguaggio, fatto di componenti di React, che qualcuno dovrà imparare oltre a TypeScript.

Un metodo alternativo per l'output condizionale di JSX consiste nel definire variabili renderche contengano il contenuto condizionale, ad esempio

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

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

Questo è un approccio standard che altri sviluppatori riconosceranno all'istante e non dovranno cercare.


0

Utilizzando un callback di rendering, sono in grado di digitare che il parametro return non è nullable.

Non sono in grado di modificare il Ifcomponente originale in quanto non so quali siano le tue conditionaffermazioni e anche quale variabile abbia affermato, ad escondition={animal !== undefined || true}

Quindi, sfortunatamente, ho creato un nuovo componente IsDefinedper gestire questo 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;
}

Indica che childrensarà un callback, al quale verrà passato un NonNullable di tipo T che è dello stesso tipo di check.

Con questo, avremo un callback di rendering, al quale verrà passata una variabile con controllo null.

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

Ho un esempio funzionante qui https://codesandbox.io/s/blazing-pine-2t4sm


Questo è un buon approccio, ma sfortunatamente sconfigge l'intero scopo dell'utilizzo del Ifcomponente (quindi potrebbe sembrare pulito)
Eliya Cohen
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.