Vyhledávání na webu

Jak aplikovat staticky typovaný záznam funkcí na argument v TypeScriptu


Mám konečnou množinu továren na funkce s podobnými návratovými typy, z nichž každá vrací operaci CRUD (ale pro účely této otázky by to mohla být jakákoli operace). Návratový typ operace je pro jednoduchost Promise.

Chci převést staticky typovaný objekt konstruktorů funkcí na staticky typovaný objekt funkcí vrácených těmito konstruktory, konkrétně:

{f1: (k: K) => (...) => Promise<...>, f2: (k: K) => (...) => Promise<...>}

mělo by se stát

{f1: (...) => Promise<...>, f2: (...) => Promise<...>}

když je použita klíč K.

Jako konkrétnější příklad:

type K = 'a' | 'b';

const makeGet = (k: K): (id: string): Promise<DbRecord<K>> => {...}
const makeSet = (k: K): (id: string, Partial<DbRecord<K>>): Promise<void> => {...}

const fs = {get: makeGet, set: makeSet} as const;

export const makeCrud = (k: K) => mapValues(fs, (f) => f(k)); // mapValues is a function I'm looking for

Zatím se mi daří získat pouze návrat podobný záznamu (tj. při použití lodash mapValues nebo fp-ts Record.map): (pseudokód) {f1: typeof f1(k) | typeof f2(k), f2: typeof f1(k) | typeof f2(k)}, a mým cílem je získat přesnější mapování (pseudokód) {f1: typeof f1(k), f2: typeof f2(k)}

Mám tušení, že fp-ts má něco pro toto, ale nemohu najít ten správný nástroj.

Existuje něco takového, nebo možná podobná mapValues, která respektuje k=>v statický vztah, co lze napsat ručně?

Odpověď č. 1

Můžete použít mapovaný typ k definování typu, který hledáte z vaší hodnoty fs, například takto:

type MappedK<T extends Record<string, (k: K) => any>> = {
  [KK in keyof T]: ReturnType<T[KK]>
};

Dětské hřiště

Tak by mělo být možné definovat vaši funkci mapValues (já jí říkám make) takto:

function make<T extends Record<string, (k: K) => any>>(vals: T, k: K): MappedK<T> {
  return Object.keys(vals).reduce((acc, objK) => ({
    ...acc,
    [objK]: vals[objK](k),
  }), {} as MappedK<T>);
}

export const makeCrud = (k: K) => make(fs, k);

O které věřím, že by mělo fungovat a mít správné typy, ale interně jsou typy trochu zjednodušené. Problém je v tom, že objekt, který předáváte, musíte v nějakém okamžiku iterovat, a všechny pomocníky pro iteraci vlastností objektu mají nutně oslabené typy kvůli tomu, jak TypeScript funguje.

Podívejte se na tento komentář na GH pro lepší vysvětlení

RE: fp-ts

Ještě jedna věc související s fp-ts: nebylo by možné použít něco jako fp-ts pomocníky Record nebo ReadonlyRecord, protože Record musí mít jednotný typ. Všechny typy a hodnoty v recordu musí být jednotné, takže nakonec vždy skončíte s velkým sjednoceným typem v parametru hodnoty, protože typ hodnoty musí reprezentovat jakoukoli hodnotu, kterou můžete získat zpět indexováním Recordu.

Pokud chcete udržet vztah mezi klíči a typem u těchto klíčů, musíte použít mapovaný typ.