使用instanceof测试React组件的基类

时间:2016-09-08 09:34:02

标签: reactjs typescript

为了支持嵌套导航菜单,我们使用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开发者工具中,我可以看到:

  1. child.type = function GeneralMenu(道具)
  2. child.type.prototype = MenuBase
  3. typescript child watch

    有人可以帮我澄清以下内容:

    1. 为什么instanceof不工作
    2. 如果我不能在反应组件的继承链中使用测试实例,我的替代方案是什么(我知道我可以测试IMenuBaseProps的一个属性的存在,但是我不太喜欢那个解决方案。)

2 个答案:

答案 0 :(得分:6)

查看definition fileReactChildren.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;
}

您的MenuBasetype,因此可能应该是:

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'。