Minha função tem um parâmetro genérico T
e outro parâmetro restrito com base no passado T
.
Exemplo:
const foo = {
a: 'hello',
b: 'world',
} as const
function bar<T extends object, K extends keyof T>(obj: T, key1: K, key2: K) {};
bar(foo, 'a', 'b') // No error, even though key1 should narrow K to 'a'. K stays 'a'|'b'
Meu objetivo é, em última análise, passar um objeto, restringir outros parâmetros com base em seu tipo e inferir seus valores quando eles são passados para restringir ainda mais outros parâmetros:
- Parâmetros passados inferem
T
e restringemK
- O segundo parâmetro passado infere
K
e restringeV
- etc.
Há uma compensação quando existem vários candidatos a inferência para um argumento de tipo genérico . Se você tiver uma função como
e alguém liga
o que deveria acontecer? Em um extremo, o compilador poderia rejeitar qualquer chamada em que
A
eB
não sejam de tipos idênticos. No outro extremo, poderia aceitar qualquer coisa sintetizando o tipo uniãoA | B
. Existem casos de uso para qualquer comportamento e para algo intermediário, e pessoas diferentes desejam coisas diferentes em momentos diferentes.Atualmente, o TypeScript usa apenas regras heurísticas que funcionam bem em uma ampla variedade de cenários do mundo real, mas que às vezes decepcionam as pessoas.
A heurística é principalmente que os tipos de união não serão sintetizados a menos que os múltiplos candidatos façam parte do mesmo tipo primitivo. Portanto, se em vez de
"a"
e"b"
(ambas as strings) você usasse"a"
and3
(uma string e um número), obteria o erro esperado:Mas quando ambos os tipos são tipos literais que seriam ampliados para o mesmo tipo primitivo, como
"a"
e"b"
são ambos subtipos destring
, o TypeScript sintetiza tipos de união para permitir que ele tenha sucesso, como você viu.Não é isso que você deseja, mas, novamente, essas são heurísticas que funcionam bem em muitos outros casos.
Então, existe uma maneira de dizer ao compilador que você deseja outra coisa? Nesse caso, você gostaria de dizer "
key2
só deve ser verificado em relação ao tipoK
inferido dekey1
. Não deve ser usado para inferirK
a si mesmo". Ou seja,key2
deve ser um "uso de parâmetro de tipo não inferencial", como umNoInfer<T>
tipo de utilitário hipotético solicitado em microsoft/TypeScript#14829 :Esse é um problema em aberto, então no momento não há implementação nativa
NoInfer<T>
do . Felizmente, existem várias abordagens discutidas que você pode usar para defini-lo sozinho, o que funciona para alguns casos de uso (mas nada parece funcionar em todos os lugares). Uma maneira descrita aqui é:que atribui
NoInfer<T>
uma prioridade de inferência mais baixa em comparação comT
e, portanto, o compilador tenderá a inferir deT
e não deNoInfer<T>
. Se você escrever dessa maneira, obterá o comportamento desejado.Outra abordagem é apenas declarar outro parâmetro de tipo restrito ao primeiro. Isso bloqueará a inferência também:
De qualquer forma, deve funcionar, pelo menos para o código de exemplo da pergunta.
Link do Playground para o código