Considere o seguinte pseudo-TypeScript, onde Class
denota algo parecido com um construtor:
function convert<C extends Class>(
stuff: unknown,
mappers: [Mapper<unknown, parameter i of constructor C>],
c: constructor of C
): C {
return c(...mappers.map((f) => f(stuff))
}
A ideia é que cada um mapper
extraia um parâmetro do construtor stuff
e a tupla de resultado seja passada ao construtor.
Nesta forma simples seria um tanto bobo, pois eu sempre poderia chamar o construtor como new C(map1(stuff), map2(stuff), ...)
em vez de chamar esta construção elaborada. Mas uma vez que eu tenha o acima, eu quero adicionar tratamento de erro para os mapeadores de forma que o construtor seja chamado somente se nenhum tiver um problema.
O que tenho até agora:
type Class = abstract new (...args: any) => any;
type Func<A, C> = (a: A) => C;
type FuncTuple<A, TUP> = { [P in keyof TUP]: Func<A, TUP[P]> };
type ParamFuncTuple<A, C extends Class> = FuncTuple<A, ConstructorParameters<C>>;
function boo<C extends Class>(
text: unknown,
funcs: ParamFuncTuple<unknown, C>,
c: Class
) {
const params = funcs.map((f) => f(text))
}
Aqui o compilador diz const params
has type any[]
o que não é muito promissor. Isso é possível?
Eu sugeriria escrever a assinatura de chamada
convert()
assim:Existem três parâmetros de tipo genérico
T
: corresponde à entrada para os mapeadores (você chamou assim,stuff
mas eu nomeeimapperArg
aqui);A
corresponde à tupla de tipos de parâmetros do construtor; eO
corresponde ao tipo de instância construído.O importante aqui é que o tipo de
mappers
é um tipo de tupla mapeado sobre os índices deA
; para cada índiceI
doA
tipo de tupla, ondeA[I]
é o tipo de elemento deA
naquele índice, o tipo de elemento demappers
naquele índice é o tipo de função(mapperArg: T) => A[I]
.Vamos testar essa digitação:
Aqui o TypeScript infere que
T
éstring
, queO
é (o tipo de instância)Foo
e queA
é o tipo de tupla[a: string, b: number, c: boolean]
. Portanto, o tipo demappers
é computado para ser o tipo de tupla mapeado[a: (mapperArg: string) => string, b: (mapperArg: string) => number, c: (mapperArg: string) => boolean]
, conforme desejado.Quanto à implementação de
convert()
, poderia ser assim:Observe que o resultado de
mappers.map(fn => fn(mapperArg))
tem que ser afirmado comoA
; não é realmente possível para o compilador TypeScript verificar se o resultado é do tipoA
, porque o sistema de tipos não é expressivo o suficiente para capturar o queArray.prototype.map
acontece com tipos de tupla arbitrários para funções de retorno de chamada arbitrárias. Veja Mapeando valor de tipo de tupla para valor de tipo de tupla diferente sem conversões . Você se deparou com isso comconst params
has typeany[]
; pode não ser promissor, mas também não é realmente evitável, então é razoável usar uma única afirmação de tipo e seguir em frente.Link do playground para o código