Genéricos avançados em TypeScript. Relatório Yandex

Os tipos genéricos ou parametrizados permitem que você escreva funções e interfaces mais flexíveis. Para ir além da parametrização com um único tipo, você só precisa entender alguns princípios gerais dos genéricos, e o TypeScript será aberto antes de você gostar de uma caixa de segredos. Alexandr Nikolaichev explicou como não ter medo de aninhar genéricos uns nos outros e usar a inferência automática de tipo em seus projetos.



- Olá a todos, meu nome é Alexander Nikolaichev. Trabalho na Yandex.Cloud como desenvolvedor front-end, trabalhando na infraestrutura interna da Yandex. Hoje vou falar sobre uma coisa muito útil, sem a qual é difícil imaginar uma aplicação moderna, especialmente em grande escala. Este é o TypeScript, digitação, um tópico mais restrito - genéricos e por que eles são necessários.



Primeiro, vamos responder à pergunta de por que TypeScript e o que a infraestrutura tem a ver com isso. Nossa principal propriedade de infraestrutura é a confiabilidade. Como isso pode ser garantido? Em primeiro lugar, você pode testar.





Temos testes de unidade e integração. O teste é uma boa prática padrão.



Você também precisa usar a revisão de código. Além disso - coleção de erros. Se, no entanto, ocorrer um erro, então um mecanismo especial o envia e podemos consertar algo rapidamente.



Como seria bom não cometer nenhum erro. Para isso, existe a digitação, o que não nos permitirá de forma alguma obter um erro em tempo de execução. Yandex usa o TypeScript padrão da indústria. E como os aplicativos são grandes e complexos, obteremos esta fórmula: se tivermos um front-end, digitação e até abstrações complexas, definitivamente chegaremos aos genéricos do TypeScript. Você não pode viver sem eles.



Sintaxe



Para conduzir um programa educacional básico, vamos primeiro examinar os fundamentos da sintaxe.



Um genérico no TypeScript é um tipo que depende de outro tipo.



Temos um tipo simples, Page. Parametrizamos com um determinado parâmetro <T>, é escrito entre colchetes angulares. E vemos que existem algumas strings, números, mas <T> é variável.



Além de interfaces e tipos, podemos aplicar a mesma sintaxe às funções. Ou seja, o mesmo parâmetro <T> é encaminhado para o argumento da função, e na resposta vamos reutilizar a mesma interface, também vamos passá-la lá.



Nossa chamada genérica também é escrita por meio de colchetes angulares com o tipo desejado, assim como quando foi inicializada.



Sintaxe semelhante existe para classes. Jogamos o parâmetro em campos privados e temos uma espécie de getter. Mas não escrevemos o tipo aí. Por quê? Porque o TypeScript pode inferir o tipo. Esta é uma característica muito útil dele, e nós a aplicaremos.



Vamos ver o que acontece ao usar esta classe. Criamos uma instância e, em vez de nosso parâmetro <T>, passamos um dos elementos de enumeração. Criamos uma enumeração - Russo, Inglês. O TypeScript entende que passamos um elemento da enumeração e infere o tipo lang.



Mas vamos ver como funciona a inferência de tipo. Se, em vez de elementos de enumeração, passarmos uma constante dessa enumeração, o TypeScript entenderá que essa não é a enumeração inteira, nem todos os seus elementos. E já haverá um valor específico do tipo, ou seja, lang en, inglês.



Se passarmos outra coisa, por exemplo, uma string, então parece que ela tem o mesmo significado que a enumeração. Mas isso já é uma string, outro tipo no TypeScript, e vamos pegá-lo. E se passarmos uma string como uma constante, então em vez de uma string haverá uma constante, uma string literal, nem todas são strings. Em nosso caso, haverá uma string específica en.



Agora vamos ver como podemos expandir isso.



Tínhamos um parâmetro. Nada nos impede de usar vários parâmetros. Todos eles são escritos separados por vírgulas. Nos mesmos colchetes angulares, e os aplicamos em ordem - do primeiro ao terceiro. Substituímos os valores desejados quando chamados.



Digamos uma concatenação de literais numéricos, algum tipo padrão, uma concatenação de literais de string. Eles são simplesmente escritos em ordem.



Vamos ver como isso acontece nas funções. Criamos uma função aleatória. Ele fornece aleatoriamente o primeiro argumento ou o segundo.



O primeiro argumento é do tipo A, o segundo é do tipo B. Conseqüentemente, sua união é retornada: isto ou isto. Em primeiro lugar, podemos digitar explicitamente a função. Indicamos que A é uma string, B é um número. O TypeScript examinará o que especificamos explicitamente e inferirá o tipo.



Mas também podemos usar a inferência de tipo. O principal é saber que não é apenas o tipo que se infere, mas o menor tipo possível para o argumento.



Suponha que passemos um argumento, um literal de string, e ele deve corresponder ao tipo A, e o segundo argumento, um, ao tipo B. O mínimo possível para um literal de string e um é literal A e o mesmo. O TypeScript irá enviar isso para nós. Acontece um estreitamento de tipos.



Antes de passar para os exemplos a seguir, veremos como os tipos geralmente se relacionam entre si, como usar esses relacionamentos, como colocar ordem no caos de todos os tipos.



Relação de tipos



Os tipos podem ser convencionalmente considerados como um tipo de conjunto. Vejamos o diagrama, que mostra uma parte de todo o conjunto de tipos.



Vemos que os tipos nele estão conectados por algum tipo de relacionamento. Mas quais? Esses são relacionamentos de ordenação parcial - o que significa que um tipo é sempre especificado com seu supertipo, ou seja, um tipo "acima" dele, que cobre todos os valores possíveis.



Se você for na direção oposta, cada tipo pode ter um subtipo, "menos" dele.



Quais são os supertipos de uma string? Qualquer junção que inclua uma string. Uma string com um número, uma string com um array de números, qualquer coisa. Todos os subtipos são literais de string: a, b, c, ou ac ou ab.



Mas é importante entender que a ordem não é linear. Ou seja, nem todos os tipos podem ser comparados. Isso é lógico e é o que leva a erros de incompatibilidade de tipo. Ou seja, uma string não pode ser simplesmente comparada a um número.



E nesta ordem há um tipo, por assim dizer, o mais alto - desconhecido. E o mais baixo, análogo do conjunto vazio, nunca é. Nunca é um subtipo de qualquer tipo. E desconhecido é um supertipo de qualquer tipo.



E, claro, há uma exceção - qualquer. Este é um tipo especial, ele ignora essa ordem completamente e é usado se estivermos migrando do JavaScript para não nos importarmos com os tipos. Não é recomendado usar nenhum desde o início. Vale a pena fazer isso se realmente não nos importarmos com a posição do tipo nessa ordem.



Vamos ver o que o conhecimento desta ordem nos dará.



Podemos restringir os parâmetros aos seus supertipos. A palavra-chave é extends. Vamos definir um tipo, genérico, que terá apenas um parâmetro. Mas diremos que só pode ser um subtipo da string ou a própria string. Não poderemos transferir números, isso causará um erro de tipo. Se digitarmos explicitamente a função, nos parâmetros podemos especificar apenas os subtipos da string ou da string - maçã e laranja. Ambas as strings são concatenação de literais de string. A verificação foi aprovada.





Também podemos inferir tipos automaticamente com base nos argumentos. Se passarmos um literal de string, isso também será uma string. A verificação funcionou.



Vamos ver como expandir essas restrições.



Nós nos limitamos a apenas uma linha. Mas uma string é um tipo muito simples. Eu gostaria de trabalhar com chaves de objeto. Para trabalhar com eles, primeiro entendemos como as próprias chaves de objeto e seus tipos são organizados.



Temos um certo objeto. Possui alguns tipos de campos: strings, números, valores booleanos e chaves por nome. Para obter as chaves, usamos a palavra-chave keyof. Obtemos a união de todos os nomes de chaves.



Se quisermos obter os valores, podemos fazê-lo por meio da sintaxe dos colchetes. Isso é semelhante à sintaxe JS. Ele retorna apenas tipos. Se passarmos todo o subconjunto das chaves, obteremos a união de todos os valores deste objeto em geral.



Se quisermos obter uma parte, podemos especificar isso - não todas as chaves, mas alguns subconjuntos. Esperamos receber apenas os campos que correspondem às chaves especificadas. Se reduzirmos tudo a um único caso, este é um campo e uma chave fornece um valor. Desta forma, você pode obter o campo correspondente.



Vamos ver como usar chaves de objeto.



É importante entender que pode haver qualquer tipo válido após a palavra-chave extends. Incluindo formados a partir de outros genéricos ou usando palavras-chave.



Vamos ver como isso funciona com o keyof. Definimos o tipo CustomPick. Na verdade, esta é quase uma cópia completa do tipo de biblioteca Pick do TypeScript. O que ele está fazendo?



Possui dois parâmetros. O segundo não é apenas um parâmetro. Devem ser as chaves do primeiro. Vemos que ele está expandindo keyof de <T>. Portanto, deve ser algum subconjunto das chaves.



A seguir, para cada chave K deste subconjunto, percorremos o objeto, colocamos o mesmo valor e especialmente removemos a opcionalidade, menos o ponto de interrogação com a sintaxe. Ou seja, todos os campos serão obrigatórios.



Nós olhamos para o aplicativo. Temos um objeto, nele os nomes dos campos. Podemos pegar apenas um subconjunto deles - a, b ou c, ou todos de uma vez. Pegamos a ou c. Apenas os valores correspondentes são exibidos, mas vemos que o campo a se tornou obrigatório, porque, relativamente falando, removemos o ponto de interrogação. Nós definimos esse tipo, usamos. Ninguém nos incomoda de pegar este genérico e enfiar em outro genérico.



Como isso acontece? Definimos outro tipo, Custom. O segundo parâmetro expande não keyof, mas o resultado da aplicação do genérico, que mostramos à direita. Como funciona, o que estamos transferindo para ele?



Passamos qualquer objeto e todas as suas chaves para este genérico. Isso significa que a saída será uma cópia do objeto com todos os campos obrigatórios. Esta cadeia de aninhamento de um genérico em outro genérico e assim por diante pode continuar indefinidamente, dependendo das tarefas e da estrutura do código. Apresente construções reutilizáveis ​​para genéricos e assim por diante.



Os argumentos especificados não precisam estar em ordem. Mais ou menos como o parâmetro P expande as chaves T no CustomPick genérico. Mas ninguém nos incomodou em indicá-lo como o primeiro parâmetro e T como o segundo. O TypeScript não passa sequencialmente pelos parâmetros. Ele olha para todos os parâmetros que especificamos. Em seguida, ele resolve um certo sistema de equações e, se encontrar uma solução para os tipos que satisfazem esse sistema, a verificação de tipo é aprovada.



A este respeito, você pode derivar um genérico engraçado, no qual os parâmetros expandem as chaves uns dos outros: a - essas são as chaves b, b - as chaves a. Ao que parece, como pode ser isso, as chaves das chaves? Mas sabemos que as strings do TypeScript são, na verdade, strings JavaScript, e as strings JavaScript têm seus próprios métodos. Conseqüentemente, qualquer nome de método de string servirá. Porque o nome de um método de string também é uma string. E a partir daí ela tem seu nome.



Conseqüentemente, podemos obter tal restrição, e o sistema de equações será resolvido se indicarmos os tipos requeridos.



Vamos ver como isso pode ser usado na realidade. Nós o usamos para a API. Existe um site onde os aplicativos Yandex são implantados. Queremos mostrar o projeto e o serviço que corresponde a ele.



No exemplo, peguei um projeto para executar máquinas virtuais qyp para desenvolvedores. Sabemos que temos a estrutura desse objeto no backend, tiramos da base. Mas além do projeto, existem outros objetos: rascunhos, recursos. E todos eles têm suas próprias estruturas.



Além disso, queremos solicitar não todo o objeto, mas alguns campos - o nome e o nome do serviço. Existe essa oportunidade, o back-end permite que você passe caminhos e receba uma estrutura incompleta. DeepPartial é descrito aqui. Aprenderemos como projetá-lo um pouco mais tarde. Mas isso significa que nem todo o objeto é transferido, mas parte dele.



Queremos escrever alguma função que solicite esses objetos. Vamos escrever em JS. Mas se você olhar de perto, você pode ver erros de digitação. No tipo de "Projeact", nos caminhos também há um erro de digitação no serviço. Não é bom, o erro será em tempo de execução.



A variante TS não parece ser muito diferente dos caminhos. Mas mostraremos que, de fato, não pode haver outros valores no campo Tipo além daqueles que temos no backend.



O campo de caminhos tem uma sintaxe especial que simplesmente não nos permite selecionar outros campos ausentes. Usamos uma função onde simplesmente listamos os níveis de aninhamento de que precisamos e obtemos um objeto. Na verdade, obter caminhos a partir dessa função é uma preocupação de nossa implementação. Não há segredo aqui, ela usa um proxy. Isso não é tão importante para nós.



Vamos ver como obter a função.





Temos uma função, seu uso. Existe essa estrutura. Primeiro, queremos obter todos os nomes. Escrevemos um tipo em que o nome corresponde à estrutura.



Digamos que, para um projeto, descrevamos seu tipo em algum lugar. Em nosso projeto, geramos taipings a partir de arquivos protobuf que estão disponíveis no repositório geral. A seguir, vemos que temos todos os tipos usados: Projeto, Rascunho, Recurso.



Vejamos a implementação. Vamos ver em ordem.



Existe uma função. Primeiro, vamos ver como ele é parametrizado. Apenas por esses nomes descritos anteriormente. Vamos ver o que ele retorna. Ele retorna valores. Porque isto é assim? Usamos a sintaxe de colchetes. Mas como estamos passando uma string para o tipo, a concatenação de literais de string quando usada é sempre uma string. Não é possível compor uma string que seja um projeto e um recurso ao mesmo tempo. Ela é sempre uma, e o significado também é o mesmo.



Vamos embrulhar tudo em DeepPartial. Tipo opcional, estrutura opcional. O mais interessante são os parâmetros. Pedimos a eles com a ajuda de outro genérico.



O tipo com o qual os parâmetros genéricos são parametrizados também corresponde à restrição da função. Ele só pode aceitar o tipo de nome - Projeto, Recurso, Rascunho. ID é, obviamente, uma string, não estamos interessados ​​nela. Aqui está o tipo que indicamos, um de três. Eu me pergunto como funciona a função de caminho. Este é outro genérico - por que não o reutilizamos. Na verdade, tudo o que ele faz é simplesmente criar uma função que retorna um array de qualquer, porque nosso objeto pode ter campos de qualquer tipo, não sabemos quais. Nesta implementação, ganhamos controle sobre os tipos.



Se alguém achou isso simples, vamos passar para as estruturas de controle.



Construções de controle



Consideraremos apenas duas construções, mas serão suficientes para cobrir quase todas as tarefas de que necessitamos.



O que são tipos condicionais? Eles são muito semelhantes aos ternarks em JavaScript, apenas para tipos. Temos uma condição de que o tipo a é um subtipo de b. Em caso afirmativo, retorne c. Se não, retorne d. Ou seja, esse é um if normal, apenas para tipos.



Vamos ver como isso funciona. Vamos definir um tipo CustomExclude, que essencialmente copia a biblioteca Exclude. Ele apenas elimina os elementos de que precisamos da união de tipos. Se a for um subtipo de b, retorne vazio, caso contrário, retorne a. Isso é estranho quando você olha por que funciona com junções.



Uma lei especial vem a calhar, que diz: se houver uma união e verificarmos as condições usando extensões, verificamos cada elemento separadamente e depois os combinamos novamente. Esta é uma lei transitiva, apenas para tipos condicionais.



Quando usamos CustomExclude, olhamos para cada item de observação por vez. a expande a, a é um subtipo, mas retorna vazio; b é um subtipo de a? Não - retorno b. c não é um subtipo de a também, retorne c. Então combinamos o que resta, todos os sinais de mais, obtemos bec. Jogamos fora um e conseguimos o que queríamos.



A mesma técnica pode ser usada para obter todas as chaves de uma tupla. Sabemos que uma tupla é o mesmo array. Ou seja, tem métodos JS, mas não precisamos disso, só precisamos de índices. Conseqüentemente, simplesmente descartamos os nomes de todos os métodos de todas as chaves da tupla e obtemos apenas os índices.



Como definimos nosso tipo DeepPartial mencionado anteriormente? É aqui que a recursão é usada pela primeira vez. Passamos por todas as chaves do objeto e olhamos. O valor é um objeto? Em caso afirmativo, aplique-o recursivamente. Se não, e este é um string ou um número, deixe-o e torne todos os campos opcionais. Ainda é um tipo parcial.



Essa chamada recursiva e tipos condicionais realmente tornam o TypeScript Turing completo. Mas não se apresse em se alegrar com isso. Você fica puto se você tentar fazer algo assim, uma abstração com muita recursividade.



O TypeScript monitora isso e gera um erro até mesmo no nível de seu compilador. Você nem vai esperar até que algo seja contado lá. E para esses casos simples, onde temos apenas uma chamada, a recursão é bastante adequada.



Vamos ver como isso funciona. Queremos resolver o problema de remendar o campo do objeto. Usamos uma nuvem virtual para planejar o lançamento de aplicativos e precisamos de recursos.



Digamos que pegamos recursos da CPU, núcleos. Todo mundo precisa de grãos. Simplifiquei o exemplo e há apenas recursos, apenas kernels, e eles são números.



Queremos fazer uma função que corrige os valores, corrige os valores. Adicione os grãos ou subtraia. No mesmo JavaScript, como você deve ter adivinhado, existem erros de digitação. Aqui, adicionamos um número a uma string - não muito bom.



Quase nada mudou no TypeScript, mas, na verdade, esse controle no nível do IDE informará que você não pode passar nada além desta string ou um número específico.



Vamos ver como fazer isso. Precisamos obter essa função e sabemos que temos um objeto desse tipo. Você precisa entender que estamos corrigindo apenas o número e os campos. Ou seja, você precisa obter o nome apenas dos campos onde há números. Temos apenas um campo e é um número.



Vamos ver como isso é implementado no TypeScript.



Definimos uma função. Ele tem apenas três argumentos, o objeto que estamos corrigindo e o nome do campo. Mas este não é apenas um nome de campo. Só pode ser o nome de campos numéricos. Vamos agora descobrir como isso é feito. E o próprio patcher, que é uma função pura.



Existe uma certa função impessoal, um patch. Não estamos interessados ​​em sua implementação, mas em como obter um tipo tão interessante, para obter as chaves não apenas numéricas, mas quaisquer campos por condição. Temos números aqui.



Vamos analisar em ordem como isso acontece.



Passamos por todas as chaves do objeto passado e, em seguida, fazemos este procedimento. Vejamos que o campo do objeto é um subtipo do desejado, ou seja, um campo numérico. Se sim, então é importante que escrevamos não o valor do campo, mas o nome do campo, caso contrário, em geral, nada, nunca.



Mas então um objeto tão estranho apareceu. Todos os campos numéricos passaram a ter seus nomes como valores e todos os campos não numéricos ficaram vazios. Então pegamos todos os valores desse estranho objeto.



Mas, uma vez que todos os valores contêm vazio, e o vazio entra em colapso quando combinados, apenas os campos que correspondem aos numéricos permanecem. Ou seja, obtivemos apenas os campos obrigatórios.



O exemplo mostra: existe um objeto simples, o campo é um. Este é um número? sim. O campo é um número, é um número? sim. A última linha não é um número. Obtemos apenas os campos numéricos necessários.



Com isso resolvido. Deixei o mais difícil para o final. Este é o tipo de inferência - Inferir. Capturando um tipo em uma construção condicional.





É inseparável do tópico anterior, pois só funciona com uma construção condicional.



Com o que se parece? Digamos que queremos saber os elementos de um array. Um certo tipo de array veio, gostaríamos de saber um elemento específico. Olhamos: recebemos algum tipo de array. É um subtipo da matriz da variável x. Se sim - retorna este x, elemento do array. Se não, devolva o vazio.



Nesta condição, o segundo branch nunca será executado, pois parametrizamos o tipo com qualquer array. Claro, será um array de algo, porque um array de any não pode deixar de ter elementos.



Se passarmos uma matriz de strings, espera-se que uma string seja retornada para nós. E é importante entender que não é apenas um tipo que está definido aqui. É visualmente claro a partir da matriz de strings: existem strings. Mas com uma tupla, nem tudo é tão simples. É importante para nós sabermos que o menor supertipo possível está sendo determinado. É claro que todas as matrizes são, por assim dizer, subtipos de uma matriz com qualquer ou com desconhecido. Esse conhecimento não nos dá nada. É importante para nós sabermos o mínimo possível.



Suponha que estejamos passando uma tupla. Na verdade, tuplas também são arrays, mas como sabemos quais elementos esse array possui? Se houver uma tupla de cadeia de caracteres de um número, trata-se, na verdade, de um array. Mas o elemento deve ser do mesmo tipo. E se houver uma string e um número, haverá uma união.



O TypeScript produzirá isso e obteremos exatamente a concatenação de uma string e um número para esse exemplo.



Você pode usar não apenas a captura em um lugar, mas também quantas variáveis ​​desejar. Digamos que definamos um tipo que simplesmente troca os elementos da tupla: o primeiro com o segundo. Pegamos o primeiro elemento, o segundo e os trocamos.



Mas, na verdade, não é recomendável flertar muito com ele. Normalmente, para 90% das tarefas, apenas um tipo de captura é suficiente.





Vamos ver um exemplo. Objetivo: você precisa mostrar, dependendo do estado da solicitação, uma opção boa ou ruim. Aqui estão as capturas de tela do nosso serviço de implantação de aplicativos. Uma entidade, ReplicaSet. Se a solicitação do back-end retornou um erro, você precisa renderizá-lo. Ao mesmo tempo, existe uma API para o back-end. Vamos ver o que o Infer tem a ver com isso.



Sabemos que estamos usando, em primeiro lugar, redux e, em segundo lugar, redux thunk. E precisamos transformar o thunk da biblioteca para poder fazer isso. Temos um jeito ruim e um jeito bom.



E sabemos que uma boa maneira de usar extraReducers no kit de ferramentas redux é assim. Sabemos que existe PayLoad e queremos extrair os tipos personalizados que chegam até nós do back-end, mas não apenas, mas também informações sobre uma solicitação boa ou ruim: há um erro ou não. Precisamos de um genérico para esta saída.



Não estou fazendo uma comparação sobre JavaScript porque não faz sentido. Em JavaScript, em princípio, você não pode controlar os tipos de forma alguma e depende apenas da memória. Não existe uma opção ruim aqui, porque simplesmente não existe uma.



Nós sabemos que queremos este tipo. Mas não temos apenas uma ação. Precisamos chamar o despacho com esta ação. E precisamos dessa visualização, onde precisamos exibir um erro pela chave de solicitação. Ou seja, você precisa misturar essa funcionalidade adicional em cima do redux thunk usando o método withRequestKey.



Temos esse método, é claro, mas também temos o método API original, getReplicaSet. Está escrito em algum lugar e precisamos substituir o redux thunk usando algum tipo de adaptador. Vamos ver como fazer.



Precisamos obter uma função como esta. É uma ideia com tanta funcionalidade extra. Parece assustador, mas não se assuste, agora vamos desmontá-lo nas prateleiras para que fique claro.



Existe um adaptador que estende o tipo de biblioteca original. Simplesmente combinamos um método withRequestKey adicional e uma chamada personalizada para este tipo de biblioteca. Vamos ver qual é a principal característica do genérico, quais parâmetros são usados.



O primeiro é apenas nossa API, um objeto com métodos. Podemos fazer getReplicaSet, obter projetos, recursos, não importa. Estamos usando um método específico no método atual e o segundo parâmetro é apenas o nome do método. Em seguida, usamos os parâmetros da função que estamos solicitando, usamos o tipo de biblioteca Parâmetros, este é um tipo TypeScript. E da mesma forma, usamos o tipo de biblioteca ReturnType para a resposta de back-end. É para isso que a função retornou.



Em seguida, apenas passamos nossa saída personalizada para o tipo AsyncThunk que a biblioteca nos forneceu. Mas qual é essa conclusão? Este é outro medicamento genérico. Na verdade, parece simples. Salvamos não apenas a resposta do servidor, mas também nossos parâmetros, o que passamos. Apenas para acompanhá-los no Redutor. A seguir, veremos withRequestKey. Nosso método apenas adiciona uma chave. O que ele retorna? O mesmo adaptador porque podemos reutilizá-lo. Não precisamos escrever withRequestKey. Esta é apenas uma funcionalidade adicional. Ele envolve e retorna recursivamente o mesmo adaptador para nós, e passamos a mesma coisa lá.



Finalmente, vamos ver como enviar para o Redutor o que essa conversão retornou para nós.





Temos esse adaptador. O principal é lembrar que existem quatro parâmetros: API, método API, parâmetros (entrada) e saída. Precisamos encontrar uma saída. Mas lembramos que temos uma saída personalizada: a resposta do servidor e o parâmetro de solicitação.



Como posso fazer isso com o Infer? Vemos que este adaptador é fornecido para a entrada, mas geralmente é any: any, any, any, any. Temos que retornar este tipo, fica assim, a resposta do servidor e os parâmetros de solicitação. E estamos olhando para onde deveria ser a entrada. No terceiro. É aqui que colocamos nossa captura de tipo. Pegamos a entrada. Da mesma forma, a saída está em quarto lugar.



TypeScript é baseado em digitação estruturada. Ele desmonta essa estrutura e entende que a entrada é aqui, no terceiro lugar, e a saída, no quarto. E retornamos os tipos que desejamos.



Assim, conseguimos inferência de tipo, temos acesso a eles já no próprio Redutor. É basicamente impossível fazer isso em JavaScript.



All Articles