typescript - ¿Cómo puedo indicar a TS que cada propiedad en un argumento debería corresponder a una propiedad en el tipo de retorno?
Estoy tratando de escribir una función main que tome un objeto opts y devuelva otro objeto. opts tiene dos propiedades opcionales a y b.
Si a está presente, el objeto devuelto debería tener una propiedad aResult. Si b está presente, el objeto devuelto debería tener una propiedad bResult. Si tanto a como b están presentes en opts, entonces tanto aResult como bResult deberían estar presentes en el objeto devuelto.
Puedo lograr esto con sobrecargas:
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
Pero esto se vuelve bastante largo si quiero agregar más propiedades opcionales: c que se mapea en cResult, d que se mapea en dResult, etc.
¿Hay una manera más concisa de hacer esto, por ejemplo, usando una función genérica?
Respuesta #1
Dada una interfaz como:
// Mapping of argument types to return types
interface MyMapping {
a: { aResult: string }
b: { bResult: string }
}
Definitivamente puedes hacer algo de magia.
Primero querrás un tipo para tu argumento. Este será cada clave del mapeo opcionalmente incluida, con un valor de string.
// Type of arguments. Each key in the mapping has a string value.
type UsesArgs<T> = { [K in keyof T]?: string }
Obtener el tipo de retorno es un poco más complicado. Básicamente, quieres mapear sobre cada propiedad del argumento, y luego buscar ese tipo en el mapeo. Ten en cuenta que al iterar necesitamos asegurarnos de que las claves sean del tipo keyof Args & keyof Mapping ya que queremos estar seguros de que cada clave esté presente en ambos tipos.
// 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]
El problema es que produce una unión { a: string } | { b : string }, pero lo que realmente queremos es una intersección { a: string } & { b: string }. Afortunadamente, esta respuesta tiene un tipo auxiliar para eso.
// 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
Ahora hagamos un tipo para poner todo eso junto:
// Type of the return values, as an intersection.
type ReturnValueType<Args, Mapping> =
UnionToIntersection<ReturnValueTypeUnion<Args, Mapping>>
Y finalmente podemos escribir la función:
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 }
Parque infantil
