Estrechamiento de múltiples tipos de devolución en TypeScript

// multiple return types
function getFruit({withCitrus: boolean}): Apples | Oranges;
// Why can't we write this?! We know Oranges are returned
var fruit: Oranges = getFruit({ withCitrus: true }); // error
// type guard distinguishes between Apples and Oranges
function isOranges(fruit: Apples | Oranges): fruit is Oranges {
return "VitaminC" in (fruit as Oranges);
}
var fruits: Apples | Oranges = getFruit({withCitrus: true);
if (isOranges(fruits)) {
// type guard narrowed the type to Oranges
var oranges: Oranges = fruits; // ok, no compilation error
}
En este artículo , Maurer Krisztián muestra cómo usar un Generic para conectar los argumentos de una función a su tipo de retorno.
type FruitType<T> =
T extends { withCitrus: false } ? Apples :
T extends { withCitrus: true | undefined } ? Oranges :
Oranges;
function getFruit<T extends { withCitrus: boolean }>(opt?: T): FruitType<T>;
var apples: Apples = getFruit({withCitrus: false}); // ok
var oranges1: Oranges = getFruit({withCitrus: true}); // ok
var oranges2: Oranges = getFruit(); // ok
Evite la conversión de tipos
Cuando implementamos el regreso de la definición de la función, FruitType<T>
encontramos que requiere un tipo interno de conversión (ugh). Si agregamos una función de sobrecarga, podemos continuar restringiendo el tipo de retorno mientras lo separamos de la implementación.
interface IOptions { withCitrus?: boolean };
const DEFAULT_FRUIT_OPTS = { withCitrus: true } as const;
function getFruit<T>(opt?: T extends IOptions ? T : never): FruitType<T>;
function getFruit(opts?: IOptions)
{
const _opts = { ...DEFAULT_FRUIT_OPTS, ...opts };
return _opts.withCitrus ? bagOfOranges : bagOfApples;
}
Permitir otras opciones
Agregar otra opción no interfiere con el estrechamiento del tipo de retorno.
interface IOptions {
withCitrus?: boolean,
hasColor?: Apples['color'] | Oranges['color'],
};
var apples: Apples = getFruit({withCitrus: false, hasColor: "red"}); // ok
var oranges1: Oranges = getFruit({withCitrus: true, hasColor: "orange"}); // ok
var oranges2: Oranges = getFruit({hasColor: "red"}); // ok
Independientemente de lo bien que refinamos el TypeScript, todavía hay algunas situaciones en las que el tipo de devolución no se puede reducir.
var opts5 = { withCitrus: false }; // boolean
var apple5: Apples = getFruit(opts); // error
var opts6: IOptions = { withCitrus: true }; // boolean
var orange6: Oranges = getFruit(opts); // error
var opts7 = { withCitrus: false } as const; // false
var apple7: Apples = getFruit(opts); // ok
Escritura de mayúsculas y minúsculas
Tenemos un par de posibles casos extremos con la definición de tipo FruitType<T>
que puede causar algunos errores: (1) fuente única de verdad para el valor de opción predeterminado y (2) activación de un tipo condicional distributivo .
Consulte este TS Playground para obtener una explicación más detallada.
Forma definitiva
Atarlo todo junto.
type FruitType<T> =
T extends { withCitrus?: infer B } ?
[boolean] extends [B] ? Oranges | Apples :
B extends undefined | typeof DEFAULT_FRUIT_OPTS.withCitrus ?
Oranges : Apples :
Oranges;
function getFruit<T>(opt?: T extends IOptions ? T : never): FruitType<T>;
function getFruit(opts?: IOptions)
{
const _opts = { ...DEFAULT_FRUIT_OPTS, ...opts };
return _opts.withCitrus ? bagOfOranges : bagOfApples;
}
En este artículo explicamos cómo reducir los tipos de devolución múltiple de una función usando su lista de argumentos; mostramos cómo informar al compilador qué tipo de devolución espera la persona que llama. Hacemos esto de una manera segura y evitamos la conversión de tipos superfluos. Recomendamos pasos adicionales para evitar errores al cambiar tipos y valores predeterminados. Finalmente, exploramos varios casos extremos en los que la reducción del tipo de retorno requiere un enfoque adicional mediante el uso de literales de tipo o protectores de tipo.
Por favor comente y déjeme saber lo que piensa.