Можно ли ограничить дочерние элементы определенными компонентами, используя React with TypeScript?

#reactjs #typescript

#reactjs #typescript

Вопрос:

Используя React with TypeScript, есть несколько способов определить тип children , например, установить для него значение JSX.Element или React.ReactChild или расширить PropsWithChildren . Но при этом возможно ли дополнительно ограничить, каким конкретным элементом может быть дочерний элемент React?

 function ListItem() {
  return (
    <li>A list item<li>
  );
}

//--------------------

interface ListProps {
  children: React.ReactChild | React.ReactChild[]
}

function List(props: ListProps) {
  return (
    <ul>
      {props.children} // what if I only want to allow elements of type ListItem here?
    </ul>
  );
}
 

Учитывая приведенный выше сценарий, можно List ли настроить его таким образом, чтобы он допускал только дочерние элементы типа ListItem ? Что-то похожее на следующий (недопустимый) код:

 interface ListProps {
  children: React.ReactChild<ListItem> | React.ReactChild<ListItem>[]
}
 

Ответ №1:

Вы не можете ограничивать дочерние элементы react таким образом.

Любой функциональный компонент react — это просто функция, которая имеет определенный тип реквизита и возвращает JSX.Element . Это означает, что если вы визуализируете компонент перед тем, как передать ему дочерний элемент, то react понятия не имеет, что вообще породило этот JSX, и просто передает его.

И проблема в том, что вы отображаете компонент с <MyComponent> помощью синтаксиса. Итак, после этого момента это просто общее дерево узлов JSX.


Однако это немного похоже на проблему XY. Обычно, если вам это нужно, есть лучший способ разработать свой api.

Вместо этого вы могли бы создать и items List использовать, который принимает массив объектов, которые будут переданы в качестве реквизита ListItem внутри List компонента.

Например:

 function ListItem({ children }: { children: React.ReactNode }) {
  return (
    <li>{children}</li>
  );
}

function List(props: { items: string[] }) {
  return (
    <ul>
      {props.items.map((item) => <ListItem>{item}</ListItem> )}
    </ul>
  );
}

const good = <List items={['a', 'b', 'c']} />
 

В этом примере вы просто вводите реквизит и List знаете, как создавать собственные дочерние элементы.

Игровая площадка

Ответ №2:

Абсолютно. Вам просто нужно использовать React.ReactElement для правильных дженериков.

 interface ListItemProps {
   text: string
}

interface ListProps {
   children: React.ReactElement<ListItemProps> | React.ReactElement<ListItemProps>[];
}
 

Редактировать — я создал для вас пример CodeSandbox:

https://codesandbox.io/s/hardcore-cannon-16kjo?file=/src/App.tsx

Комментарии:

1. Можете ли вы предоставить доказательства использования, потому что я уверен, что это работает не так, как вы ожидаете tsplay.dev/ND574m

2. Я получаю красную строку ListItem , в которой утверждается, что она ссылается на значение вместо типа, спрашивая, имел ли я в виду typeof ListItem . Использование этого позволяет мне продолжить, однако, похоже, это не ограничивает дочерние элементы, которым они предоставлены List . Что я делаю не так?

3. @MagnusBull — Извините, я только что понял, что ListItem это функция, а не тип. Вам нужно будет создать тип для свойства ListItem ( ListItemProps ) и ссылаться на него как на общий.

4. @MagnusBull — Я обновил исходный ответ, включив ссылку на быстрый Typescript / React CodeSandbox, который вы можете проверить, чтобы решить эту проблему. Я делал это много раз для таких вещей, как <Tabs><Tab /><Tab /></Tabs>

5. @AlexWayne — Спасибо, что указали на это. Теперь я подвергаю сомнению все, что, как я думал, я знал о Typescript и React. 🙂 Или просто еще одна среда 🙂