【问题标题】:Constraint child elements约束子元素
【发布时间】:2021-12-25 13:46:17
【问题描述】:

我有一个Modal 函数组件,它应该有三个子函数组件HeaderBodyFooter,我想将Modal 限制为只允许Header | Body | Footer 类型的元素,因为它是顶级子元素。

<Modal>
  <Modal.Header></Modal.Header>
  <Modal.Body></Modal.Body>
  <Modal.Footer></Modal.Footer>
</Modal> 

我已经创建了函数组件,但我不知道如何约束它们:

import React, { ReactElement, ReactNode } from 'react'

function Header(props: { children: ReactElement }) {
  const { children } = props
  return <>{children}</>
}

function Body(props: { children: ReactElement }) {
  const { children } = props
  return <>{children}</>
}

function Footer(props: { children: ReactElement }) {
  const { children } = props
  return <>{children}</>
}


function Modal(props: { children }) {
  const { children } = props
  return <>{children}</>
}

Modal.Header = Header
Modal.Body = Body
Modal.Footer = Footer

export default Modal

我试图为这样的函数创建一个返回类型(例如Header),但它并没有阻止通过例如&lt;div/&gt; 而不是 Header 元素:

type Header = ReactElement
function Header(props: { children: ReactElement }): Header => {...}
function Modal(props: { children: Header }): ReactElement => {...}

我创建了一个 TypeScript Playground here

【问题讨论】:

    标签: reactjs typescript


    【解决方案1】:

    您的Modal 可以遍历其子级并丢弃任何不允许的内容并记录警告。

    const Header = props => props.children;
    const Body = props => props.children;
    const Footer = props => props.children;
    
    const Modal = ({ children }) => {
      const content = [];
      React.Children.forEach(children,
        (child) => {
          if ([Header, Body, Footer].includes(child.type))
            content.push(child);
          else
            console.warn(`child elements of type ${child.type} not allowed as content`);
        }
      );
      return content;
    };
    
    Modal.Header = Header;
    Modal.Body = Body;
    Modal.Footer = Footer;
    

    【讨论】:

    • OP 询问打字稿类型约束而不是运行时约束。
    • @KarenGrigoryan 从目前的措辞来看,这个问题并不是很清楚。
    • 如果您打开 OP 提供的 ts 游乐场链接,那就更清楚了。该帖子还带有 ts 标记,并且 OP 提到他尝试并因返回类型约束而失败。无论如何,如果您有疑问,您可以在发布答案之前先在 cmets 中询问。
    【解决方案2】:

    TL;DR:由于在 TypeScript 中键入 JSX 元素,这目前是不可能的。如果您愿意,请继续阅读以获取更多详细信息。

    罪魁祸首

    来自TS JSX Docs

    JSX 结果类型

    默认情况下,JSX 表达式的结果类型为 any。您可以通过指定 JSX.Element 接口来自定义类型。但是,无法从此接口检索有关 JSX 的元素、属性或子项的类型信息。这是一个黑盒子。

    我在该主题上发现的几乎所有其他问题(如 this one)链接自 2018 年 2 月和 still active 以来都已打开。

    打字兔子洞

    react typingsJSX.Element 定义为React.ReactElement&lt;any, any&gt;。除了any, any 一开始看起来没有希望之外,让我们看看我们是否可以以任何方式利用ReactElement 来进一步锁定打字以帮助我们进一步打字(我们知道答案不是,但让我们看看为什么会这样)。

    感谢 GitHub 对 React 类型的出色索引,我们可以轻松找到 React.ReactElement&lt;P, T&gt; 定义 here

    interface ReactElement<P = any, T extends string | JSXElementConstructor<any> = string | JSXElementConstructor<any>> {
        type: T;
        props: P;
        key: Key | null;
    }
    

    告诉我们第一个类型参数是用于道具的(所以除非我们想尝试基于道具进行类型检查,否则这没有用,请参阅下面的替代段落)。第二个参数让我们更进一步; type: T 听起来很有趣,但仅限于 string | JSXElementConstructor&lt;any&gt;。也许这能让我们走得更远?

    答案是没有了。找到definition后:

    type JSXElementConstructor<P> =
        | ((props: P) => ReactElement<any, any> | null)
        | (new (props: P) => Component<any, any>);
    

    原来JSXElementConstructor&lt;any&gt;中的any代表某种道具。

    为了完整起见,查看Componentdefinition 我们会发现它将类组件的props 和state 的类型作为类型参数。

    我试着从那里开始工作并用类似的东西输入 Modal's child

    type ModalChild = ReactElement<unknown, new (props: unknown) => MHeader>;
    
    function Modal(props: { children: ModalChild | ModalChild[] }) {
      const { children } = props
      return <>{children}</>
    }
    

    但是,这仍然以某种方式允许跨度和 div 作为子级。

    这里没有太多的进展,因此 JSX 文档中的声明似乎也适用于 React 类型的特定情况。

    (非)替代品

    网站上有一个相关问题讨论了同样的问题:How do I restrict the type of React Children in TypeScript, using the newly added support in TypeScript 2.3?。现有答案建议通过组件所采用的道具进行类型检查,但是我无法开始工作(并且两个高票数的答案都指出也没有适当的类型检查)。为了完整起见,我仍然想提一下这种方法的想法。

    我很惊讶我会这么说但是在这种情况下,Flow 比 TypeScript 有一个优势,因为它的 React 类型(特别是 React.Element)采用定义类型的通用参数组件,因此只允许特定项目。查看他们关于主题的docs 页面,将他们的道具输入为

    type Props = {
      children: React.ChildrenArray<React.Element<typeof TabBarIOSItem>>,
    };
    

    TabBarIOS 的子代限制为TabBarIOSItems。

    【讨论】:

    • 感谢您的详细解释。
    猜你喜欢
    • 1970-01-01
    • 2011-07-10
    • 2016-06-08
    • 1970-01-01
    • 1970-01-01
    • 2019-03-29
    • 2020-01-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多