我正在尝试编写一个函数main
,该函数opts
获取一个对象,并返回另一个对象。opts
有两个可选属性a
和b
。
如果存在a
,则返回的对象应具有属性aResult
。如果存在b
则返回的对象应具有属性bResult
。如果a
和b
都存在于opts
上,则aResult
和bResult
都应该出现在返回的对象上。
我可以通过重载来实现这一点:
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
映射到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 }
获取返回类型有点棘手。您基本上希望映射参数的每个属性,然后在映射中查找该类型。请注意,在迭代时,我们需要强制密钥属于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 }
操场