Как я могу создать компонент React «If», который действует как настоящее «if» в Typescript?


11

Я сделал простой <If />компонент функции, используя 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;
}

Это позволяет мне писать более чистый код, такой как:

render() {

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

В большинстве случаев это работает хорошо, но когда я хочу, например, проверить, не задана ли данная переменная, а затем передать ее как свойство, это становится проблемой. Я приведу пример:

Допустим, у меня есть компонент с именем, <Animal />который имеет следующие реквизиты:

interface AnimalProps {
  animal: Animal;
}

и теперь у меня есть другой компонент, который отображает следующий 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>
);

Как я уже говорил, хотя на самом деле animal не определена, у меня нет возможности сообщить Typescript, что я его уже проверил. Утверждение animal!будет работать, но это не то, что я ищу.

Любые идеи?


1
не уверен, что в машинописи можно сказать ему, что ты уже проверяешь нулевую безопасность. Может быть {animal !== undefined && <Anibal animal={animal} />}, сработает
Оливье Буасе

1
Кастинг это работает? <Animal animal={animal as Animal} />
Павел

@ OlivierBoissé Я ограничен в использовании этого синтаксиса
Элия ​​Коэн

@ Пол да, но не будет ли это похоже на "!" в конце?
Элия ​​Коэн

Ответы:


3

Это кажется невозможным.

Причина: если мы изменим содержимоеIf компонента с

if (props.condition) {
  ...
}

напротив

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

Вы обнаружите, что на этот раз вы хотите, чтобы тип diff обрабатывался противоположным образом.

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

Это означает, что он не является независимым, что приводит к выводу, что невозможно создать такой тип в этом состоянии, не касаясь ни одного из двух компонентов.


Я не уверен, что лучший подход, но вот одна из моих мыслей.

Вы можете определить Animal componentреквизиты animalс помощью машинописи
условных типов Distributive текста : NonNullable .

Документ

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

Применение

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

Он не генерируется Ifусловием компонента, но поскольку вы используете только child componentвнутри этого условия, имеет смысл разработатьchild component реквизиты как none nullable, при условии, что

тип Animalсодержит undefined.


Я не уверен, как это решает мою проблему. Компонент Animal не имеет ничего общего с компонентом If. Он не должен адаптироваться к компоненту If. Кроме того, я не уверен, что NonNullable имеет какое-либо отношение к моей проблеме
Элия ​​Коэн

@EliyaCohen Обновлено, что-нибудь нужно добавить к этому ответу?
Кейкай

Я одобрил ваш ответ, хотя я не могу принять его, так как это не решение. Вероятно, это будет решено в будущем, когда TS и React сделают такую ​​возможность возможной.
Элия ​​Коэн

1

Короткий ответ?

Ты не можешь

Поскольку вы определили animalкак Animal | undefined, единственный способ удалить undefinedэто либо создать охрану, либо преобразовать animalкак-то еще. Вы скрыли охрану типов в свойстве условия, и TypeScript не знает, что там происходит, поэтому он не может знать, выбираете ли вы между Animalи undefined. Вам нужно будет разыграть его или использовать !.

Однако подумайте: это может показаться чище, но оно создает фрагмент кода, который должен быть понят и поддержан, возможно, кем-то еще в дальнейшем. По сути, вы создаете новый язык, состоящий из компонентов React, который кто-то должен будет выучить в дополнение к TypeScript.

Альтернативный метод условного вывода JSX состоит в определении переменных, в renderкоторых содержится ваш условный контент, например

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

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

Это стандартный подход, который другие разработчики сразу узнают и не должны искать.


0

Используя обратный вызов рендеринга, я могу напечатать, что возвращаемый параметр не имеет значения NULL.

Я не могу изменить ваш оригинальный Ifкомпонент, так как я не знаю, какой у васcondition утверждаете, а также какую переменную он утверждал, т.е.condition={animal !== undefined || true}

К сожалению, я создал новый компонент IsDefinedдля этого случая:

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

Указывает, что childrenэто будет обратный вызов, которому будет передан NonNullable типа T, того же типа, что иcheck .

При этом мы получим обратный вызов рендеринга, которому будет передана переменная, проверенная нулем.

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

У меня есть рабочий пример здесь https://codesandbox.io/s/blazing-pine-2t4sm


Это хороший подход, но, к сожалению, он отрицает всю цель использования Ifкомпонента (чтобы он мог выглядеть чисто)
Элия ​​Коэн
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.