如何解决打字稿错误“'X' 类型的参数不可分配给 'Y' 类型的参数

How to resolve typescript error "Argument of type 'X' is not assignable to parameter of type 'Y'

更新:Minimum example code reproduction

我有生成以下类型的自动生成代码 (Graphql-Codegen)。为了简单起见,我对其进行了压缩。

export type AccessControlList = {
  __typename?: 'AccessControlList';
  id: Scalars['Int'];
  subscriber: Scalars['Int'];
  subscriberRel: MasterSubscriberData;
};

export type Query = {
  __typename?: 'Query';
  workOrders: Array<WorkOrdersGroupBy>;
  viewer: AccessControlAccounts;
};

因为我只对这个例子的查看器感兴趣,所以我 select 使用 Typescript Pick

export type ViewerType = Pick<Query, 'viewer'>;

它还会为需要 AccessControlAccounts 中一些但不是所有可能字段的查询自动生成专用类型,而我需要的字段各不相同。我在这里省略了 MasterSubscriberData 的类型定义,但它可以在下面推断出来(但这个例子并不是真的需要)

不同的查询需要访问查询结构的不同部分,因此为每个不同的查询生成不同的类型,例如

export type LeadSourcesQuery = 
{ __typename?: 'Query', 
  viewer: { __typename?: 'AccessControlAccounts', 
            AccessControlList: Array<{ __typename?: 'AccessControlList', 
                                     subscriberRel: { __typename?: 'MasterSubscriberData', 
                                     LeadSources: Array<{ __typename?: 
                                     'LeadSources', id: number> } 
                                    }> 
         } 
};

我正在尝试创建一个通用函数,它可以 return 对经常访问的深层嵌套对象的引用,而不管传递的子查询类型如何(例如 LeadSourcesQuery 等)。我想出的东西在运行时有效,但未能通过 Typescript 静态检查。

// expected subscriberRel return type
type SubRetType<T extends ViewerType> =
    T["viewer"]["AccessControlList"][number]["subscriberRel"];

// returning a reference to subscriberRel to avoid doing this nesting everywhere
export const subscriber = <T extends ViewerType>(
    result: T | undefined
): SubRetType<T> | undefined => {
    if (!result) return undefined;
    return result.viewer?.AccessControlList.at(0)?.subscriberRel;
};

每当我在 src 中调用此代码时,我都会收到以下 Typescript 错误,我知道这与 type compatibility.

有关

Argument of type 'ListCustomerTypesQuery | undefined' is not assignable to parameter of type 'ViewerType | undefined'.ts(2345) Types of property 'viewer' are incompatible. ...is missing the following properties from type AccessControlAccounts

我只是不确定如何帮助 Typescript 理解我传递的任何 T 都将始终是 Query 的子类型。但是,由于不在超类型 Query 上使用 extends,Typescript 无法理解 AccessControlList.subscriberRel 的嵌套字段存在于“编译时”。并且专用泛型 T 并没有真正扩展超类型 Query.

每当我不使用泛型时,我可以使用 Extract<Query, ListCustomerTypesQuery> 来提取公共属性,但是当我使用泛型 Extract<Query, T> 时,这不会使错误消失。

我可以通过这样强制转换它来解决这个错误,但我不想每次调用函数都这样做。

subscriber(result.value as ViewerType);

我更愿意清理它并保持原样

subscriber(result.value);

Query 的任何子类型听起来都像是 DeepPartial 的任务。您需要 Query 的任何可能的子类型,这意味着它的所有属性都是可选的。这是 DeepPartial:

type DeepPartial<T> = T extends object ? {
    [P in keyof T]?: DeepPartial<T[P]>;
} : T;

您可能还需要使其适用于数组。

然后我们更改 subscriber 的签名以获取 ViewerType 的深度部分:

export const subscriber = <T extends ViewerType>(
  result: DeepPartial<T> | undefined /** Extract<ViewerType,T> doesn't work either */
): SubRetType<T> | undefined => {

不幸的是,您仍然需要转换最后一个 return,因为 TypeScript 不理解您只需要子类型:

return result.viewer?.AccessControlList?.at(0)?.subscriberRel as SubRetType<T>;

Playground