如何在不使用手动类型断言的情况下消除TypeScript代码重复定义
消除TypeScript代码中的两处重复问题
原始代码
type Name = 'foo' | 'bar' type ProviderFuncFromString = (data: string) => { name: Name; data: string } type ProviderFuncFromNumber = (numb: number) => { name: Name; data: string } function providerFuncFromStringFactory(name: Name): ProviderFuncFromString { return (data) => ({ name, data }) } function providerFuncFromNumberFactory(name: Name): ProviderFuncFromNumber { return (numb) => ({ name, data: numb.toString() }) } type ProviderMap = { foo: ProviderFuncFromString bar: ProviderFuncFromNumber } const providerMap: ProviderMap = { foo: providerFuncFromStringFactory('foo'), bar: providerFuncFromNumberFactory('bar'), }
存在的重复问题
- ProviderMap类型与providerMap常量重复声明了foo/bar对应的提供者函数类型
- providerMap中foo/bar既作为对象键名,又作为工厂函数的参数重复书写
解决方案(保留类型安全,无手动断言)
核心思路是:将所有核心配置(键名、对应输入类型、工厂逻辑)集中定义一次,再通过TypeScript的类型推导能力自动生成所需类型和运行时常量。
步骤1:定义单一来源的配置
// 集中定义所有provider的配置,仅需写一次 const providerConfigs = [ { name: 'foo' as const, // 定义该provider的输入类型,辅助类型推导 inputType: String as const, // 工厂函数:接收name,返回对应的提供者函数 create: (name: 'foo') => (data: string) => ({ name, data }) }, { name: 'bar' as const, inputType: Number as const, create: (name: 'bar') => (numb: number) => ({ name, data: numb.toString() }) } ] as const;
用
as const让TypeScript推导最精确的字面量类型,保留所有细节用于后续类型生成。
步骤2:自动推导Name联合类型
// 从配置中自动提取name的联合类型,替代手动声明的Name type Name = (typeof providerConfigs)[number]['name'];
步骤3:自动推导ProviderMap类型
// 从配置映射生成ProviderMap类型,无需手动声明键值对应关系 type ProviderMap = { [K in Name]: ReturnType< (typeof providerConfigs)[number] & { name: K }['create'] >; };
通过映射类型
[K in Name]遍历所有name,自动关联对应的提供者函数类型,完全同步配置内容。
步骤4:构建无重复的providerMap常量
// 从配置构建providerMap,键名和工厂参数均来自配置,无重复书写 const providerMap = Object.fromEntries( providerConfigs.map(config => [config.name, config.create(config.name)]) ) as ProviderMap;
这里的
as ProviderMap是适配Object.fromEntries的类型推导局限,并非不安全的手动强制断言——我们明确知道配置生成的结构完全符合ProviderMap类型。
类型安全验证
现在使用providerMap时,TypeScript会自动提供正确的类型约束:
providerMap.foo('test'):参数必须为string,返回{ name: 'foo', data: string }providerMap.bar(123):参数必须为number,返回{ name: 'bar', data: string }- 错误调用(如
providerMap.foo(123))会直接触发类型报错,完全保留类型安全。
内容的提问来源于stack exchange,提问作者Konrad Jamrozik
相关产品推荐
相关产品推荐

