Как написать HOC для компонента React Apollo Query в TypeScript? - PullRequest
1 голос
/ 18 мая 2019

Я пытаюсь написать 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 для этого примера здесь .

Как написать надлежащие типы для этого HOC?

1 Ответ

1 голос
/ 21 мая 2019

То, как я это прочитал, - это несоответствие между объявленным propTypes в Query компоненте и QueryProps (реквизиты для этого компонента).Неверные реквизиты исправлены ниже (с оригинальным типом в комментариях):

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 для реакции-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, реагирует-Аполлон работаеткак и ожидалось:

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 
}
...