// Detecta tema escuro var iframe = document.getElementById('tweet-1840346445334864141-950'); if (document.body.className.includes('dark-theme')) { iframe.src = \\\"https://platform.twitter.com/embed/Tweet.html?id=1840346445334864141&theme=dark\\\" }

podemos chegar a... ??


5. O que funciona: a tupla desestruturada

tipo Provedor = \\\"PROVEDOR A\\\" | “PROVEDOR B”;digite ProviderAOpts={...};digite ProviderBOpts={...};função conectar ( ...[provedor, opções]: | [\\\"PROVEDOR A\\\", ProviderAOpts] | [\\\"PROVEDOR B\\\", ProviderBOpts]) { switch (provedor) { caso \\\"PROVEDOR A\\\": //opções é ProviderAOpts ✅ caso \\\"PROVEDOR B\\\": //opções é ProviderBOpts ✅ ... }}
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(\\\"PROVEDOR A\\\", { ... });conectar(\\\"PROVEDOR B\\\", { ... }); ^ o preenchimento automático funciona ✅
connect(\\\"PROVIDER A\\\", { ... });connect(\\\"PROVIDER B\\\", { ... });                      ^ autocomplete works ✅
Então, estamos desestruturando uma tupla (array) com os tipos exatos que queremos.

A única desvantagem, se formos exigentes, adicionando mais pares à tupla... podemos extrair um tipo genérico aqui:


6. O que funciona: solução de tupla generalizada

tipo Provedor = \\\"PROVEDOR A\\\" | “PROVEDOR B”;digite ProviderAOpts={...};digite ProviderBOpts={...};digite ProviderOpts = { \\\"PROVEDOR A\\\": ProviderAOpts; \\\"PROVEDOR B\\\": ProviderBOpts;};//resolve para // [\\\"PROVEDOR A\\\", ProviderAOpts] | [\\\"PROVEDOR B\\\", ProviderBOpts]digite ConnectOptions = { [K na chave de ProviderOpts]: [K, ProviderOpts[K]];}[chave de ProviderOpts]; function connect(...[provedor, opções]: ConnectOptions) { switch (provedor) { caso \\\"PROVEDOR A\\\": //opções é ProviderAOpts ✅ caso \\\"PROVEDOR B\\\": //opções é ProviderBOpts ✅ ... }}
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(\\\"PROVEDOR A\\\", { ... });conectar(\\\"PROVEDOR B\\\", { ... }); ^ o preenchimento automático funciona ✅
connect(\\\"PROVIDER A\\\", { ... });connect(\\\"PROVIDER B\\\", { ... });                      ^ autocomplete works ✅
7.TL;DR. COPIAR COLAR, OBRIGADO

tipo Provedor = \\\"PROVEDOR A\\\" | “PROVEDOR B”;digite ProviderAOpts={...};digite ProviderBOpts={...};digite ProviderOpts = { \\\"PROVEDOR A\\\": ProviderAOpts; \\\"PROVEDOR B\\\": ProviderBOpts;};// tipo aux para extrair a chave e as opções de ProviderOptsdigite KeyOpts = { [K na tonalidade de T]: [K, T[K]];}[chave de T];function connect(...[provedor, opções]: KeyOpts) { switch (provedor) { caso \\\"PROVEDOR A\\\": //opções é ProviderAOpts ✅ caso \\\"PROVEDOR B\\\": //opções é ProviderBOpts ✅ ... }}
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(\\\"PROVEDOR A\\\", { ... });conectar(\\\"PROVEDOR B\\\", { ... }); ^ o preenchimento automático funciona ✅
connect(\\\"PROVIDER A\\\", { ... });connect(\\\"PROVIDER B\\\", { ... });                      ^ autocomplete works ✅

Obrigado a Mateusz e Lenz pela ajuda?.

obrigado pela leitura?.

","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"}}
"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"

Ts avançados: Parâmetros dependentes, uniões inferidas e uma interação saudável no Twitter.

Publicado em 2024-11-07
Navegar:366

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

Cada vez que escrevo como Foo no TypeScript, sinto o peso da derrota.

Há um cenário em que esse sentimento é particularmente intenso: quando uma função recebe um parâmetro que depende de qual "modo" está ativo.

mais claro com algum código de exemplo:

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
  }
}

(Tentei usar nomes mais realistas em vez de foo, goo, dog e cat).

Se você já passou algum tempo com TypeScript, pode suspeitar que costumávamos lidar com isso como ProviderAOpts, como ProviderBOpts.


Mas chega um momento em que você bate o punho na mesa e diz: "Chega!"


1. O que não funciona

A primeira coisa que sempre me vem à mente nesses casos é usar sobrecarga de funções:

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) ❌
  }
}

O que não funciona. A assinatura da função não é inferida corretamente. O parâmetro options é sempre ProviderAOpts | ProvedorBOpts. que resolverá para a união comum.

Ts não vincula ambos os parâmetros corretamente.


2. O que funciona, mas não vincula os parâmetros

A próxima ferramenta que experimento é Type Predicates:

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)) {
        ...
      }
  }
  ...
}

Mas sinceramente, não resolvemos nada. Acabamos de mover o como para debaixo do tapete? Introduzimos ifs extras e ainda não estamos vinculando os parâmetros.


3. O que não funciona e me faz chorar

Genéricos. Tentei usar genéricos para vincular os parâmetros. Não 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) ❌
  }
}

Eu tentei tanto e cheguei tão longe
Mas no final, isso nem importa
Eu tive que cair para perder tudo
Mas no final, isso nem importa
?‍?


4. O que funciona, mas nos obriga a alterar a assinatura da função

Modificar os parâmetros opts adicionando o tipo de provedor resolve:

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 é a solução mais comum, mas nem sempre é possível alterar a assinatura da função. Ou talvez você simplesmente não queira . Questão de princípios?.


Twitter para o resgate

Obrigado a Mateusz Burzyński (@AndaristRake) e Lenz Weber (@phry)

// Detecta tema escuro var iframe = document.getElementById('tweet-1840828253684056557-683'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1840828253684056557&theme=dark" }

// Detecta tema escuro var iframe = document.getElementById('tweet-1840346445334864141-950'); if (document.body.className.includes('dark-theme')) { iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=1840346445334864141&theme=dark" }

podemos chegar a... ??


5. O que funciona: a tupla desestruturada

tipo Provedor = "PROVEDOR A" | “PROVEDOR B”; digite ProviderAOpts={...}; digite ProviderBOpts={...}; função conectar ( ...[provedor, opções]: | ["PROVEDOR A", ProviderAOpts] | ["PROVEDOR B", ProviderBOpts] ) { switch (provedor) { caso "PROVEDOR A": //opções é ProviderAOpts ✅ caso "PROVEDOR B": //opções é ProviderBOpts ✅ ... } }
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("PROVEDOR A", { ... }); conectar("PROVEDOR B", { ... }); ^ o preenchimento automático funciona ✅
connect("PROVIDER A", { ... });
connect("PROVIDER B", { ... });
                      ^ autocomplete works ✅
Então, estamos desestruturando uma tupla (array) com os tipos exatos que queremos.

A única desvantagem, se formos exigentes, adicionando mais pares à tupla... podemos extrair um tipo genérico aqui:


6. O que funciona: solução de tupla generalizada

tipo Provedor = "PROVEDOR A" | “PROVEDOR B”; digite ProviderAOpts={...}; digite ProviderBOpts={...}; digite ProviderOpts = { "PROVEDOR A": ProviderAOpts; "PROVEDOR B": ProviderBOpts; }; //resolve para // ["PROVEDOR A", ProviderAOpts] | ["PROVEDOR B", ProviderBOpts] digite ConnectOptions = { [K na chave de ProviderOpts]: [K, ProviderOpts[K]]; }[chave de ProviderOpts]; function connect(...[provedor, opções]: ConnectOptions) { switch (provedor) { caso "PROVEDOR A": //opções é ProviderAOpts ✅ caso "PROVEDOR B": //opções é ProviderBOpts ✅ ... } }
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("PROVEDOR A", { ... }); conectar("PROVEDOR B", { ... }); ^ o preenchimento automático funciona ✅
connect("PROVIDER A", { ... });
connect("PROVIDER B", { ... });
                      ^ autocomplete works ✅
7.TL;DR. COPIAR COLAR, OBRIGADO

tipo Provedor = "PROVEDOR A" | “PROVEDOR B”; digite ProviderAOpts={...}; digite ProviderBOpts={...}; digite ProviderOpts = { "PROVEDOR A": ProviderAOpts; "PROVEDOR B": ProviderBOpts; }; // tipo aux para extrair a chave e as opções de ProviderOpts digite KeyOpts = { [K na tonalidade de T]: [K, T[K]]; }[chave de T]; function connect(...[provedor, opções]: KeyOpts) { switch (provedor) { caso "PROVEDOR A": //opções é ProviderAOpts ✅ caso "PROVEDOR B": //opções é ProviderBOpts ✅ ... } }
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("PROVEDOR A", { ... }); conectar("PROVEDOR B", { ... }); ^ o preenchimento automático funciona ✅
connect("PROVIDER A", { ... });
connect("PROVIDER B", { ... });
                      ^ autocomplete works ✅

Obrigado a Mateusz e Lenz pela ajuda?.

obrigado pela leitura?.

Declaração de lançamento Este artigo é reproduzido em: https://dev.to/manuartero/advanced--dependent-parameters-inferred-unions-and-a-healthy--interaction-on-twitter-13bc?1 Se houver alguma infração, entre em contato com [email protected] para deletá-lo.
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3