Comment puis-je créer un composant React «If» qui agit comme un vrai «if» dans Typescript?

11

J'ai créé un <If />composant fonctionnel simple en utilisant 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;
}

Cela me permet d'écrire un code plus propre, tel que:

render() {

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

Dans la plupart des cas, cela fonctionne bien, mais lorsque je veux vérifier, par exemple, si une variable donnée n'est pas définie, puis la passer en tant que propriété, cela devient un problème. Je vais donner un exemple:

Disons que j'ai un composant appelé <Animal />qui a les accessoires suivants:

interface AnimalProps {
  animal: Animal;
}

et maintenant j'ai un autre composant qui rend le DOM suivant:

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

Comme je l'ai commenté, bien que l'animal ne soit pas défini, je n'ai aucun moyen de dire à Typescript que je l'ai déjà vérifié. L'affirmation de animal!fonctionnerait, mais ce n'est pas ce que je recherche.

Des idées?

Eliya Cohen
la source
1
Je ne sais pas s'il est possible en dactylographie de lui dire que vous avez déjà chech la sécurité nulle. Peut {animal !== undefined && <Anibal animal={animal} />}- être que
ça
1
Est-ce que ça marche? <Animal animal={animal as Animal} />
paul
@ OlivierBoissé Je n'ai pas le droit d'utiliser cette syntaxe
Eliya Cohen
@paul oui, mais ne serait-il pas similaire de mettre "!" à la fin?
Eliya Cohen

Réponses:

3

Cela semble impossible.

Raison: si nous changeons Ifle contenu du composant de

if (props.condition) {
  ...
}

à son opposé

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

Vous découvrirez que cette fois vous voulez que le type diff soit traité de la manière opposée.

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

Ce qui signifie qu'il n'est pas indépendant, ce qui conduit à la conclusion qu'il n'est pas possible de faire un tel type de différence dans cette condition, sans toucher à aucun des deux composants.


Je ne sais pas quelle est la meilleure approche, mais voici une de mes pensées.

Vous pouvez définir Animal componentles accessoires de animalavec les
types conditionnels distributifs du tapuscrit : NonNullable .

Document

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

Usage

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

Il n'est pas généré par la Ifcondition du composant, mais comme vous n'utilisez que l' child componentintérieur de cette condition, il est logique de concevoir les child componentaccessoires de comme as none nullablesous la condition que

type Animalcontiennent undefined.

keikai
la source
Je ne sais pas comment cela résout mon problème. Le composant animal n'a rien à voir avec le composant If. Il ne doit pas s'adapter pour correspondre au composant If. De plus, je ne sais pas comment NonNullable a quoi que ce soit à voir avec mon problème
Eliya Cohen
@EliyaCohen Mis à jour, quelque chose doit être ajouté pour cette réponse?
keikai
J'ai appoté votre réponse, bien que je ne puisse pas l'accepter car ce n'est pas une solution. Cela sera probablement résolu à l'avenir lorsque TS et React rendront cela possible.
Eliya Cohen
1

Réponse courte?

Tu ne peux pas.

Puisque vous avez défini animalcomme Animal | undefined, la seule façon de supprimer undefinedest de créer un garde ou de refondre animalcomme autre chose. Vous avez masqué la protection de type dans votre propriété condition et TypeScript n'a aucun moyen de savoir ce qui s'y passe, il ne peut donc pas savoir que vous choisissez entre Animalet undefined. Vous devrez le lancer ou l'utiliser !.

Considérez, cependant: cela peut sembler plus propre, mais cela crée un morceau de code qui doit être compris et maintenu, peut-être par quelqu'un d'autre plus loin. En substance, vous créez un nouveau langage, composé de composants React, que quelqu'un devra apprendre en plus de TypeScript.

Une autre méthode de sortie conditionnelle de JSX consiste à définir des variables renderqui contiennent votre contenu conditionnel, telles que

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

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

Il s'agit d'une approche standard que les autres développeurs reconnaîtront instantanément et n'auront pas à rechercher.

Michael Landis
la source
0

En utilisant un rappel de rendu, je peux taper que le paramètre de retour n'est pas nullable.

Je ne suis pas en mesure de modifier votre Ifcomposant d' origine car je ne sais pas ce que vous conditionaffirmez et quelle variable il a affirmé, c'est-à-direcondition={animal !== undefined || true}

Alors malheureusement, j'ai fait un nouveau composant IsDefinedpour gérer ce cas:

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

Indiquant que ce childrensera un rappel, auquel sera transmis un NonNullable de type T qui est du même type que check.

Avec cela, nous aurons un rappel de rendu, auquel sera transmise une variable vérifiée par 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>
  );

J'ai un exemple de travail ici https://codesandbox.io/s/blazing-pine-2t4sm

user2340824
la source
C'est une bonne approche, mais malheureusement, cela va à l'encontre du but de l'utilisation du Ifcomposant (afin qu'il puisse paraître propre)
Eliya Cohen