Разрешить только определенные компоненты как дочерние в React и Typescript

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

<Menu>
  <MenuItem />
  <MenuItem />
</Menu>

Поэтому я хотел бы, чтобы Typescript выдавал мне ошибку в среде IDE, когда я пытаюсь сделать другой компонент дочерним. Что-то предупреждает меня, что я должен использовать MenuItem только как дети. Например, в этой ситуации:

<Menu>
  <div>My item</div>
  <div>My item</div>
</Menu>

Эта ветка почти аналогична, но не включает решение TypeScript. Мне было интересно, можно ли решить эту проблему с помощью типов и интерфейсов TypeScript. В моем воображаемом мире это выглядело бы так, но, конечно, проверка типов не работает, потому что дочерний компонент имеет тип Element:

type MenuItemType = typeof MenuItem;

interface IMenu {
  children: MenuItemType[];
}

const MenuItem: React.FunctionComponent<IMenuItem> = ({ props }) => {
  return (...)
}

const Menu: React.FunctionComponent<IMenu> = ({ props }) => {
  return (
    <nav>
      {props.children}
    </nav>
  )
}

const App: React.FunctionComponent<IApp> = ({ props }) => {
  return (
    <Menu>
      <MenuItem />
      <MenuItem />
    </Menu>
  )
}

Есть ли способ добиться этого с помощью Typescript? Хотите расширить тип элемента чем-то, относящимся только к определенному компоненту?

Или что было бы хорошим подходом к тому, чтобы быть уверенным, что дочерний элемент является экземпляром определенного компонента? Без необходимости добавлять условие, которое смотрит на дочерний компонент displayName.


person neiya    schedule 23.08.2019    source источник
comment
Что, если дочерние компоненты расширяют тип, а не компонент, где этот тип расширяет компонент плюс дополнительный атрибут?   -  person Amir-Mousavi    schedule 23.08.2019


Ответы (1)


Для этого вам нужно извлечь интерфейс props из дочернего компонента (и, желательно, также родительского) и использовать его следующим образом:

interface ParentProps {
    children: ReactElement<ChildrenProps> | Array<ReactElement<ChildrenProps>>;
}

так в вашем случае это будет выглядеть так:

interface IMenu {
  children: ReactElement<IMenuItem> | Array<ReactElement<IMenuItem>>;
}

const MenuItem: React.FunctionComponent<IMenuItem> = ({ props }) => {
  return (...)
}

const Menu: React.FunctionComponent<IMenu> = ({ props }) => {
  return (
    <nav>
      {props.children}
    </nav>
  )
}
person Minwork    schedule 23.08.2019
comment
Отличное решение, хорошо работает! И можно ли проверить, что он содержит только дочерние элементы этих типов? Например, если есть компонент MenuItem и тег div, ошибка не отображается. - person neiya; 23.08.2019
comment
Я думаю, что это невозможно в машинописном тексте (но я могу ошибаться), потому что он не может динамически разрешить, что компоненты, вложенные внутри какого-либо компонента, должны соответствовать типу, объявленному в этой опоре дочерних компонентов компонента. - person Minwork; 23.08.2019
comment
Также из того, что я помню, хотя было технически возможно использовать PropTypes в jsx, потребовался неприятный взлом, чтобы извлечь имя компонента (которое не всегда может быть там) и сравнить его с именами дочерних компонентов. - person Minwork; 23.08.2019
comment
На самом деле я не знаю, верен ли этот ответ. Я не знаю, что проверяет эти типы при children добавлении, но они, кажется, только гарантируют, что дочерний элемент существует, а не того, что он относится к определенному типу. Проблема сводится к тому, что все, что отображает JSX, возвращает JSX.Element, что является общим и недостаточно конкретным для сравнения. Дополнительные сведения об этом выпуске можно найти на github TypeScript: github.com/microsoft/TypeScript/issues/ 21699 - person Salem; 28.04.2021
comment
Я с @Salem по этому поводу. Что, если бы у вас был RedButtonContainer компонент, который может иметь только RedButton в качестве дочерних? Если RedButton использует те же свойства, что и BlueButton, они будут иметь тот же тип (что касается TypeScript), даже если кнопки отображаются синим, а не красным цветом. RedButton и BlueButton были бы просто псевдонимами для одного и того же, верно? - person ChrisCrossCrash; 16.05.2021
comment
Вы правы, @ChrisCrossCrash. В TS лучшее, что вы можете получить, - это различать компоненты по реквизитам. Как я сказал ранее, отличить компоненты друг от друга непросто, а в чистом JS это становится еще сложнее. - person Minwork; 17.05.2021