podemos llegar a... ??


5. Qué funciona: la tupla desestructurada

type Provider = \\\"PROVIDER A\\\" | \\\"PROVIDER B\\\";type ProviderAOpts = { ... };type ProviderBOpts = { ... };function connect(  ...[provider, options]:    | [\\\"PROVIDER A\\\", ProviderAOpts]    | [\\\"PROVIDER B\\\", ProviderBOpts]) {  switch (provider) {    case \\\"PROVIDER A\\\":      // options is ProviderAOpts ✅    case \\\"PROVIDER B\\\":      // options is ProviderBOpts ✅    ...  }}
connect(\\\"PROVIDER A\\\", { ... });connect(\\\"PROVIDER B\\\", { ... });                      ^ autocomplete works ✅

Entonces la cuestión es que estamos desestructurando una tupla (matriz) con los tipos exactos que queremos.

El único inconveniente, si somos exigentes, agregar más pares a la tupla... podemos extraer un tipo genérico aquí:


6. Qué funciona: solución de tupla generalizada

type Provider = \\\"PROVIDER A\\\" | \\\"PROVIDER B\\\";type ProviderAOpts = { ... };type ProviderBOpts = { ... };type ProviderOpts = {  \\\"PROVIDER A\\\": ProviderAOpts;  \\\"PROVIDER B\\\": ProviderBOpts;};// solves to // [\\\"PROVIDER A\\\", ProviderAOpts] | [\\\"PROVIDER B\\\", ProviderBOpts]type ConnectOptions = {  [K in keyof ProviderOpts]: [K, ProviderOpts[K]];}[keyof ProviderOpts]; function connect(...[provider, options]: ConnectOptions) {  switch (provider) {    case \\\"PROVIDER A\\\":      // options is ProviderAOpts ✅    case \\\"PROVIDER B\\\":      // options is ProviderBOpts ✅    ...  }}
connect(\\\"PROVIDER A\\\", { ... });connect(\\\"PROVIDER B\\\", { ... });                      ^ autocomplete works ✅

7. TL;DR. COPIAR Y PEGAR, GRACIAS

type Provider = \\\"PROVIDER A\\\" | \\\"PROVIDER B\\\";type ProviderAOpts = { ... };type ProviderBOpts = { ... };type ProviderOpts = {  \\\"PROVIDER A\\\": ProviderAOpts;  \\\"PROVIDER B\\\": ProviderBOpts;};// aux type to extract the key and the options from ProviderOptstype KeyOpts = {  [K in keyof T]: [K, T[K]];}[keyof T];function connect(...[provider, options]: KeyOpts) {  switch (provider) {    case \\\"PROVIDER A\\\":      // options is ProviderAOpts ✅    case \\\"PROVIDER B\\\":      // options is ProviderBOpts ✅    ...  }}
connect(\\\"PROVIDER A\\\", { ... });connect(\\\"PROVIDER B\\\", { ... });                      ^ autocomplete works ✅

¿Gracias a Mateusz y Lenz por la ayuda?.

gracias por leer?.

","image":"http://www.luping.net/uploads/20241104/17307234156728be5752a02.jpg","datePublished":"2024-11-07T10:48:39+08:00","dateModified":"2024-11-07T10:48:39+08:00","author":{"@type":"Person","name":"luping.net","url":"https://www.luping.net/articlelist/0_1.html"}}
"Si un trabajador quiere hacer bien su trabajo, primero debe afilar sus herramientas." - Confucio, "Las Analectas de Confucio. Lu Linggong"
Página delantera > Programación > Ts avanzados: parámetros dependientes, uniones inferidas y una interacción saludable en Twitter.

Ts avanzados: parámetros dependientes, uniones inferidas y una interacción saludable en Twitter.

Publicado el 2024-11-07
Navegar:430

Advanced Ts: Dependent parameters, inferred unions and a healthy interaction on Twitter.

Cada vez que escribo como Foo en TypeScript, siento el peso de la derrota.

Hay un escenario en el que este sentimiento es particularmente intenso: cuando una función toma un parámetro que depende de qué "modo" está activo.

más claro con algún código de ejemplo:

type Provider = "PROVIDER A" | "PROVIDER B";
type ProviderAOpts = { ... };
type ProviderBOpts = { ... };

function connect(provider: Provider, options: ProviderAOpts | ProviderBOpts)  {
  switch (provider) {
    case "PROVIDER A":
      // options is ProviderAOpts
    case "PROVIDER B":
      // options is ProviderBOpts
  }
}

(Traté de usar nombres más realistas en lugar de foo, goo, dog y cat).

Si ha pasado algún tiempo con TypeScript, puede sospechar que solíamos manejar esto como ProviderAOpts, como ProviderBOpts.


Pero hay un momento en que golpeas la mesa con el puño y dices: "¡No más!"


1. Lo que no funciona

Lo primero que siempre me viene a la mente en estos casos es utilizar sobrecarga de funciones:

function connect(provider: "PROVIDER A", options: ProviderAOpts): void;
function connect(provider: "PROVIDER B", options: ProviderBOpts): void;

function connect(provider: Provider, options: ProviderAOpts | ProviderBOpts) {
  switch (provider) {
    case "PROVIDER A":
    // (options as ProviderAOpts) ❌
    case "PROVIDER B":
    // (options as ProviderBOpts) ❌
  }
}

Lo cual no funciona. La firma de la función no se infiere correctamente. El parámetro de opciones siempre es ProviderAOpts | ProveedorBOpts. que resolverá a la unión común.

Ts no vincula ambos parámetros correctamente.


2. Qué funciona pero no vincula los parámetros

La siguiente herramienta que pruebo es Escribir predicados:

type ConnectOptions = ProviderAOpts | ProviderBOpts;

function isAOptions(options: ConnectOptions): options is ProviderAOpts {
  return (options as ProviderAOpts).$$$ !== undefined;
}

function isBOptions(options: ConnectOptions): options is ProviderBOpts {
  return (options as ProviderBOpts).$$$ !== undefined;
}

function connect(provider: Provider, options: ConnectOptions) {
  switch (provider) {
    case "PROVIDER A":
      if (isAOptions(options)) {
        ...
      }
    case "PROVIDER B":
      if (isBOptions(options)) {
        ...
      }
  }
  ...
}

Pero, sinceramente, no solucionamos nada. ¿Acabamos de mover el as debajo de la alfombra? Se introdujeron condiciones adicionales y todavía no vinculamos los parámetros.


3. Lo que no funciona y me hace llorar

Genéricos. Intenté usar genéricos para vincular los parámetros. No funciona:

function connect(
  provider: T,
  options: T extends "PROVIDER A" ? ProviderAOpts : ProviderBOpts
) {
  switch (provider) {
    case "PROVIDER A":
    // (options as ProviderAOpts) ❌
    case "PROVIDER B":
    // (options as ProviderBOpts) ❌
  }
}

Me esforcé mucho y llegué tan lejos
Pero al final ni siquiera importa
Tuve que caer para perderlo todo
Pero al final ni siquiera importa
?‍?


4. Qué funciona pero nos obliga a cambiar la firma de la función

Modificar los parámetros de opciones y agregar el tipo de proveedor funciona:

type Provider = "PROVIDER A" | "PROVIDER B";

type ProviderOptsBase = {
  provider: Provider;
}

type ProviderAOpts = ProviderOptsBase & {
  provider: "PROVIDER A";
  ...;
};

type ProviderBOpts = ProviderOptsBase & {
  provider: "PROVIDER B";
  ...;
};

function connect(options: ConnectOptions) {
  switch (options.provider) {
    case "PROVIDER A":
      // options is ProviderAOpts ✅
    case "PROVIDER B":
      // options is ProviderBOpts ✅
  }
}

Esta es la solución más común, pero no siempre es posible cambiar la firma de la función. O tal vez simplemente no quieres. ¿Cuestión de principios?.


Twitter al rescate

Gracias a Mateusz Burzyński (@AndaristRake) y Lenz Weber (@phry)

podemos llegar a... ??


5. Qué funciona: la tupla desestructurada

type Provider = "PROVIDER A" | "PROVIDER B";
type ProviderAOpts = { ... };
type ProviderBOpts = { ... };

function connect(
  ...[provider, options]:
    | ["PROVIDER A", ProviderAOpts]
    | ["PROVIDER B", ProviderBOpts]
) {
  switch (provider) {
    case "PROVIDER A":
      // options is ProviderAOpts ✅
    case "PROVIDER B":
      // options is ProviderBOpts ✅
    ...
  }
}
connect("PROVIDER A", { ... });
connect("PROVIDER B", { ... });
                      ^ autocomplete works ✅

Entonces la cuestión es que estamos desestructurando una tupla (matriz) con los tipos exactos que queremos.

El único inconveniente, si somos exigentes, agregar más pares a la tupla... podemos extraer un tipo genérico aquí:


6. Qué funciona: solución de tupla generalizada

type Provider = "PROVIDER A" | "PROVIDER B";
type ProviderAOpts = { ... };
type ProviderBOpts = { ... };

type ProviderOpts = {
  "PROVIDER A": ProviderAOpts;
  "PROVIDER B": ProviderBOpts;
};

// solves to 
// ["PROVIDER A", ProviderAOpts] | ["PROVIDER B", ProviderBOpts]
type ConnectOptions = {
  [K in keyof ProviderOpts]: [K, ProviderOpts[K]];
}[keyof ProviderOpts]; 

function connect(...[provider, options]: ConnectOptions) {
  switch (provider) {
    case "PROVIDER A":
      // options is ProviderAOpts ✅
    case "PROVIDER B":
      // options is ProviderBOpts ✅
    ...
  }
}
connect("PROVIDER A", { ... });
connect("PROVIDER B", { ... });
                      ^ autocomplete works ✅

7. TL;DR. COPIAR Y PEGAR, GRACIAS

type Provider = "PROVIDER A" | "PROVIDER B";
type ProviderAOpts = { ... };
type ProviderBOpts = { ... };

type ProviderOpts = {
  "PROVIDER A": ProviderAOpts;
  "PROVIDER B": ProviderBOpts;
};

// aux type to extract the key and the options from ProviderOpts
type KeyOpts = {
  [K in keyof T]: [K, T[K]];
}[keyof T];


function connect(...[provider, options]: KeyOpts) {
  switch (provider) {
    case "PROVIDER A":
      // options is ProviderAOpts ✅
    case "PROVIDER B":
      // options is ProviderBOpts ✅
    ...
  }
}
connect("PROVIDER A", { ... });
connect("PROVIDER B", { ... });
                      ^ autocomplete works ✅

¿Gracias a Mateusz y Lenz por la ayuda?.

gracias por leer?.

Declaración de liberación Este artículo se reproduce en: https://dev.to/manuartero/advanced-ts-dependent-parameters-inferred-unions-and-a-healthy-interaction-on-twitter-13bc?1 Si hay alguna infracción, por favor contacto Study_golang@163 .comeliminar
Último tutorial Más>

Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.

Copyright© 2022 湘ICP备2022001581号-3