如何定义返回类型由参数值决定的TypeScript泛型函数?
类型安全的符号查找泛型函数实现问题
先给出简化后的基础类型定义:
type Symbol = TypeDef | VarDecl interface TypeDef { kind: 'type', name: string, typeData: string; } interface VarDecl { kind: 'var', name: string, varData: number } type SymbolKind = Symbol['kind']; interface SymbolMap { type: TypeDef[], var: VarDecl[] }
现有实现的问题
方案1:传入SymbolKind参数无法缩小返回类型
function LookupSymbol(kind: SymbolKind, map: SymbolMap, name: string) { let symbols = map[kind]; for(var symbol of symbols) { if(symbol.name == name) return symbol; } } let x = LookupSymbol('var', m, 'x'); // x: TypeDef | VarDecl | undefined
调用后返回类型仍是联合类型,无法根据传入的kind自动推导对应具体类型。
方案2:使用泛型参数无法匹配SymbolMap对应属性
interface SymbolBase { kind: SymbolKind, name: string } function LookupSymbol2<T extends SymbolBase>(map: SymbolMap, name: string): T | undefined { let symbols = map[???]; // 无法通过泛型T找到对应的map属性 for(var symbol of symbols) { if(symbol.name == name) return symbol; } return undefined; }
无法从泛型参数T中提取对应的kind值,进而无法访问map的对应属性。
需求是实现支持以下两种调用形式之一的类型安全函数:
LookupSymbol<VarDecl>(map: SymbolMap, name: string): VarDecl | undefinedLookupSymbol(map: SymbolMap, kind: 'var', name: string): VarDecl | undefined
解决方案
方案一:基于SymbolKind参数的类型安全实现
通过泛型约束关联SymbolKind与返回类型,利用条件类型和索引类型实现精准推导:
// 定义kind到对应类型的映射 type SymbolKindToType<K extends SymbolKind> = K extends 'type' ? TypeDef : VarDecl; function LookupSymbol<K extends SymbolKind>(map: SymbolMap, kind: K, name: string): SymbolKindToType<K> | undefined { const symbols = map[kind]; for (const symbol of symbols) { if (symbol.name === name) { return symbol as SymbolKindToType<K>; } } return undefined; } // 调用示例 const m: SymbolMap = { type: [{ kind: 'type', name: 'MyType', typeData: 'data' }], var: [{ kind: 'var', name: 'x', varData: 123 }] }; const x = LookupSymbol(m, 'var', 'x'); // x: VarDecl | undefined const myType = LookupSymbol(m, 'type', 'MyType'); // myType: TypeDef | undefined
这里的类型断言是安全的,因为map[kind]的类型必然与SymbolKindToType<K>严格对应。
方案二:基于泛型类型参数的实现
通过从泛型T中提取kind属性,结合索引类型访问SymbolMap的对应属性:
// 从符号类型中提取对应的kind值 type SymbolTypeToKind<T extends Symbol> = T['kind']; function LookupSymbol<T extends Symbol>(map: SymbolMap, name: string): T | undefined { const kind = SymbolTypeToKind<T> as keyof SymbolMap; const symbols = map[kind] as T[]; for (const symbol of symbols) { if (symbol.name === name) { return symbol; } } return undefined; } // 调用示例 const x = LookupSymbol<VarDecl>(m, 'x'); // x: VarDecl | undefined const myType = LookupSymbol<TypeDef>(m, 'MyType'); // myType: TypeDef | undefined
由于T是Symbol的子类型,其kind必然是SymbolMap的合法键,因此类型断言不会存在安全风险。
内容的提问来源于stack exchange,提问作者Антон Игоревич Злыгостев
相关产品推荐
相关产品推荐

