Typesafe更高阶React组件对类组件和无状态功能组件的影响

时间:2017-04-13 09:17:57

标签: reactjs typescript

我正在尝试编写更高阶的组件来检查用户是否经过身份验证。我正在使用React 15.5.4和@ types / react 15.0.21,我的(简化)代码如下所示:

import * as React from 'react';

interface IAuthProps {
  authenticated: boolean
}

function authenticated1<Props extends Object>(wrapped: React.ComponentClass<Props> | React.SFC<Props>):
  React.SFC<Props & IAuthProps> {
  return (props: Props & IAuthProps): React.ReactElement<any> => {
    if (props.authenticated) {
      return React.createElement(wrapped, props);
    } else {
      return <h1>Unauthorized!</h1>
    }
  }
}

但是,在createElement

的调用中,编译失败
TS2345:Argument of type 'ComponentClass<Props> | StatelessComponent<Props>' is not assignable to parameter of type 'ComponentClass<Props>'.
  Type 'StatelessComponent<Props>' is not assignable to type 'ComponentClass<Props>'.
    Type 'StatelessComponent<Props>' provides no match for the signature 'new (props?: Props | undefined, context?: any): Component<Props, ComponentState>'

由于@types/reactReact.createElement声明为重载函数和Typescript can't resolve overloads with union types,因此错误并不令人惊讶。

但是@types/react为联合中的每个类型提供了合格的重载(SFCEelement继承ReactElement,因此返回类型是兼容的):

function createElement<P>(
    type: ComponentClass<P>,
    props?: Attributes & P,
    ...children: ReactNode[]): ReactElement<P>;

function createElement<P>(
    type: SFC<P>,
    props?: Attributes & P,
    ...children: ReactNode[]): SFCElement<P>;

为了使代码编译,我只需要强制Typescript考虑联合类型中相应分支的重载,但我不知道如何做到这一点。

如何区分ComponentClassSFC以使Typescript为类型检查选择相应的重载?

PS:目前我只是强迫它通过传递wrapped as React.ComponentClass<Props>来选择第一个重载,这是安全的,因为两个重载调用同一个接受两种参数类型的运行时函数,但我宁愿不喜欢在这里“欺骗”,而不是类型系统保证安全。

1 个答案:

答案 0 :(得分:0)

我认为目前无法做到这一点,因为它需要更改类型定义。

要使用类型保护功能,它允许缩小对象的类型,我们需要扩展一些定义。

interface StatelessComponent<P> {
    (props: P & { children?: ReactNode }, context?: any): ReactElement<any>;
    type: 'StatelessComponent'; // NEW ONE
    propTypes?: ValidationMap<P>;
    contextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
}

interface ComponentClass<P> {
    new (props?: P, context?: any): Component<P, ComponentState>;
    type: 'ComponentClass'; // NEW ONE
    propTypes?: ValidationMap<P>;
    contextTypes?: ValidationMap<any>;
    childContextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
}

然后我们就可以做那样的事了

function createElementWrapper<Props extends Object>(wrapped: React.ComponentClass<Props> | React.SFC<Props>, props: any) {
  if (wrapped.type === "StatelessComponent") {
    return React.createElement(wrapped, props); // wrapped is a StatelessComponent
  } else {
    return React.createElement(wrapped, props); // wrapped is a ComponentClass
  }
}

在绝对式存储库中创建有关此问题的问题可能是个好主意