TypeScript编译时泛型类型被替换为never的问题修复咨询
问题原因
初始定义的Firebinder.Firestore.CollectionTypes是空接口,keyof Firebinder.Firestore.CollectionTypes在编译阶段会被TypeScript推断为never。TS的类型优化机制会直接将条件T extends keyof Firebinder.Firestore.CollectionTypes替换为T extends never,导致后续用户扩展该接口时,编译后的声明文件无法识别新增的键,类型判断完全失效。
解决方案
方案1:给空接口添加占位符属性
在初始的CollectionTypes接口中添加一个可选的占位符属性,避免keyof返回never,从而阻止TS的过度优化:
declare global { namespace Firebinder { export namespace Firestore { // 添加占位符属性,防止keyof返回never export interface CollectionTypes { __placeholder__?: never; } } export namespace Auth { export interface CustomClaims {} } } }
修改后,keyof Firebinder.Firestore.CollectionTypes初始值为"__placeholder__",编译时TS不会将条件替换为T extends never。当用户扩展CollectionTypes时,keyof会自动包含新增的键,类型判断逻辑就能正常生效。
方案2:使用延迟解析的类型别名包装keyof判断
将keyof的判断逻辑封装到一个单独的类型别名中,利用TypeScript对类型别名的延迟解析特性,避免提前优化:
declare global { namespace Firebinder { export namespace Firestore { export interface CollectionTypes {} // 包装keyof判断为类型别名 export type CollectionKeys = keyof CollectionTypes; } export namespace Auth { export interface CustomClaims {} } } } // 源码中使用类型别名替代直接的keyof判断 const useCollection = <V, T extends string>(collectionId: T): CollectionReference<T extends Firebinder.Firestore.CollectionKeys ? Firebinder.Firestore.CollectionTypes[T] : V> => {...}
这种方式下,TS会保留对CollectionKeys的引用,用户扩展CollectionTypes后,CollectionKeys会自动更新,类型条件判断也能正常工作。
方案3:调整泛型参数的约束顺序
通过调整泛型参数的约束,让TS优先解析用户可能扩展的类型,避免提前收缩:
// 源码中先定义与CollectionTypes关联的泛型参数 const useCollection = <T extends keyof Firebinder.Firestore.CollectionTypes | string, V = never>( collectionId: T ): CollectionReference<T extends keyof Firebinder.Firestore.CollectionTypes ? Firebinder.Firestore.CollectionTypes[T] : V> => {...}
这里将T的约束设为keyof ... | string,初始时keyof ...是never,所以T的约束等价于string,但TS不会将条件判断替换为T extends never,因为约束中存在string分支,用户扩展后keyof ...会新增有效值,类型判断即可生效。
内容的提问来源于stack exchange,提问作者Bora Ciner

