键入一个React组件,克隆其子组件以在TypeScript

时间:2017-01-26 19:59:34

标签: javascript reactjs typescript typescript-typings

假设您有以下组件接受一个或多个子JSX.Elements,并在使用React.cloneElement(child, { onCancel: () => {} }呈现它们时将额外的回调传递给它们。

有问题的组件(摘录):

interface XComponentProps {
    onCancel: (index: number) => void;
}

class XComponent extends React.Component<XComponentProps> {
    render() {
        const { onCancel } = this.props;
        const children = typeof this.props.children === 'function' ?
            React.cloneElement(this.props.children, { onCancel: () => onCancel() }) :
            this.props.children.map((child, idx) => (
                React.cloneElement(child, { onCancel: () => onCancel(idx) })
            ));
        return <div>{children}</div>;
    }
}

有问题的组件(摘录):

interface UserComponentProps { caption: string }
const UserComponent = (props: UserComponentProps) => (
    <button onClick={props.onClose}>{props.caption || "close"}</button>
);

ReactDOM.render(
    <XComponent onCancel={handleCancel}>
        <UserComponent />
        <UserComponent />
        <UserComponent />
    </XComponent>
);

现在TSC抱怨UserComponent在其道具中没有onCancel&#39;接口定义,确实没有。最简单的解决方法是手动定义onCancelUserComponentProps接口。

但是,我想在不修改子节点的prop定义的情况下修复它,以便组件可以接受任意一组React元素。在这种情况下,有没有办法定义返回元素的类型,这些元素在XComponent(父级)级别传递了额外的隐式道具?

4 个答案:

答案 0 :(得分:1)

这是不可能的。无法静态地知道UserComponent从ReactDOM.render上下文中的父XComponent接收的道具。

如果您需要类型安全的解决方案,请将子项用作函数:

以下是XComponent定义

interface XComponentProps {
    onCancel: (index: number) => void;
    childrenAsFunction: (props: { onCancel: (index: number) => void }) => JSX.Element;
}

class XComponent extends React.Component<XComponentProps> {
    render() {
        const { onCancel } = this.props;
        return <div>{childrenAsFunction({ onCancel })}</div>;
    }
}

现在您可以使用它来呈现UserComponent

<XComponent onCancel={handleCancel} childrenAsFunction={props => 
  <span>
    <UserComponent {...props} />
    <UserComponent {...props} />
  </span>
} />

这将很好地工作,我经常使用这种模式没有麻烦。 您可以重构XComponentProps以使用相关部分(此处为childrenAsFunction函数)键入onCancel道具。

答案 1 :(得分:0)

您可以使用接口继承(请参阅Extending Interfaces)并让UserComponentProps扩展XComponentProps:

interface UserComponentProps extends XComponentProps { caption: string }

这将为UserComponentProps提供XComponentProps的所有属性以及它自己的属性。

如果您不想要UserComponentProps定义XComponentProps的所有属性,您也可以使用Partial类型(参见Mapped Types):

interface UserComponentProps extends Partial<XComponentProps> { caption: string }

这将为UserComponentProps提供XComponentProps的所有属性,但使它们成为可选项。

答案 2 :(得分:0)

即使在JSX中,您也可以将子项作为函数传递。通过这种方式,您将获得正确的输入方式。 UserComponents道具界面应该扩展ChildProps。

interface ChildProps {
    onCancel: (index: number) => void;
}

interface XComponentProps {
    onCancel: (index: number) => void;
    children: (props: ChildProps) => React.ReactElement<any>;
}

class XComponent extends React.Component<XComponentProps> {
    render() {
        const { onCancel } = this.props;
        return children({ onCancel })};
    }
}


<XComponent onCancel={handleCancel}>
    { props => <UserComponent {...props} /> } 
</XComponent>

答案 3 :(得分:0)

我知道这已经解决,但是另一个解决方案是限制可以传递的子代类型。限制了包装器的功能,但可能是您想要的。

因此,如果您保留原始示例:

interface XComponentProps {
   onCancel: (index: number) => void;
   children: ReactElement | ReactElement[]
}

class XComponent extends React.Component<XComponentProps> {
    render() {
        const { onCancel } = this.props;
        const children = !Array.isArray(this.props.children) ?
            React.cloneElement(this.props.children, { onCancel: () => onCancel() }) :
            this.props.children.map((child, idx) => (
                React.cloneElement(child, { onCancel: () => onCancel(idx) })
            ));
        return <div>{children}</div>;
    }
}

interface UserComponentProps { caption: string }
const UserComponent: React.FC = (props: UserComponentProps) => (
    <button onClick={props.onClose}>{props.caption || "close"}</button>
);

ReactDOM.render(
    <XComponent onCancel={handleCancel}>
        <UserComponent />
        <UserComponent />
        <UserComponent />
    </XComponent>
);