Як я можу зробити компонент 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>
);

Як я коментував, що насправді тварина не визначена, я не можу сказати Typescript, що я це вже перевірив. Твердження animal!працювало б, але це не те, що я шукаю.

Будь-які ідеї?


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

1
Чи працює це кастинг? <Animal animal={animal as Animal} />
Павло

@ OlivierBoissé Мені заборонено використовувати цей синтаксис
Елія Коен

@paul так, але хіба це не було б подібне до "!" наприкінці?
Елія Коен

Відповіді:


3

Це здається неможливим.

Причина: якщо ми змінюємо Ifкомпонента змісту від

if (props.condition) {
  ...
}

йому навпаки

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

Ви дізнаєтесь, що цього разу ви хочете, щоб тип розроблений протилежно.

  <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з
умовними типами дистрибутива typecript : 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.


Я не впевнений, як це вирішує мою проблему. Тваринний компонент не має нічого спільного з компонентом If. Він не повинен адаптувати себе до компонента If. Плюс, я не впевнений, як NonNullable має відношення до моєї проблеми
Елія Коен,

@EliyaCohen Оновлено, щось потрібно додавати до цієї відповіді?
keikai

Я схвалив вашу відповідь, хоча я не можу її прийняти, оскільки це не є рішенням. Можливо, це буде вирішено в майбутньому, коли 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

Використовуючи зворотний виклик візуалізації, я можу ввести, що параметр повернення не зводиться до нуля.

Я не в змозі змінити ваш оригінальний 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компонента (щоб він міг виглядати чисто)
Eliya Cohen
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.