如何在TypeScript中为React Apollo查询组件编写HOC?

时间:2019-05-18 15:31:32

标签: reactjs typescript apollo react-apollo

我正在尝试编写HOC,以在这样的组件中显示使用过的Query的信息:

const GET_RATES = gql`
  query ratesQuery {
    rates(currency: "USD") {
      currency
      rate
    }
  }
`;

class RatesQuery extends Query<{
  rates: { currency: string; rate: string }[];
}> {}

const RatesQueryWithInfo = withQueryInfo(RatesQuery);

const Rates = () => (
  <RatesQueryWithInfo query={GET_RATES}>
    {({ loading, error, data }) => {
      if (loading) return "Loading...";
      if (error || !data) return "Error!";

      return (
        <div>
          {data.rates.map(rate => (
            <div key={rate.currency}>
              {rate.currency}: {rate.rate}
            </div>
          ))}
        </div>
      );
    }}
  </RatesQueryWithInfo>
);

withQueryInfo看起来像(它的实现基于article):

const withVerbose = <P extends object>(
  WrappedComponent: React.ComponentType<P>
) =>
  class extends React.Component<P> {
    render() {
      return (
        <div>
          {(this.props as any).query.loc.source.body}
          <WrappedComponent {...this.props as P} />;
        </div>
      );
    }
  };

此HOC正常工作(它在原始组件上方附加了查询字符串),但键入已损坏

withQueryInfo(RatesQuery)中的错误

Argument of type 'typeof RatesQuery' is not assignable to parameter of type 'ComponentType<QueryProps<{ rates: { currency: string; rate: string; }[]; }, OperationVariables>>'.
  Type 'typeof RatesQuery' is not assignable to type 'ComponentClass<QueryProps<{ rates: { currency: string; rate: string; }[]; }, OperationVariables>, any>'.
    Types of property 'propTypes' are incompatible.
      Type '{ client: Requireable<object>; children: Validator<(...args: any[]) => any>; fetchPolicy: Requireable<string>; notifyOnNetworkStatusChange: Requireable<boolean>; onCompleted: Requireable<(...args: any[]) => any>; ... 5 more ...; partialRefetch: Requireable<...>; }' is not assignable to type 'WeakValidationMap<QueryProps<{ rates: { currency: string; rate: string; }[]; }, OperationVariables>>'.
        Types of property 'fetchPolicy' are incompatible.
          Type 'Requireable<string>' is not assignable to type 'Validator<"cache-first" | "cache-and-network" | "network-only" | "cache-only" | "no-cache" | "standby" | null | undefined>'.
            Types of property '[nominalTypeHack]' are incompatible.
              Type 'string | null | undefined' is not assignable to type '"cache-first" | "cache-and-network" | "network-only" | "cache-only" | "no-cache" | "standby" | null | undefined'.
                Type 'string' is not assignable to type '"cache-first" | "cache-and-network" | "network-only" | "cache-only" | "no-cache" | "standby" | null | undefined'.ts(2345)

{ loading, error, data }也隐式具有'any'类型。

此示例的CodeSanbox是here

如何为此HOC编写适当的类型?

1 个答案:

答案 0 :(得分:1)

我的阅读方式是propTypes组件中声明的QueryQueryProps(该组件的道具)之间不匹配。错误的道具已在下面固定(注释中为原始类型):

export default class Query<TData = any, TVariables = OperationVariables> extends React.Component<QueryProps<TData, TVariables>> {
    static propTypes: {
        // ...
        client: PropTypes.Requireable<ApolloClient<any>>; //PropTypes.Requireable<object>;
        // ...
        fetchPolicy: PropTypes.Requireable<FetchPolicy>; //PropTypes.Requireable<string>;
        // ...
        query: PropTypes.Validator<DocumentNode>; // PropTypes.Validator<object>;
    };
}

除了您尝试创建HOC并使用React.ComponentType<P>来验证propTypes与道具是否一致之外,这种不兼容性通常并不重要。

最简单的解决方案(禁止PR进行react-apollo)是对WrappedComponent使用一个较弱的类型,该类型不验证propTypes

使用下面的定义,客户端代码将按预期工作:

interface WeakComponentClass<P = {}, S = React.ComponentState> extends React.StaticLifecycle<P, S> {
  new (props: P, context?: any): React.Component<P, S>;
}

const withVerbose = <P extends any>(
  WrappedComponent: WeakComponentClass<P> | React.FunctionComponent<P>
) =>
  class extends React.Component<P> {
    render() {
      return (
        <div>
          {(this.props as any).query.loc.source.body}
          <WrappedComponent {...this.props as P} />;
        </div>
      );
    }
  };

注意::我犹豫要提交带有更正类型的PR,尽管它们可以解决打字问题,但它们无法准确反映出propTypes执行的运行时验证。也许更好的方法是将Validator<T>的反应本身的行为更改为不是协变的,而是相反的。

使用Validator的此定义,react-apollo可以按预期工作:

export interface Validator<T> {
    (props: object, propName: string, componentName: string, location: string, propFullName: string): Error | null;
    [nominalTypeHack]?: (p: T) => void; // originally T, now behaves contra-variantly 
}