为了支持嵌套导航菜单,我们使用React.cloneElement向子菜单组件添加属性(菜单组件是自定义组件,基于react-bootstrap)。为了防止我们克隆所有元素,即使它们不是子菜单组件,而是常规内容组件,我希望克隆是有条件的。
所有菜单组件都是' MenuBase'的子类。 (它本身是React.Component的子类)。在我的代码中,我试图测试一个react组件的子代(通过使用React.Children实用程序函数读取this.props.children)是否是MenuBase的一个实例。
简化代码:
interface IMenuBaseProps {
// menu related properties
}
abstract class MenuBase<P extends IMenuBaseProps, S> extends React.Component<P, S> {
// constructor etc.
}
interface IGeneralMenuProps extends IMenuBaseProps {
// additional properties
}
class GeneralMenu extends MenuBase<IGeneralMenuProps, {}> {
public render(): JSX.Element {
// do some magic
}
}
在菜单逻辑的某处,我想做类似以下的事情
React.Children.map(this.props.children, (child: React.ReactElement<any>): React.ReactElement<any> ==> {
if (child instanceof MenuBase) {
// return clone of child with additional properties
} else {
// return child
}
}
但是,此测试永远不会产生真实结果,因此永远不会进行克隆。
在Chrome开发者工具中,我可以看到:
答案 0 :(得分:6)
查看definition file,ReactChildren.map的定义如下:
map<T>(children: ReactNode, fn: (child: ReactChild, index: number) => T): T[];
这个ReactChild的定义如下:
type ReactChild = ReactElement<any> | ReactText;
在你的情况下,它可能是ReactElement:
interface ReactElement<P> {
type: string | ComponentClass<P> | SFC<P>;
props: P;
key?: Key;
}
您的MenuBase
是type
,因此可能应该是:
React.Children.map(this.props.children, (child: React.ReactElement<any>): React.ReactElement<any> ==> {
if (child.type === MenuBase) {
// return clone of child with additional properties
} else {
// return child
}
}
似乎child.type
是组件的类而不是实例,因此您需要执行child.type instanceof MenuBase
而不是child.type === MenuBase
。
在玩完各种事情之后,我想出了这个解决方案:
React.Children.map(this.props.children, (child: React.ReactElement<any>): React.ReactElement<any> ==> {
if (MenuBase.isPrototypeOf(child.type)) {
// return clone of child with additional properties
} else {
// return child
}
}
如果您想要检查child
是否为GeneralMenu
,那么您需要执行以下操作:
child.type.prototype === GeneralMenu.prototype
答案 1 :(得分:3)
@NitzanTomer的解决方案似乎并不适用于所有情况。我无法在他的机器和我的机器上找到测试结果差异的原因。
最后我找到了以下解决方案:
public render(): JSX.Element {
// Iterate over children and test for inheritance from ComponentBase
let results: JSX.Element[] = React.Children.map(this.props.children, (child: React.ReactElement<any>, index: number): JSX.Element => {
let isComponent: boolean = typeof child.type !== 'string' && React.Component.prototype.isPrototypeOf((child.type as any).prototype);
let result: boolean = isComponent ? (child.type as any).prototype instanceof ComponentBase : false; // Not a component, then never a ComponentBase
return <li>
<div>Child nr. {index} is a React component: {isComponent ? "True" : "False"}</div>
<div>And is a sub class of ComponentBase: {result ? "True" : "False"} { result !== this.props.expectedTestResult ? "(unexpected)" : ""}</div>
</li>;
})
return (
<ul>
{results}
</ul>
)
}
在此解决方案中,首先执行测试以检查子项是否为React组件。之后,'instanceof'用于确定子组件是否是'ComponentBase'的子类(直接或间接)。
因为在TypeScript中,React.Component的'type'属性没有'prototype'属性,所以需要强制转换为'any'。