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 aA
2️⃣ üres, küldje visszaboolean
- ha a
A
első típusa nem definiált 3️⃣ vagy ha aA
első típusa megegyezik aP
első típusával 4️⃣ dobja el aP
első típusát, dobja el aA
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 aP
első típusával) anever
é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ényP
- 3️⃣ Ha a
A
első típusa nem definiált, az eredmény aP
első típusa lesz (mert nem volt megadva), majd aP
ésA
többi típusának feldolgozásának eredménye. - 4️⃣ Ellenkező esetben, ha a
P
első típusa megegyezik aA
első típusával, az eredmény az lesz, amit aP
többi típusának aA
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
ésA
típusai nem egyeznek, akkornever
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ünkP
típusú paramétereket vesz fel, ésR
típusú eredményt ad. - ha a
A
nem üres, akkor kiszámítjuk aP
ésA
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 aR
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 anonsense
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 simastring
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é