仅允许特定组件作为 React 和 Typescript 中的子组件

问题描述 投票:0回答:3

我只想允许特定组件作为子组件。例如,假设我有一个菜单组件,它应该只包含

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 来实现这一点?喜欢用仅与特定组件相关的东西来扩展 Element 类型吗?

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

reactjs typescript typing
3个回答
30
投票

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

12
投票

尽管有上述答案,但你不能对孩子这样做。您可以在组件的开发版本中执行运行时检查,但无法使用 TypeScript 类型执行此操作 — 至少目前还不能。

来自 TypeScript 问题 #18357

现在除了在

ElementChildrenAttribute
命名空间内指定
JSX
之外,无法指定子代表示是什么。它与子项的 React 表示紧密结合,这意味着子项是 props 的一部分。这使得无法对具有单独存储子项的实现的子项启用类型检查,例如 https://github.com/dfilatov/vidom/wiki/Component-properties

并请注意,它在 #21699 中被引用,基本上,围绕

ReactElement
的可能破坏性更改也可能使执行此操作成为可能。

现在,您所能做的就是运行时检查,或接受 props (或 props 数组)和可选的组件函数(在您的情况下,您知道它是

MenuItem
)并在组件中创建元素。

还有一个问题是你是否应该这样做。为什么我不能编写一个返回

MenuItem
的组件,而不是直接使用
MenuItem
? :-)


0
投票

我一直在研究和记录,我可能刚刚找到了解决方案。

const childrenWithProps = React.Children.map(props.children, (child) => {
    console.log(child.type)
    if (React.isValidElement(child) &&
        ((child.type as any)._payload.value.name === NavLinks.name)) {
        return cloneElement(child, { ref: menuRef });
    }
    
    throw new Error(`Navbar solo puede recibir ${NavLinks.name} o NavbarIcons como children.`);
});

在 vscode 中,属性 _payload 无法从 child.type 访问,因此我将其设置为 any,这样 IDE 就不会抛出错误。

© www.soinside.com 2019 - 2024. All rights reserved.