Typescript 如何向TS指示参数上的每个属性都应映射到返回类型上的属性?

Typescript 如何向TS指示参数上的每个属性都应映射到返回类型上的属性?,typescript,typescript-generics,Typescript,Typescript Generics,我试图编写一个函数main,它接受一个对象opts,并返回另一个对象opts有两个可选属性a和b 如果存在a,则返回的对象应具有属性aResult。如果存在b,则返回的对象应具有属性bResult。如果opts上同时存在a和b,则返回的对象上应同时存在aResult和bResult 我可以通过重载实现这一点: 接口使用a{ a:弦; } 接口使用b{ b:弦; } 接口转换类型{ 结果:字符串; } 接口类型{ 结果:字符串; } 主要功能(选项:usesA):aReturnType; 主功能(

我试图编写一个函数
main
,它接受一个对象
opts
,并返回另一个对象
opts
有两个可选属性
a
b

如果存在
a
,则返回的对象应具有属性
aResult
。如果存在
b
,则返回的对象应具有属性
bResult
。如果
opts
上同时存在
a
b
,则返回的对象上应同时存在
aResult
bResult

我可以通过重载实现这一点:

接口使用a{
a:弦;
}
接口使用b{
b:弦;
}
接口转换类型{
结果:字符串;
}
接口类型{
结果:字符串;
}
主要功能(选项:usesA):aReturnType;
主功能(选项:usesB):bReturnType
主要功能(选项:usesA和usesB):aReturnType和bReturnType;
//省略实现
但是,如果我想添加更多可选属性,这会变得相当冗长:
c
映射到
cResult
d
映射到
dResult


是否有一种更简洁的方法来实现这一点,例如使用通用函数?

给定如下界面:

// Mapping of argument types to return types
interface MyMapping {
  a: { aResult: string }
  b: { bResult: string }
}
你绝对可以变魔术

首先,你需要一个论点的类型。这将是映射中可选包含的每个键,值为
string

// Type of arguments. Each key in the mapping has a string value.
type UsesArgs<T> = { [K in keyof T]?: string }
问题是产生了一个联合
{a:string}|{b:string}
,但我们实际需要的是一个交集
{a:string}&{b:string}
。幸运的是,它有一个助手类型

// Convert a union to an intersection 
intersection-type
type UnionToIntersection<U> = (U extends any
? (k: U) => void
: never) extends (k: infer I) => void
  ? I
  : never
//将并集转换为交点
交叉口类型
类型UnionToIntersection=(U扩展任何
?(k:U)=>void
:从不)扩展(k:推断I)=>void
? 我
:从不
现在让我们制作一个要制作的类型,将所有这些放在一起:

// Type of the return values, as an intersection.
type ReturnValueType<Args, Mapping> = 
  UnionToIntersection<ReturnValueTypeUnion<Args, Mapping>>
//返回值的类型,作为交集。

键入ReturnValueType

您是否尝试过,
main(opts:T):T[K]
?我可能会误解,但我不认为这正是我想要的。如果我这样做:
声明函数main(opts:T):T[K]
const示例:usesA={a:'foo'}
const result=main(示例)
然后结果类型显示为
string
,其中应该是
{aResult:string}
对不起,我误解了示例。因此,您希望将
Result
附加到上一个对象的键。我无法使类型具有将字符串附加到键的能力。我能想到的最好的方法是
main(opts:T):{[K in keyof T]:T[K]}
。扩展类型是任意的,因此可以使用任何东西来代替
any
// Type of the return values, as an intersection.
type ReturnValueType<Args, Mapping> = 
  UnionToIntersection<ReturnValueTypeUnion<Args, Mapping>>
declare function main<T extends UsesArgs<MyMapping>>(
  opts: T,
): ReturnValueType<T, MyMapping>

// Examples
const a = main({ a: 'abc' }) // { aResult: string }
const b = main({ b: 'def' }) // { bResult: string }
const aPlusB = main({ a: 'abc', b: 'def' }) // { aResult: string } & { bResult: string }