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

May 01 2023
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. Evite la conversión de tipos Cuando implementamos la definición de la función que devuelve FruitType<T>, encontramos que requiere una conversión de tipos interna (ugh).
¿Naranja o manzana?

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