8

Когда использовать JSX.Element, ReactNode и ReactElement?

1

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

Я всегда использовал JSX.Element в качестве типа возвращаемого значения, но теперь это не работает, если компонент решает не ничего не рендерить, то есть возвращает null, поскольку null не является допустимым значением для JSX.Element. Это стало отправной точкой моей проблемы. Я искал информацию в интернете и нашел, что следует использовать ReactNode, который включает null и несколько других возможных значений.

Однако при создании функционального компонента TypeScript выдает ошибку относительно типа ReactNode. После дополнительных поисков я выяснил, что для функциональных компонентов следует использовать ReactElement. Но если я делаю это, проблема совместимости исчезает, но теперь TypeScript снова жалуется на то, что null не является допустимым значением.

Чтобы не затягивать, у меня три вопроса:

  1. В чем разница между JSX.Element, ReactNode и ReactElement?
  2. Почему методы render классовых компонентов возвращают ReactNode, а функциональные компоненты возвращают ReactElement?
  3. Как решить эту проблему в отношении null?

2 ответ(ов)

0

Вот перевод кода интерфейса AppProps на русский язык в стиле ответа на StackOverflow:

export declare interface AppProps {
  children1: JSX.Element; // плохой вариант, не учитывает массивы
  children2: JSX.Element | JSX.Element[]; // средний вариант, не принимает строки
  children3: React.ReactChildren; // несмотря на название, это совершенно неподходящий тип; это утилита
  children4: React.ReactChild[]; // лучше, принимает дочерние элементы в виде массива
  children: React.ReactNode; // лучший вариант, принимает всё (см. крайний случай ниже)
  functionChildren: (name: string) => React.ReactNode; // рекомендованный тип для функции, используемой как render prop
  style?: React.CSSProperties; // для передачи стилей через пропсы
  onChange?: React.FormEventHandler<HTMLInputElement>; // обработка событий форм! Обобщенный параметр - это тип event.target
  // Дополнительная информация: https://react-typescript-cheatsheet.netlify.app/docs/advanced/patterns_by_usecase/#wrappingmirroring
  props: Props & React.ComponentPropsWithoutRef<"button">; // для передачи всех пропсов элемента button и явной передачи refs
  props2: Props & React.ComponentPropsWithRef<MyButtonWithForwardRef>; // для передачи всех пропсов MyButtonWithForwardRef и явной передачи его refs
}

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

0

Давайте разберем разницу между JSX.Element, React.ReactElement и React.ReactNode шаг за шагом:

JSX.Element и React.ReactElement

JSX.Element и React.ReactElement — это по сути один и тот же тип. Их можно использовать взаимозаменяемо.

Рассмотрим следующий пример:

const element = <div className="greeting">Hello, world!</div>;

Когда этот JSX-код выполняется, он не создает непосредственно видимый элемент на экране. Вместо этого он создает объект JavaScript, который описывает этот элемент пользовательского интерфейса. Этот объект можно рассматривать как чертеж или рецепт. Вот как будет выглядеть этот объект:

const element = {
  type: 'div',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};
  1. type: Это говорит React, какой тип элемента мы хотим создать. В данном случае это div.
  2. props: Это объект, который содержит все свойства (или "пропсы"), которые мы передали нашему элементу. Здесь мы передали пропс className со значением 'greeting'.
  3. children: Это специальный проп, который представляет содержимое внутри нашего элемента. В этом случае это строка 'Hello, world!'.

Этот объект используется React для понимания того, как построить и обновить фактический интерфейс на экране. Когда React видит этот объект, он знает, что нужно создать элемент div, задать ему класс 'greeting' и поместить текст 'Hello, world!' внутрь.

В двух словах, можно сказать, что ReactElement — это объект с типом и пропсами.

В чем разница между JSX.Element и React.ReactElement?

JSX.Element — это тип TypeScript для выражений JSX, а React.ReactElement — это тип React для элементов React.

Правильно ли это?

const Component = ({
  children,
}: {
  children: JSX.Element;
}) => {
  return <div>{children}</div>;
};

// Использование
<Component>hello world</Component>

В приведенном выше примере TypeScript недоволен, потому что мы объявили тип children как JSX.Element, однако мы передаем строку ("hello world"), которая не является JSX.Element. Поэтому нам нужен другой тип для children, который может принимать строку, число, null, ReactElement и т.д.

Здесь на помощь приходит React.ReactNode.

React.ReactNode

Согласно определению React.ReactNode, это может быть ReactElement, строка, число, ReactFragment, ReactPortal, булевое значение, null и undefined.

declare namespace React {
  type ReactNode =
    | ReactElement
    | string
    | number
    | ReactFragment
    | ReactPortal
    | boolean
    | null
    | undefined;
}

Пример:

const Component = ({
  children,
}: {
  children: React.ReactNode
}) => {
  return <div>{children}</div>;
};

// Использование
<Component> Hello World </Component>

Теперь TypeScript доволен!!

Счастливого кодинга!

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь