Wie kann ich eine React-If-Komponente erstellen, die sich in Typescript wie ein echtes If verhält?

11

Ich habe <If />mit React eine einfache Funktionskomponente erstellt:

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

Damit kann ich einen saubereren Code schreiben, wie zum Beispiel:

render() {

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

In den meisten Fällen funktioniert es gut. Wenn ich jedoch beispielsweise überprüfen möchte, ob eine bestimmte Variable nicht definiert ist, und sie dann als Eigenschaft übergeben möchte, wird dies zu einem Problem. Ich werde ein Beispiel geben:

Angenommen, ich habe eine Komponente <Animal />mit den folgenden Requisiten:

interface AnimalProps {
  animal: Animal;
}

und jetzt habe ich eine andere Komponente, die das folgende DOM rendert:

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

Wie ich kommentierte, obwohl Tier tatsächlich nicht definiert ist, kann ich Typescript nicht sagen, dass ich es bereits überprüft habe. Die Behauptung von animal!würde funktionieren, aber das ist nicht das, wonach ich suche.

Irgendwelche Ideen?

Eliya Cohen
quelle
1
Ich bin mir nicht sicher, ob es in Typoskript möglich ist, ihm zu sagen, dass Sie bereits die Nullsicherheit überprüft haben. Vielleicht {animal !== undefined && <Anibal animal={animal} />}würde es funktionieren
Olivier Boissé
1
Funktioniert das Casting? <Animal animal={animal as Animal} />
Paul
@ OlivierBoissé Ich kann diese Syntax nicht verwenden
Eliya Cohen
@ Paul ja, aber wäre es nicht ähnlich, "!" schlussendlich?
Eliya Cohen

Antworten:

3

Es scheint unmöglich.

Grund: Wenn wir Ifden Inhalt der Komponente von ändern

if (props.condition) {
  ...
}

dazu ist es entgegengesetzt

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

Sie werden feststellen, dass diesmal der Typ diff umgekehrt verarbeitet werden soll.

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

Dies bedeutet, dass es nicht unabhängig ist, was zu der Schlussfolgerung führt, dass es nicht möglich ist, einen solchen Typ in diesem Zustand zu unterscheiden, ohne eine der beiden Komponenten zu berühren.


Ich bin mir nicht sicher, was der beste Ansatz ist, aber hier ist einer meiner Gedanken.

Sie können die Animal componentRequisiten animalmit den
verteilenden bedingten Typen des Typoskripts definieren : NonNullable .

Dokumentieren

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

Verwendungszweck

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

Es wird nicht durch die IfBedingung der Komponente generiert , aber da Sie nur das child componentInnere dieser Bedingung verwenden, ist es sinnvoll, die child componentRequisiten der Komponente als none nullable, unter der Bedingung zu entwerfen , dass

Typ Animalenthalten undefined.

Keikai
quelle
Ich bin nicht sicher, wie es mein Problem löst. Die tierische Komponente hat nichts mit der If-Komponente zu tun. Es sollte sich nicht an die If-Komponente anpassen. Außerdem bin ich mir nicht sicher, wie NonNullable etwas mit meinem Problem zu tun hat
Eliya Cohen,
@EliyaCohen Aktualisiert, muss etwas für diese Antwort angehängt werden?
Keikai
Ich habe Ihre Antwort gebilligt, obwohl ich sie nicht akzeptieren kann, da sie keine Lösung ist. Wird wahrscheinlich in Zukunft gelöst, wenn TS und React so etwas ermöglichen.
Eliya Cohen
1

Kurze Antwort?

Das kannst du nicht.

Da Sie animalals definiert haben Animal | undefined, besteht die einzige Möglichkeit zum Entfernen undefineddarin, entweder einen Schutz zu erstellen oder animalals etwas anderes neu zu fassen . Sie haben den Typschutz in Ihrer Bedingungseigenschaft ausgeblendet, und TypeScript kann nicht wissen, was dort passiert. Daher kann es nicht wissen, dass Sie zwischen Animalund wählen undefined. Sie müssen es wirken oder verwenden !.

Bedenken Sie jedoch: Dies mag sich sauberer anfühlen, schafft jedoch einen Code, der verstanden und gepflegt werden muss, möglicherweise von einer anderen Person auf der ganzen Linie. Im Wesentlichen erstellen Sie eine neue Sprache aus React-Komponenten, die zusätzlich zu TypeScript von jemandem gelernt werden muss.

Eine alternative Methode zur bedingten Ausgabe von JSX besteht darin, Variablen zu definieren render, die Ihren bedingten Inhalt enthalten, z

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

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

Dies ist ein Standardansatz, den andere Entwickler sofort erkennen und nicht nachschlagen müssen.

Michael Landis
quelle
0

Durch Verwendung eines Render-Rückrufs kann ich eingeben, dass der return-Parameter nicht nullwertfähig ist.

Ich kann Ihre ursprüngliche IfKomponente nicht ändern, da ich nicht weiß, was Sie conditionbehaupten und welche Variable sie behauptet, dhcondition={animal !== undefined || true}

Daher habe ich leider eine neue Komponente erstellt IsDefined, um diesen Fall zu behandeln:

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

Dies zeigt an, dass childrenes sich um einen Rückruf handelt, an den ein NonNullable vom Typ T übergeben wird, der vom selben Typ ist wie check.

Damit erhalten wir einen Render-Rückruf, dem eine null-überprüfte Variable übergeben wird.

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

Ich habe hier ein Arbeitsbeispiel https://codesandbox.io/s/blazing-pine-2t4sm

user2340824
quelle
Das ist ein netter Ansatz, aber leider macht er den ganzen Zweck der Verwendung von IfKomponenten
Eliya Cohen,