Jak utworzyć komponent React „If”, który działa jak prawdziwy „if” w maszynowym pisma?

11

Zrobiłem prosty <If />komponent funkcji za pomocą 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;
}

Pozwala mi napisać czystszy kod, taki jak:

render() {

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

W większości przypadków działa dobrze, ale gdy chcę na przykład sprawdzić, czy dana zmienna nie jest zdefiniowana, a następnie przekazać ją jako właściwość, staje się to problemem. Podam przykład:

Załóżmy, że mam komponent o nazwie, <Animal />który ma następujące rekwizyty:

interface AnimalProps {
  animal: Animal;
}

a teraz mam inny komponent, który renderuje następujący 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>
);

Jak skomentowałem, chociaż w rzeczywistości zwierzę nie jest zdefiniowane, nie mam sposobu, aby powiedzieć maszynopisowi, że już to sprawdziłem. Zapewnienie animal!by działało, ale nie tego szukam.

Jakieś pomysły?

Eliya Cohen
źródło
1
nie jestem pewien, czy w maszynopisie można powiedzieć, że już sprawdziłeś zerowe bezpieczeństwo. Może {animal !== undefined && <Anibal animal={animal} />}zadziała
Olivier Boissé
1
Czy rzutowanie działa? <Animal animal={animal as Animal} />
Paul
@ OlivierBoissé Nie mogę używać tej składni
Eliya Cohen
@ paul tak, ale czy nie byłoby podobnie wstawić „!” na końcu?
Eliya Cohen

Odpowiedzi:

3

To wydaje się niemożliwe.

Powód: jeśli zmienimy Ifkomponentu zawartość z

if (props.condition) {
  ...
}

na odwrót

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

Dowiesz się, że tym razem chcesz, aby różnicowanie typów było przetwarzane w odwrotny sposób.

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

Co oznacza, że ​​nie jest niezależny, co prowadzi do wniosku, że nie jest możliwe, aby taki typ różnił się w tym stanie, bez dotykania jednego z dwóch składników.


Nie jestem pewien, jakie jest najlepsze podejście, ale oto jedna z moich myśli.

Możesz zdefiniować Animal componentrekwizyty animalza pomocą
typów warunkowych Dystrybucyjnych maszynopisu : NonNullable .

Dokument

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

Stosowanie

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

Nie jest generowany przez Ifwarunek komponentu, ale ponieważ używasz tylko child componenttego warunku, sensowne jest zaprojektowanie child componentrekwizytów jako none nullable, pod warunkiem, że

typ Animalzawiera undefined.

keikai
źródło
Nie jestem pewien, jak to rozwiązuje mój problem. Składnik zwierzęcy nie ma nic związanego ze składnikiem If. Nie powinien on dostosowywać się do komponentu If. Poza tym nie jestem pewien, jak NonNullable ma coś wspólnego z moim problemem
Eliya Cohen,
@EliyaCohen Zaktualizowano, coś musi dołączyć do tej odpowiedzi?
keikai
Przyjąłem twoją odpowiedź, chociaż nie mogę jej zaakceptować, ponieważ nie jest to rozwiązanie. Prawdopodobnie zostanie rozwiązany w przyszłości, gdy TS i React umożliwią takie rozwiązanie.
Eliya Cohen
1

Krótka odpowiedź?

Nie możesz

Ponieważ zdefiniowałeś animaljako Animal | undefined, jedynym sposobem na usunięcie undefinedjest utworzenie osłony lub przekształcenie animaljako czegoś innego. Masz ukryte strażnika wpisać stan nieruchomości oraz maszynopis nie ma możliwości dowiedzenia się, co się tam dzieje, więc nie może wiedzieć, że wybiera pomiędzy Animali undefined. Musisz go rzucić lub użyć !.

Zastanów się jednak: może to wydawać się czystsze, ale tworzy fragment kodu, który musi zostać zrozumiany i utrzymany, być może przez kogoś innego. W gruncie rzeczy tworzysz nowy język, złożony ze składników React, którego ktoś będzie musiał nauczyć się oprócz TypeScript.

Alternatywną metodą warunkowego wyprowadzenia JSX jest zdefiniowanie zmiennych, renderktóre zawierają treść warunkową, takich jak

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

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

Jest to standardowe podejście, które inni programiści natychmiast rozpoznają i nie będą musieli szukać.

Michael Landis
źródło
0

Korzystając z wywołania zwrotnego renderowania, jestem w stanie wpisać, że parametr return nie ma wartości null.

Nie jestem w stanie zmodyfikować twojego oryginalnego Ifkomponentu, ponieważ nie wiem, co twoje conditiontwierdzenia, a także jaką zmienną potwierdził, tjcondition={animal !== undefined || true}

Niestety stworzyłem nowy komponent IsDefineddo obsługi tego przypadku:

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

Wskazując, że childrenbędzie to callback, który zostanie przekazany jako NonNullable typu T, który jest tego samego typu co check.

Dzięki temu otrzymamy wywołanie zwrotne renderowania, które zostanie przekazane do zmiennej o zerowej wartości.

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

Mam tutaj działający przykład https://codesandbox.io/s/blazing-pine-2t4sm

użytkownik2340824
źródło
To miłe podejście, ale niestety nie udaje mu się użyć całego Ifkomponentu (żeby mógł wyglądać na czysty)
Eliya Cohen