Az adatbevitel általában egyszerű, de funkcionális technikákkal, rekurzióval és egyebekkel bonyolulttá válhat. Ez a cikk kiegészíti az előzőt a részleges alkalmazásról, a TypeScript-ben történő teljes gépelés fejlesztésével

A sorozat előző cikkében megtanultuk, hogyan kell részleges alkalmazást végrehajtani JavaScriptben, és kifejlesztettünk egy magasabb rendű partial() függvényt... de mi a helyzet a TypeScript verzióval?



Előfordul, hogy a függvényünk begépelése nem triviális, és több érdekes technikát igényel, ezért ebben a cikkben az előző cikkben leírt munkát fejezzük be, JavaScriptről TypeScriptre lépve, és teljes típusvezérlőket adunk hozzá. (További részletekért tekintse meg a Mastering JavaScript Functional Programming című könyvemet, amelyben a curry-ről és más átalakításokról is beszélek.) Érdemes újra megnézni a kódunkat, hogy friss legyen. gondolatban.

Ellenőrizze a megfelelő típusokat

A megadott és hiányzó paraméterek elemzésekor egyenként ellenőriznünk kell, hogy a típusok megegyeznek-e közöttük. A típusok meghatározásakor azonban nem írhatunk pusztán egy if-t vagy egy ciklust, ezért meg kell találnunk a kerülő megoldást: a deklarációnk háromtagú operátorokat és rekurziót fog használni a if s és ciklusok helyett.

Írunk egy TypesMatch<P,A> típusdeklarációt (a P a 'paramétereket', a A pedig az 'argumentumokat' jelenti; mindkettő típusos sor lesz), amely vagy boolean (ha P és A egyezik) vagy never (ha eltérés volt) . A nyilatkozat a következő:

type TypesMatch<
  P extends any[],
  A extends any[]
> = 0 extends P["length"]                      
  ? boolean                      1️⃣
  : 0 extends A["length"]        
  ? boolean                      2️⃣
  : [P, A] extends [         
      [infer PH, ...infer PT],
      [infer AH, ...infer AT]?
    ]
  ? AH extends undefined       
    ? TypesMatch<PT, AT>         3️⃣
    : PH extends AH             
    ? TypesMatch<PT, AT>         4️⃣
    : never
  : never;

A 0 extends P["length"] sor 1️⃣ megzavarhatja Önt, de ez a módja annak, hogy ellenőrizze, hogy a P hossza nulla-e. A infer és a terjedést használjuk a P és A első típusának ( PH és AH; a H a 'fejet') elkülönítésére a többitől ( PT és AT; T a 'farok').

A mi típusunk a következő:

  • ha a P 1️⃣ vagy a A 2️⃣ üres, küldje vissza boolean
  • ha a A első típusa nem definiált 3️⃣ vagy ha a A első típusa megegyezik a P első típusával 4️⃣ dobja el a P első típusát, dobja el a A első típusát, és rekurzívan elemezze a fennmaradó típusokat
  • egyébként (ha a A első típusa nem definiálatlan, de nem egyezik a P első típusával) a never értéket adja vissza

Néhány gyorsteszttel ellenőrizhetjük, hogy minden rendben van - az „ok” típusok boolean (azaz helyesek), a „rossz” típusok pedig never (tehát rosszak):

type ok1 = TypesMatch<
  [boolean, number, string],
  [undefined, undefined, undefined]
>;
type ok2 = TypesMatch<
  [boolean, number | string, string],
  [boolean, undefined, string]
>;
type ok3 = TypesMatch<
  [boolean, number | string, string],
  [boolean, string, string]
>;

type bad1 = TypesMatch<
  [boolean, number, string],
  [undefined, string, number]
>;
type bad2 = TypesMatch<
  [boolean, number | string, string],
  [string, undefined, string]
>;
type bad3 = TypesMatch<
  [boolean, number | string, string],
  [boolean, boolean, string]
>;

Következtesse a függő paramétereket

Szükségünk lesz egy másik segédtípusra, a Partialize<P,A>-re, amely megkapja a paramétertípusokat és egy másikat az argumentumtípusokkal, és létrehoz egy sort a még mindig nem megadott argumentumok típusaival; azaz azok a típusok a P-ban, amelyekhez a A-ban van egy megfelelő undefined típus. Tegyük fel, hogy már ellenőriztük, hogy a típusok egyeznek-e (ahogyan az előző részben láttuk). A típusokat a következőképpen írhatjuk fel.

type Partialize<
  P extends any[],
  A extends any[]
> = 0 extends P["length"]                 
  ? []                                1️⃣
  : 0 extends A["length"]                 
  ? P                                 2️⃣
  : [P, A] extends [
      [infer PH, ...infer PT],
      [infer AH, ...infer AT]
    ]
  ? AH extends undefined                 
    ? [PH, ...Partialize<PT, AT>]     3️⃣
    : [...Partialize<PT, AT>]         4️⃣
  : never;

A típusdefiníció stílusában nagyon hasonló a TypesMatch-hez, bár az eredmények eltérőek.

  • 1️⃣ Ha a P üres (minden paraméter megadva volt), akkor az eredmény is üres
  • 2️⃣ Ha a A üres (nincs argumentum, minden paraméter függőben van), az eredmény P
  • 3️⃣ Ha a A első típusa nem definiált, az eredmény a P első típusa lesz (mert nem volt megadva), majd a P és A többi típusának feldolgozásának eredménye.
  • 4️⃣ Ellenkező esetben, ha a P első típusa megegyezik a A első típusával, az eredmény az lesz, amit a P többi típusának a A többi típusával való összehasonlításával visszaadunk.

Ellenőrizhetjük, hogyan működik a Partialize:

type part1 = Partialize<
  [boolean, number, string],
  [undefined, undefined, undefined]
>; // boolean,number,string
type part2 = Partialize<
  [boolean | string, number, string],
  [undefined, number, undefined]
>; // boolean|string,string
type part3 = Partialize<
  [boolean | string, number, string],
  [boolean, undefined, string]
>; // number
type part4 = Partialize<
  [boolean, number, string],
  [boolean, number, string]
>; // empty!

Munkamenet-visszajátszás fejlesztőknek

Fedezze fel a frusztrációkat, értse meg a hibákat, és javítsa ki a lassulásokat, mint még soha az OpenReplay segítségével – egy nyílt forráskódú munkamenet-visszajátszási eszköz fejlesztők számára. Percek alatt saját maga tárolja, és teljes mértékben kézben tarthatja ügyféladatait. Tekintse meg GitHub-tárhelyünket, és csatlakozzon közösségünk több ezer fejlesztőjéhez.

Teljes gépelés

Most, hogy módunk van ellenőrizni, hogy a paraméterek és az argumentumok egyeznek-e, és azt is, hogyan kell kiszámítani a még függőben lévő paramétereket, megírhatjuk az Partial típusú deklarációnkat.

Az alábbi definícióban az P a függvény paramétereinek típusát, A a megadott argumentumok típusait és R a függvény eredményének típusát jelöli.

type Partial<P extends any[], R> = <A extends any[]>(
  ...x: A
) => TypesMatch<P, A> extends never
  ? never
  : P extends any[]
  ? 0 extends Partialize<P, A>["length"]
    ? R
    : Partial<Partialize<P, A>, R>
  : never;

Hogy működik ez?

  • ha az P és A típusai nem egyeznek, akkor never eredményt adunk vissza, ami azt jelenti, hogy probléma volt
  • ha az A üres (nincs megadva argumentum), akkor a részlegesített függvényünk P típusú paramétereket vesz fel, és R típusú eredményt ad.
  • ha a A nem üres, akkor kiszámítjuk a P és A típusok összehasonlításának eredményét, és ezek lesznek a paraméterek típusai a részleges függvényünkhöz, amely a R típusú eredményt eredményezi.

Ezt az adattípus-definíciót önmagában nagyon nehéz lenne megérteni, de mivel már minden összetevőjét láttuk, érthető.

Fejezd be a munkát

Rendben, úgy tűnik, készen állunk a befejezésre! Íme a partial(...) teljes definíciója a TypeScript-ben – és még egy gépelési részlettel kell foglalkoznunk! A probléma az lesz, hogy a TypeScript, bár általában önmagában is képes kikövetkeztetni a típusokat, nem tudja kitalálni, hogyan működik a függvényünk, és mit ad vissza, ezért egy trükköt kell tennünk, hogy segítsünk neki.

function partial<P extends any[], R>(              1️⃣
  fn: (...a: P) => R
): Partial<P, R>;                                  
function partial(fn: (...a: any) => any) {         2️⃣ 
  const partialize =
    (...args1: any[]) =>
    (...args2: any[]) => {
      for (
        let i = 0;
        i < args1.length && args2.length;
        i++
      ) {
        if (args1[i] === undefined) {
          args1[i] = args2.shift();
        }
      }
      const allParams = [...args1, ...args2];
      return allParams.includes(undefined) ||
        allParams.length < fn.length
        ? partialize(...allParams)
        : fn(...allParams);
    };
  return partialize();
}

A partial() típusát kétszer határozzuk meg: egyszer az 1️⃣ gépelésünkkel, másodszor pedig a 2️⃣ általános any értékekkel. A probléma az, hogy a TypeScript nem tudja eléggé „érteni” a függvényt ahhoz, hogy kidolgozza az eredményt, ezért „túlterheljük” a típusdefiníciót, és ezután a mi felelősségünk az adattípusok helyességének biztosítása!

Befejezésül néhány példával a teljesen gépelt részleges alkalmazásra a munkahelyen.

const nonsense = partial(function (
  a: number,
  b: string,
  c: boolean
) {
  return `${a}/${b}/${c}`;
});

const ns1 = nonsense(undefined, "9", undefined); 
// type: Partial<[number, boolean], string>

const ns2 = nonsense(22, "9", undefined); 
// type: Partial<[boolean], string>

const ns3 = nonsense(22, "9", true); 
// type: string -- its value is "22/9/true"

const ns4 = nonsense(undefined,"X",undefined)(undefined,false);
// type: Partial<[number], string>

A típusokat ellenőrizhetjük:

  • A ns1 javította a nonsense függvény 2. paraméterét, így most van egy [number,boolean] - string függvényünk.
  • ns3 javítja az összes paramétert, így az eredmény egy sima string

Következtetés

Ez egy példa volt az összetett gépelésre; Rekurziót kellett használnunk a ciklusok helyett, háromtagú operátorokat az alternatív struktúrák helyett, és egy típust kellett létrehoznunk az érték visszaadása helyett. Azt is láttuk, hogy a TypeScriptnek még ennyi munka mellett is gondjai lehetnek a típusok meghatározásával, de van kiút.

Eredetileg a https://blog.openreplay.com címen tették közzé