Tentando entender os limites de 'T estende inferir U'

8

Eu entendi que algo como:

type GenericExample<T> = T extends (infer U) ? U : 'bar';

é igual a:

type GenericExample<T> = T extends T ? T : 'bar';

Mas quando as coisas se tornam mais elaboradas, o TypeScript reclama:

type Types = 'text' | 'date' | 'articles' | 'params';

type MyExperiment<Type extends Types> =  { t : Type };

type MyExperimentsUnion = Types extends (infer U) ? MyExperiment<U> : never;
// Type 'U' does not satisfy the constraint 'Types'.
// Type 'U' is not assignable to type '"params"'.

Então, eu gostaria de perguntar por que isso está errado: nesse caso específico, a distribuição por união deve ocorrer, portanto o Utipo inferido deve ser text, então datee assim por diante. Então, o que T extends (infer U)realmente significa e quando seria apropriado usá-lo?

Andrea Simone Costa
fonte

Respostas:

0

Eu não acho que ele deveria ser usado da maneira que você está usando - basicamente inferdeve ser usado para "inferir" (ou resolver um nome talvez melhor?) O tipo, mais comumente de um genérico.

Do jeito que você o está usando, você está criando um tipo que não possui nenhuma parte "dinâmica" (basicamente não é genérica), o que significa que é sempre a mesma e, portanto, deduzir que algo sempre é o mesmo não faz sentido . Porque, em tempo de compilação, você já sabe que isso Typesse estende apenas Types & '...anything else'e, como você não pode definir essa outra parte do seu MyExperimentsUniontipo, infernão tem muita utilidade.

Exemplo de uso

interface Action<T> {
    payload: T
}

type ExtractGeneric<T> = T extends Action<infer X> ? X : never

function getPayload<T extends Action<any>>(action: T): ExtractGeneric<T> {
    return action.payload;
}

const myAction = { payload: 'Test' };
const myPayloadWithResolvedType = getPayload(myAction);

No exemplo acima, myPayloadWithResolvedTypeteria o stringtipo resolvido, porque, se você não estivesse usando infer, teria que passar esse tipo de retorno como segundo parâmetro, provavelmente assim:

function getPayloadNonExtract<T extends Action<U>, U>(action: T): U {
    return action.payload;
}

Aqui está o link para o playground.

Felicidades.

zhuber
fonte
1
Oi cara, obrigado pela sua resposta! Devo dizer que conheço o objetivo geral da inferpalavra - chave, mas fiz essa pergunta estranha porque descobri que T extends (infer U)é válida em um tipo condicional. Fiquei surpreso com isso e comecei a investigar.
Andrea Simone Costa
1
Acho que entendi sua pergunta, mas não tenho certeza de como você gostaria que o texto datilografado fosse resolvido U? O que deveria MyExperiment<U>ser? Quero dizer, Udeve ser a "subclasse" de Types, mas o texto datilografado não pode determinar exatamente o que é, Uuma vez que você não está passando nenhum tipo (não é do tipo genérico), e não vejo por que deveria usar o padrão Types. Eu acho que essa é uma das razões pelas quais você recebe o erro, embora eu concorde que poderia ser mais descritivo, mas parece que eles estão pensando que os desenvolvedores devem saber que infernão era para ser usado dessa maneira.
zhuber 14/04
0

Provavelmente não vale mais do que dois centavos, mas:

type Types = 'text' | 'date' | 'articles' | 'params';
type MyExperiment<Type extends Types> =  { t : Type };

type MyExperimentsGenericUnion<T extends Types> = T extends (infer U) ? MyExperiment<U> : never;
// same error

type MyExperimentsUnionConstraint = any extends MyExperiment<infer U> ? MyExperiment<U> : never;
// no error

Posso pensar em algumas razões pelas quais MyExperimentsUnionConstraintfunciona, mas MyExperimentsUnionnão funciona , mas não tenho o conhecimento aprofundado da linguagem para expressá-las, para que faça sentido.

Para dar uma explicação por exemplo:

  • in MyExperimentsUnion, from Types extends U, Upode ser inferido como Types | 'bla'e MyExperiment<Type | 'bla'>não funciona.
  • em MyExperimentsUnionConstraint, Usó pode ser inferido como algo que funciona comMyExperiment
artcorpse
fonte