是否可以使用React with TypeScript将子项限制为特定组件?

9q78igpj  于 2022-11-18  发布在  TypeScript
关注(0)|答案(3)|浏览(123)

将React与TypeScript一起使用时,有几种方法可以定义children的类型,比如将其设置为JSX.ElementReact.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>[]
}
e5nszbig

e5nszbig1#

你不能这样约束孩子的React。
任何react函数组件都只是一个具有特定props类型并返回JSX.Element的函数。这意味着如果在传递子组件之前 render 该组件,则react根本不知道生成JSX的是什么,只是沿着。
问题是你用<MyComponent>语法来呈现组件,所以在这之后,它只是一个JSX节点的通用树。
这听起来有点像一个XY problem,但是,如果你需要它,通常有一个更好的方法来设计你的api。
相反,您可以在List上创建和items prop,List接受一个对象数组,这些对象将作为prop传递给List组件中的ListItem
例如:

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']} />

在本例中,您只是输入props,而List知道如何生成它自己的子对象。
Playground

h9vpoimq

h9vpoimq2#

下面是一个基本示例,我将其用于一个包含多个步骤的“向导”。它使用一个主组件WizardSteps(复数)和一个子组件WizardStep(单数),后者具有一个“label”属性,该属性在主WizardSteps组件中呈现。使其正确工作的关键是Children.map(...)调用,该调用确保React将“children”视为一个数组。并且还允许Typescript和IDE正常工作。

const WizardSteps: FunctionComponent<WizardStepsProps> & WizardSubComponents = ({children}) => {
    const steps = Children.map(children, child => child); /* Treat as array with requisite type */

    return (
        <div className="WizardSteps">
            <header>
                <!-- Note the use of step.props.label, which is properly typecast -->
                {steps.map(step => <div className="WizardSteps__step">{step.props.label}</div>)}
            </header>
            <main>
                <!-- Here you can render the body of each WizardStep child component -->
                {steps.map(step => <div className="WizardSteps__body">{step}</div>)}
            </main>
        </div>
    );
}

const Step: FunctionComponent<WizardStepProp> = ({label, onClick}) => {
    return <span className="WizardSteps__label">
        {label}
    </span>
}

WizardSteps.Step = Step;

type WizardSubComponents = {
    Step: FunctionComponent<WizardStepProp>
}

type WizardStepsProps = {
    children: ReactElement<WizardStepProp> | Array<ReactElement<WizardStepProp>>
};

type WizardStepProp = {
    label: string
    onClick?: string
    children?: ReactNode
}
svmlkihl

svmlkihl3#

当然。您只需要使用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

相关问题