TypeScript泛型未正确收窄:函数返回类型匹配问题
问题:根据入参
apiVersion推导函数返回类型失败 我希望函数的返回类型由入参的apiVersion属性值决定,代码实现如下:
type MockSubscriptionInputs = | { apiVersion: 'ApiV4' products: Product[] subscriptionItemType: 'addons' | 'items' } | { apiVersion: 'ApiV5' products: Product[] subscriptionItemType?: never } type Subscription<Version extends ApiVersion> = Version extends 'ApiV4' ? V4Subscription : V5Subscription export function mockSubscriptionWithProducts( config: MockSubscriptionInputs, count = config.products.length ): Subscription<typeof config.apiVersion> { return config.apiVersion === 'ApiV5' ? mockV5SubscriptionWithProducts(config.products, count) : mockV4SubscriptionWithProducts( config.products, config.subscriptionItemType, count ) }
但调用函数时出现类型不匹配错误,两种调用场景的具体报错如下:
场景1:调用ApiV5版本
const subscriptionV5: V5Subscription = mockSubscriptionWithProducts( { products, apiVersion: 'ApiV5' }, 4 )
错误信息:
TS2322: Type { id: number; shopifySubscriptionContractId: number | null; shopifyPlanLineId: string; shopifyBillingCycleEndAt: string; interval: "every_month" | "every_2_months" | "every_3_months" | ... 4 more ... | "every_42_days"; ... 4 more ...; customer: { ...; }; } | { ...; } is not assignable to type { id: number; shopifySubscriptionContractId: number | null; shopifyPlanLineId: string; shopifyBillingCycleEndAt: string; interval: "every_month" | "every_2_months" | "every_3_months" | ... 4 more ... | "every_42_days"; ... 4 more ...; customer: { ...; }; } Type '{ box: { addons: ({ description: string; fullDescription?: string | undefined; image: string; meatGroup: string; packSize: string; price: number; sku: string; markupPrice?: string | undefined; } & { ...; } & { ...; })[]; ... 7 more ...; type: "all_beef" | ... 7 more ... | "basic_beef_chicken_pork"; }; ... 4 more ......' is missing the following properties from type '{ id: number; shopifySubscriptionContractId: number | null; shopifyPlanLineId: string; shopifyBillingCycleEndAt: string; interval: "every_month" | "every_2_months" | "every_3_months" | ... 4 more ... | "every_42_days"; ... 4 more ...; customer: { ...; }; }': shopifySubscriptionContractId, shopifyPlanLineId, shopifyBillingCycleEndAt, lines, customer
场景2:调用ApiV4版本
const subscriptionV4: V4Subscription = mockSubscriptionWithProducts( { products, apiVersion: 'ApiV4', subscriptionItemType: 'items' }, 4 )
错误信息:
TS2719: Type { box: { addons: ({ description: string; fullDescription?: string | undefined; image: string; meatGroup: string; packSize: string; price: number; sku: string; markupPrice?: string | undefined; } & { ...; } & { ...; })[]; ... 7 more ...; type: "all_beef" | ... 7 more ... | "basic_beef_chicken_pork"; }; ... 4 more ...... is not assignable to type { box: { addons: ({ description: string; fullDescription?: string | undefined; image: string; meatGroup: string; packSize: string; price: number; sku: string; markupPrice?: string | undefined; } & { ...; } & { ...; })[]; ... 7 more ...; type: "all_beef" | ... 7 more ... | "basic_beef_chicken_pork"; }; ... 4 more ...... . Two different types with this name exist, but they are unrelated. Property 'box' is missing in type '{ id: number; shopifySubscriptionContractId: number | null; shopifyPlanLineId: string; shopifyBillingCycleEndAt: string; interval: "every_month" | "every_2_months" | "every_3_months" | ... 4 more ... | "every_42_days"; ... 4 more ...; customer: { ...; }; }' but required in type '{ box: { addons: ({ description: string; fullDescription?: string | undefined; image: string; meatGroup: string; packSize: string; price: number; sku: string; markupPrice?: string | undefined; } & { ...; } & { ...; })[]; ... 7 more ...; type: "all_beef" | ... 7 more ... | "basic_beef_chicken_pork"; }; ... 4 more ......'.
解决方案
问题核心是函数未通过泛型约束参数类型,导致TypeScript无法精确推导config的具体分支类型,只能将其视为MockSubscriptionInputs的联合类型,最终返回类型也变成了V4Subscription | V5Subscription,无法匹配你指定的单一类型。
修改方式是将函数改为泛型函数,让apiVersion的类型作为泛型参数,同时约束config的类型与泛型参数关联:
// 先明确ApiVersion类型(如果未定义) type ApiVersion = 'ApiV4' | 'ApiV5'; // 给输入类型添加泛型,根据Version返回对应分支类型 type MockSubscriptionInputs<Version extends ApiVersion> = Version extends 'ApiV4' ? { apiVersion: 'ApiV4' products: Product[] subscriptionItemType: 'addons' | 'items' } : { apiVersion: 'ApiV5' products: Product[] subscriptionItemType?: never }; type Subscription<Version extends ApiVersion> = Version extends 'ApiV4' ? V4Subscription : V5Subscription; // 函数使用泛型参数,绑定输入和返回类型 export function mockSubscriptionWithProducts<Version extends ApiVersion>( config: MockSubscriptionInputs<Version>, count = config.products.length ): Subscription<Version> { // 泛型分支内需要类型断言,TypeScript无法自动关联分支返回值与泛型类型 if (config.apiVersion === 'ApiV5') { return mockV5SubscriptionWithProducts(config.products, count) as Subscription<Version>; } else { return mockV4SubscriptionWithProducts( config.products, config.subscriptionItemType, count ) as Subscription<Version>; } }
关键改动说明
- 给
MockSubscriptionInputs添加泛型参数,让它根据Version返回对应的具体类型,而非联合类型。 - 函数
mockSubscriptionWithProducts绑定泛型Version,将config的类型与MockSubscriptionInputs<Version>关联,TypeScript就能根据传入的参数精确推导返回类型。 - 返回值添加类型断言是安全的,因为我们已经通过
apiVersion做了明确的分支判断,确保返回值与泛型Version匹配。
修改后,调用函数时TypeScript会正确推导返回类型,匹配你指定的V4Subscription或V5Subscription,不会再出现联合类型的错误。
内容的提问来源于stack exchange,提问作者Jonathan Tuzman
相关产品推荐
相关产品推荐

