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

Posted

技术标签:

【中文标题】如何在 TypeScript 中为 React Apollo Query 组件编写 HOC?【英文标题】:How to write HOC for React Apollo Query component in TypeScript? 【发布时间】:2019-10-05 14:39:52 【问题描述】:

我正在尝试编写 HOC 以在组件中显示已用查询的信息,如下所示:

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 也隐含了一个“任意”类型。

此示例的 CodeSanbox 是 here。

如何为这个 HOC 编写合适的类型?

【问题讨论】:

我不确定最初的错误,但您可以通过 a.) 从 react-apollo 导入 QueryResult 为查询结果提供 QueryResult 类型。 B.) 声明类型如下:(loading, data, error:QueryResult) @MichaelKnight 是的,这只是部分解决方案,但在使用QueryResult 时不会输入data。我的目标是在包装在 HOC 中时不要触摸原始 RatesQueryWithInfo 【参考方案1】:

我的阅读方式是Query 组件中声明的propTypesQueryProps(该组件的道具)之间存在不匹配。错误的 props 修复如下(以 cmets 中的原始类型):

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&lt;P&gt; 来验证 propTypes 和 props 是否一致。

最简单的解决方案(对 react-apollo 使用 PR)是为 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&lt;T&gt; 在 react 中的行为改变为不是协变的,而是相反的。

使用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 

【讨论】:

感谢您的详细分析。你能解释一下WeakComponentClass是什么吗?或者,如果您根据我的示例提供修改后的 CodeSanbox 并使用此 WeakComponentClass,那么最好的方法是。 @Everettss wops .. 昨晚很晚了,我忘了发布那个界面。它只是类组件的自定义接口,仅包含构造函数,不对其他任何内容进行任何检查。将其添加到答案中

以上是关于如何在 TypeScript 中为 React Apollo Query 组件编写 HOC?的主要内容,如果未能解决你的问题,请参考以下文章

在 React 应用程序中为 Firebase 正确的 Typescript 类型

如何在 TypeScript 中为 css 模块设置汇总

在 typescript 中为 JSX.Element 添加更多道具

在为第三方库进行模块扩充时,如何在 VSCode 中为 JavaScript 中的代码库启用 TypeScript 智能感知?

TypeScript:如何使用泛型输入 react-query useMutation onError?

如何从 React Typescript 的整个项目中删除未使用的导入/声明?