Digamos que temos uma Estrutura segurando um registro no qual desejamos apply
outra Estrutura segurando um Registro contendo funções para as chaves da primeira Estrutura. (basicamente o reverso da Apply
especificação do Land da fantasia)
Vamos primeiro definir alguns tipos que usaremos:
type Index = string|number|symbol
type Struct<R extends Record<Index, any>> = {
readonly value: R
apOn: <B>(fab: Struct<{[K in keyof R]?: (a: R[K], k: K) => B}>) =>
Struct<{[K in keyof R]: B}>
}
em seguida, aborda a própria estrutura
declare function newStruct<R extends Record<Index, any>> (value: R): Struct<R>
const struct = newStruct ({a: 1, b: true})
Os dois primeiros funcionam conforme o esperado ao especificar para o apOn
argumento do método a Struct
com o mesmo número de teclas que possuem funções com o mesmo tipo de retorno.
const withFuncsa = newStruct ({
a: (a: number) => `${a}`,
b: (a: boolean) => `${a}`,
})
const withFuncsb = newStruct ({
a: (a: number) => a > 0,
})
const resa = struct.apOn (withFuncsa).value // as expected {a: string, b: string}
const resb = struct.apOn (withFuncsb).value // as expected {a: boolean, b: boolean}
Mas interrompe quando os tipos de retorno são diferentes:
const withFuncsc = newStruct ({
a: (a: number) => a > 0,
b: (a: boolean) => `${a}`,
})
const resc = struct.apOn (withFuncsc).value
// breaks cause expect all returns type of this withFuncc function to be identical
// expected {a: boolean, b: string}
Eles são uma maneira de fazê-lo funcionar com diferentes tipos de retorno?
Precisamos retrabalhar a assinatura para adiar a decisão B
até que a chave que ela calcula seja conhecida.
type Struct<R extends Record<Index, any>> = {
readonly value: R
apOn: <F extends {[K in keyof Partial<R>]: (a: R[K], k: K) => any}>
(fab: Struct<F>) =>
Struct<Omit<R, keyof F> & {[K in keyof F]: ReturnType<F[K]>}>
}
Aqui F
está o registro dos mapeadores. É importante isso, [K in keyof Partial<R>]: ...
e não [K in keyof R]?: ...
porque queremos garantir que o modificador opcional não seja achatado em ... | undefined
. Os mapeadores estão presentes e uma função ou não.
Então, no tipo de retorno, mantemos os campos não mapeados iguais, Omit<R, keyof F>
mas para todos os campos mapeados, usamos o tipo produzido pelo mapeador {[K in keyof F]: ReturnType<F[K]>}
,.
Os tipos intermediários parecem um pouco confusos no intellisense, mas eles fazem o trabalho:
const t: {strings: string[], bools: boolean[]} = {
strings: [resa.a, resa.b, resc.b],
bools: [resb.a, resb.a, resc.a],
}
Este artigo é coletado da Internet.
Se houver alguma infração, entre em [email protected] Delete.
deixe-me dizer algumas palavras