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

use*_*925 11 typescript typescript-generics graphql-codegen

更新:最小示例代码复制

我有自动生成的代码(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;
};
Run Code Online (Sandbox Code Playgroud)

由于我只对此示例的查看器感兴趣,因此我使用 Typescript Pick 选择它

export type ViewerType = Pick<Query, 'viewer'>;
Run Code Online (Sandbox Code Playgroud)

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

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

export type LeadSourcesQuery = 
{ __typename?: 'Query', 
  viewer: { __typename?: 'AccessControlAccounts', 
            AccessControlList: Array<{ __typename?: 'AccessControlList', 
                                     subscriberRel: { __typename?: 'MasterSubscriberData', 
                                     LeadSources: Array<{ __typename?: 
                                     'LeadSources', id: number> } 
                                    }> 
         } 
};
Run Code Online (Sandbox Code Playgroud)

我正在尝试创建一个通用函数,它可以返回对常用访问的深层嵌套对象的引用,而不管传递的子查询类型如何(例如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;
};
Run Code Online (Sandbox Code Playgroud)

每当我在 src 中调用此代码时,我都会收到以下 Typescript 错误,我知道这与类型兼容性有关。

类型为“ListCustomerTypesQuery |”的参数 未定义' 不可分配给'ViewerType | 类型的参数 undefined'.ts(2345) 属性“viewer”的类型不兼容。...AccessControlAccounts 类型缺少以下属性

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

每当我不使用泛型时,我都可以使用,例如Extract<Query, ListCustomerTypesQuery>提取公共属性,但这并不会使错误在我使用泛型时消失Extract<Query, T>

我可以通过将其强制转换来解决此错误,但我不想对单个函数调用执行此操作。

subscriber(result.value as ViewerType);
Run Code Online (Sandbox Code Playgroud)

我更愿意清理它并保持它简单

subscriber(result.value);
Run Code Online (Sandbox Code Playgroud)

moc*_*cha 2

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

type DeepPartial<T> = T extends object ? {
    [P in keyof T]?: DeepPartial<T[P]>;
} : T;
Run Code Online (Sandbox Code Playgroud)

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

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

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

不幸的是,您仍然需要转换最后一个返回值,因为 TypeScript 不明白您只需要子类型:

return result.viewer?.AccessControlList?.at(0)?.subscriberRel as SubRetType<T>;
Run Code Online (Sandbox Code Playgroud)

操场