Typescript: Ako `aplikovať` na štruktúru obsahujúcu záznam?
Povedzme, že máme štruktúru, ktorá drží záznam, na ktorom chceme použiť inú štruktúru, ktorá drží záznam obsahujúci funkcie pre kľúče prvej štruktúry. (v podstate opačne k špecifikácii Apply fantasy Land)
Najprv si definujme niektoré typy, ktoré budeme používať:
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}>
}
potom sa pustí do samotnej štruktúry
declare function newStruct<R extends Record<Index, any>> (value: R): Struct<R>
const struct = newStruct ({a: 1, b: true})
Prvé dva te fungujú podľa očakávania, keď sa pri zadávaní argumentu metód apOn špecifikuje Struct s rovnakým počtom kľúčov, ktoré majú funkcie s rovnakým typom návratovej hodnoty.
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}
Ale zlyhá, keď sa typy návratu líšia:
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}
Existuje spôsob, ako to mať fungujúce s rôznymi návratovými typmi?
Odpoveď č. 1
Musíme prerobiť podpis tak, aby rozhodovanie o B bolo odložené, kým nebude známy kľúč, pre ktorý sa počíta.
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]>}>
}
Tu je F záznam mapperov. Je dôležité, že [K in keyof Partial<R>]: ... namiesto [K in keyof R]?: ..., pretože chceme zabezpečiť, aby sa voliteľný modifikátor nezmenil na ... | undefined. Mappere sú prítomné a sú buď funkciou, alebo neprítomné.
Potom v návratovom type ponecháme nemapované polia rovnaké Omit<R, keyof F>, ale pre všetky mapované polia vezmeme typ vytvorený mapperom, {[K in keyof F]: ReturnType<F[K]>}.
Medzi typy vyzerajú v Intellisense trošku neporiadne, ale svoju prácu zvládnu:
const t: {strings: string[], bools: boolean[]} = {
strings: [resa.a, resa.b, resc.b],
bools: [resb.a, resb.a, resc.a],
}
Odkaz na ihrisko
