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



我正在尝试编写一个函数main,该函数opts获取一个对象,并返回另一个对象。opts有两个可选属性ab

如果存在a,则返回的对象应具有属性aResult。如果存在b则返回的对象应具有属性bResult。如果ab都存在于opts上,则aResultbResult都应该出现在返回的对象上。

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

interface usesA {
a: string;
}
interface usesB {
b: string;
}
interface aReturnType {
aResult: string;
}
interface bReturnType {
bResult: string;
}
function main(opts: usesA): aReturnType;
function main(opts: usesB): bReturnType
function main(opts: usesA & usesB): aReturnType & bReturnType; 
// implementation omitted

但是,如果我想添加更多可选属性,这会变得相当冗长:c映射到cResultd映射到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 }

获取返回类型有点棘手。您基本上希望映射参数的每个属性,然后在映射中查找该类型。请注意,在迭代时,我们需要强制密钥属于keyof Args & keyof Mapping类型,因为我们希望确保每个密钥都存在于两种类型中。

// Type of each possible return value, as a union of all types.
type ReturnValueTypeUnion<Args, Mapping> = {
// for each key Args and Mapping have in common, get the type from mapping
[K in keyof Args & keyof Mapping]: Mapping[K]
// Get values from mapping as a union
}[keyof Args & keyof Mapping]

问题是产生了一个并集{ 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

现在让我们创建一个类型来将所有这些放在一起:

// 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 }

操场

最新更新