generics - Typescript - Sužavanje T[K] u funkciji kada se proslijedi više vrijednosti ključa


Pokušavam napisati hook koji prihvaća više ključeva objekta i filtrira podatke na temelju trenutno primijenjenih filtera. Dakle, nešto u stilu ovoga:

interface IFilterable<T,K extends keyof T & string>{
  key: K;
  customFilter?: (item: T[K], applied: T[K][]) => boolean
  //The hook uses some default logic to filter if this function isn't available.
}

function useFilters<T,K extends keyof T & string>(data: T[], filters: IFilterable<T,K>[]): T[]{
 //filter out the data, return new dataset.
}

Problem je u tome što kada se proslijedi više filtara (IFilterable), parametri item i applied u funkciji customFilter postaju unija tipova ključeva. Ili, u kodu...

useFilters(
  [{a: 1, b: "bob"], {a: 2, b: "berry"}],
  [{key: "a", customFilter: (item, applied) => {
    //Wanted: item has the type number
    //Reality: item has the type (string | number)
  }, 
  {key: "b", customFilter: (item, applied) => {
    //Wanted: item has the type string.
    //Reality: item has the type (string | number)
  }
}]);

Riješio sam problem tako da sam sve upisivao kao unknown (ili unknown[]), a zatim stvarao omotne funkcije koje kasnije vraćaju stvari na pravi tip, ali želim zadržati sve snažno tipizirano cijelim putem ako je moguće.

Dakle, postoji li na bilo koji način u slučajevima poput ovog, kada prosljeđujem niz objekata gdje je jedno svojstvo ključ, a drugo svojstvo funkcija koja prima T[K], da se suzi tip na određeni T[K] umjesto na uniju tipova?

Hvala.

Odgovor #1

AZURIRANJE 17. rujna 2021.

Napravio sam domaću zadaću.

Razmotrite ovaj primjer:

/**
 * Obtain a union of all values of a dictionary
 */
type Values<T> = T[keyof T]

type FilterBuilder<T extends any[]> = Values<{
  [Key in keyof T[number]]: {
    key: Key,
    customFilter: (item: T[number][Key], applied: T[number][Key][]) => void
  }
}>

{
  //   type Test = {
  //     key: "a";
  //     customFilter: (item: 1, applied: 1[]) => void;
  // } | {
  //     key: "b";
  //     customFilter: (item: "str", applied: "str"[]) => void;
  // }
  type Test = FilterBuilder<[{ a: 1, b: 'str' }]>
}

const withFilters = <
  Item extends Record<PropertyKey, unknown>,
  Items extends Item[],
  >(data: [...Items]) => <
    Filter extends FilterBuilder<Items>,
    Filters extends Filter[],
    >(filters: [...Filters]) => { }

const useFilters = withFilters([{ a: 1, b: "bob" }, { a: 2, b: "berry" }])

const result = useFilters(
  [{
    key: "a", customFilter: (item, applied) => { }
  },
  {
    key: "b", customFilter: (item, applied) => { }
  }]
);

Za TypeScript je teško zaključiti key objekta i odmah primijeniti taj tip na drugu svojinu istog objekta, barem u ovom slučaju.

Stoga sam upravo stvorio uniju svih dopuštenih parova key/customFilter filtara.

Igralište

Values - stvara uniju svih vrijednosti u strukturi podataka rječnika

GraditeljFiltra:

  1. iterira preko svakog ključa elemenata koji postoje u tupli T.
  2. stvara novi objekt za iterirani ključ s key i customFilter svojstvima.
  3. Gdje je customFilter funkcija s dva argumenta: item - vrijednost iteriranog objekta, applied - niz vrijednosti iteriranog objekta

withFilters: je kurrirana funkcija. Očekuje tuple objekata za koje smo zainteresirani i vraća funkciju koja očekuje niz rezultata FilterBuilder. FilterBuilder jednostavno vraća diskriminirani unija svih dopuštenih stanja key/customFilter objekta.