如何在 TypeScript 中为 React Apollo Query 组件编写 HOC?
How to write HOC for React Apollo Query component in TypeScript?
我正在尝试编写 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 }
隐含地具有 'any' 类型。
此示例的 CodeSanbox 是 here。
如何为此 HOC 编写正确的类型?
我的阅读方式是 Query
组件中声明的 propTypes
与 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
和 props 是一致的。
最简单的解决方案(禁止 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,因为尽管它们修复了打字问题,但它们没有准确反映 run-time 所做的验证 propTypes
。也许更好的方法是将 Validator<T>
的行为更改为 co-variant,而不是 contra-variant。
使用 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
}
我正在尝试编写 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 }
隐含地具有 'any' 类型。
此示例的 CodeSanbox 是 here。
如何为此 HOC 编写正确的类型?
我的阅读方式是 Query
组件中声明的 propTypes
与 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
和 props 是一致的。
最简单的解决方案(禁止 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,因为尽管它们修复了打字问题,但它们没有准确反映 run-time 所做的验证 propTypes
。也许更好的方法是将 Validator<T>
的行为更改为 co-variant,而不是 contra-variant。
使用 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
}