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



使用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>
);
}

在上述情况下,是否可以以只允许ListItem类型的子级的方式设置List?类似于以下(无效)代码的东西:

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

不能像这样约束react子级。

任何react函数组件都只是一个具有特定props类型并返回JSX.Element的函数。这意味着,如果在向组件传递子组件之前渲染,那么react根本不知道是什么生成了该JSX,而只是传递它。

问题是,您使用<MyComponent>语法来渲染组件。因此,在这一点之后,它只是一个JSX节点的通用树。


这听起来有点像XY问题。通常,如果您需要,有一种更好的方法来设计您的api。

相反,您可以在List上制作和items道具,它接受一个对象数组,这些对象将作为道具传递给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就知道如何生成自己的子级。

游乐场

下面是一个简单的例子,我正在使用它;向导";具有多个步骤。它使用主组件WizardSteps(复数)和子组件WizardStep(单数);标签";属性,该属性在主WizardSteps组件中呈现。使这项工作正确进行的关键是Children.map(…)调用,它确保React处理";儿童";作为数组,还允许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
}

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

最新更新