如何简化TypeScript类型查找映射(Type Lookup Map)
简化TypeScript泛型函数实现的问题
需求与问题
我定义了一个记录类型Foo,用于关联名称和对应的值:
type Foo<Name extends keyof FooMap, Value> = { name: Name; value: Value; };
同时有一个映射类型FooMap,键对应Foo的name属性,值对应Foo的value类型:
type FooMap = { a: { complex: string; type?: boolean; }; b: string; c: number; };
我尝试用以下泛型函数处理Foo实例,但value始终是所有可能类型的联合类型,无法在条件分支中正确窄化:
<Name extends keyof FooMap>({ name, value }: Foo<Name, FooMap[Name]>) => { if (name === 'a') { if (value.type ?? false) { strings.push(value.complex); // 类型错误:value可能是string/number,不存在type/complex属性 } } if (name === 'b') { strings.push(value); // 类型错误:value可能是object/number,无法直接push到string数组 } if (name === 'c') { while (strings.length < value) { // 类型错误:value可能是object/string,无法比较长度 strings.push(strings[strings.length % Math.min(strings.length, value)]); } } };
临时解决方案(存在冗余)
我通过修改FooMap直接存储Foo实例解决了类型窄化问题,但需要重复编写Foo的定义,代码冗余:
type FooMap = { a: Foo<'a', { complex: string; type?: boolean; }>; b: Foo<'b', string>; c: Foo<'c', number>; }; <Name extends keyof FooMap>({ name, value }: FooMap[Name]) => { if (name === 'a') { if (value.type ?? false) { strings.push(value.complex); } } if (name === 'b') { strings.push(value); } if (name === 'c') { while (strings.length < value) { strings.push(strings[strings.length % Math.min(strings.length, value)]); } } };
请问是否有办法避免这种代码重复?我是不是遗漏了TypeScript的关键概念?
解决方案:利用映射类型自动关联类型
你可以通过映射类型自动生成关联name和value的联合类型,无需重复定义:
步骤1:分离值类型映射
先定义只存储值类型的映射,避免和Foo耦合:
type ValueMap = { a: { complex: string; type?: boolean; }; b: string; c: number; };
步骤2:调整Foo类型定义
让Foo自动从ValueMap中获取对应值类型:
type Foo<Name extends keyof ValueMap> = { name: Name; value: ValueMap[Name]; };
步骤3:生成联合类型或使用泛型约束
方案A:使用联合类型作为参数类型
生成所有Foo类型的联合,TypeScript会自动识别条件分支中的类型:
type FooUnion = { [K in keyof ValueMap]: Foo<K> }[keyof ValueMap]; const processFoo = (foo: FooUnion) => { if (foo.name === 'a') { if (foo.value.type ?? false) { strings.push(foo.value.complex); } } else if (foo.name === 'b') { strings.push(foo.value); } else if (foo.name === 'c') { while (strings.length < foo.value) { strings.push(strings[strings.length % Math.min(strings.length, foo.value)]); } } };
方案B:使用泛型函数
通过泛型参数约束,让TypeScript关联name和value的类型:
const processFoo = <K extends keyof ValueMap>(foo: Foo<K>) => { if (foo.name === 'a') { if (foo.value.type ?? false) { strings.push(foo.value.complex); } } else if (foo.name === 'b') { strings.push(foo.value); } else if (foo.name === 'c') { while (strings.length < foo.value) { strings.push(strings[strings.length % Math.min(strings.length, foo.value)]); } } };
原理说明
当参数类型是Foo<K>(K为ValueMap的键)时,TypeScript会将name的字面量类型和value的类型绑定,通过if (foo.name === 'a')这样的类型守卫,就能自动将foo窄化为Foo<'a>,从而正确识别value的具体类型。
内容的提问来源于stack exchange,提问作者Dom Hastings
相关产品推荐
相关产品推荐

