在 React 和 Typescript 中仅允许特定组件为 children

Only allow specific components as children in React and Typescript

我只想允许特定组件作为 children。例如,假设我有一个菜单组件,它应该只包含 MenuItem 作为 children,像这样:

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

所以当我尝试将另一个组件作为 child 时,我希望 Typescript 在 IDE 中抛出一个错误。警告我应该只使用 MenuItem 作为 children。例如在这种情况下:

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

This thread 几乎相似,但不包括 TypeScript 解决方案。我想知道这个问题是否可以使用 TypeScript 类型和接口来解决。在我想象的世界中,它看起来像这样,但是类型检查当然不起作用,因为 child 组件有一个 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 实现这个?想用仅与特定组件相关的东西扩展 Element 类型?

或者什么是确定 child 是特定组件实例的好方法?无需添加查看 child 组件 displayName.

的条件

为此,您需要从子组件(最好是父组件)中提取 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>
  )
}

尽管上面有答案,但您不能使用 children 来做到这一点。您可以在组件的开发版本中执行运行时检查,但您不能对 TypeScript 类型执行此操作 — 至少现在还不能。

来自 TypeScript 问题 #18357

Right now there's no way to specify what children representation is, except specifying ElementChildrenAttribute inside JSX namespace. It's heavily coupled with React representation for children which implies that children is a part of props. This makes impossible to enable type checking for children with implementations which store children separately, for instance https://github.com/dfilatov/vidom/wiki/Component-properties.

请注意,它在 #21699 中被引用,基本上 possibly-breaking 周围 ReactElement 的变化也可能使它成为可能。

现在,您所能做的就是运行时检查,或接受道具(或道具数组)和可选的组件函数(在您的情况下,您知道它是 MenuItem)并在其中创建元素你的组件。

还有一个问题是您是否应该这样做。为什么我不能编写 returns 和 MenuItem 的组件,而不必直接使用 MenuItem? :-)