Kód bázis

Funkcionális programozási módszerek JavaScriptben

A JS nyelvet onnantól kezdve tudod nagyon hatékonyan használni, amikor rájössz, hogy a lelke mélyén egy funkcionális nyelv.

Ebben a cikkben hasznos eszközöket és módszereket mutatok, amelyek nagyban kiaknázzák a javascript funkcionális oldalát.


 

Objektum aggregálása reduce function-nel

 

Tegyük fel hogy egy étterem web app-ját építed és az itt látható adatsettel dolgozol:

 

const termekek = [

{ id: "102", name: "DELUXE COOKED HAM", price: 5.15 },

{ id: "896", name: "JALAPENO SAUCE", price: 2.39 },

{ id: "358", name: "BEEF BOLOGNA", price: 4 },

{ id: "159", name: "DELUXE LOW-SODIUM COOKED HAM", price: 5.15 },

{ id: "105", name: "DELUXE LOW-SODIUM WHOLE HAM", price: 5.15 },

{ id: "156", name: "SMOKED VIRGINA HAM", price: 5.15 },

{ id: "150", name: "HONEY MAPLE HAM", price: 5.15 },

{ id: "151", name: "HONEY MAPLE HAM 1/2", price: 5.15 },

{ id: "166", name: "BAVARIAN SMOKED HAM", price: 5.15 },

{ id: "11005", name: "BLACK FOREST BEECHWOOD HAM L/S", price: 5.15 },

{ id: "158", name: "TAVERN SMOKED HAM", price: 5.15 },

// (további 4321 elem...)

]

 

Az adatset egy tömbből áll, amiben objektumok sorakoznak és minden objektumnak van egy id, name és price kulcsa.
 

Sokszor előfordul, hogy a program működése során a beolvasott adatból, egyedi azonosító alapján kell kikeresni elemeket.

Ha a tömbödben sok elem van és az app futása során sok ilyen id alapú keresés megtörténhet, akkor az adatokat nem előnyős tömbben tartani, mert minden keresés alkalmával egyesével át kell nézni az elemeket, hogy az adott elem id-ja megegyezik-e a keresett id-val.

 

Keresés tömbben:

const talalat = termekek.find((termek) => termek.id === "166");

 

 

Emiatt szokás rögtön az adat beolvasása után, a tömb adatot átalakítani objektummá úgy, hogy minden egyes elem kapcsán az elem id-ja kerül be kulcsnak és maga az elem kerül be értéknek.

 

// termekekKulccsal:

 

{

"102": { id: "102", name: "DELUXE COOKED HAM", price: 5.15 },

"896": { id: "896", name: "JALAPENO SAUCE", price: 2.39 },

"358": { id: "358", name: "BEEF BOLOGNA", price: 4 },

"159": { id: "159", name: "DELUXE LOW-SODIUM COOKED HAM", price: 5.15 },

"105": { id: "105", name: "DELUXE LOW-SODIUM WHOLE HAM", price: 5.15 },

"156": { id: "156", name: "SMOKED VIRGINA HAM", price: 5.15 },

"150": { id: "150", name: "HONEY MAPLE HAM", price: 5.15 },

"151": { id: "151", name: "HONEY MAPLE HAM 1/2", price: 5.15 },

"166": { id: "166", name: "BAVARIAN SMOKED HAM", price: 5.15 },

"11005": { id: "11005", name: "BLACK FOREST BEECHWOOD HAM L/S", price: 5.15 },

"158": { id: "158", name: "TAVERN SMOKED HAM", price: 5.15 },

// (további 4321 elem...)

}

 

Ez azért hasznos, mert egyetlen elem id alapú kikeresése nagyon olcsó műveletté válik, ugyanis ahelyett, hogy egyesével végigiterálnál a tömb elemein és összehasonlítgatnád az id-t hogy egyezik-e, ahelyett egy sima kulcs alapú kikéréssel rögtön hozzájuthatsz a kívánt elemhez.

 

Elem kikeresése objektumból:
const talalat = termekekKulccsal["166"];

 

Az objektumot úgy állíthatjuk fel a tömbből, hogy ráhívjuk a tömbre a reduce függvényt és összeaggregáljuk az objektumot.

 

const termekekKulccsal = termekek

.reduce((aggregalt, termek) => ({ ...aggregalt, [termek.id]: termek }), {});

 

A reduce függvénynek tulajdonképpen azt mondjuk, hogy 

  1. Induljunk ki egy üres objektumból.
  2. Vegyük sorra a tömb összes elemét.
  3. A reduce function mindig a kezünkbe fogja dobni az addig összeaggregált értéket és a tömb soron következő értékét.
  4. Mi dönthetünk, hogy mi legyen ebből a kettőből.
  5. Tehát megadhatjuk azt, hogy térjen vissza egy objektum, ami az addig összeaggregált kulcs érték párokból, plusz a soron következő elem kapcsán egy új kulcs érték párból áll.
  6. A operátor neve spread operátor, ilyenkor a létrehozott új objektumba beleírod egy már létező objektum összes kulcs érték párját.
  7. Az új kulcs a soron következő elem id-ja lesz, az érték pedig maga a teljes elem.

 

Az így megkapott objektumból már nagyon könnyű id alapon elemet kikérni, de egyúttal elvesztett pár fontos tulajdonságot.

 

Miután egy objektum van a kezünkben, az adat további átalakítására nem használhatunk olyan tömbfüggvényeket, mint map, filter, reduce, sort stb.

 

 

Adat további átalakítása funkcionális módszerekkel

 

Tegyük fel például, hogy elő akarunk állítani egy új adatsetet, amiben csak azon elemek vannak jelen, amelyek 50 dollárnál drágábbak,

és ezen termékeknek az árait módosítani is akarjuk, adni akarunk rájuk 10% kedvezményt.

 

Ehhez át kell alakítanunk az objektumot tömbbé úgy, hogy az objektum kulcs értékpárjaiból két elemű tömböket csinálunk.

 

// termekekKulccsal:

 

[

["102", { id: "102", name: "DELUXE COOKED HAM", price: 5.15 }],

["896", { id: "896", name: "JALAPENO SAUCE", price: 2.39 }],

["358", { id: "358", name: "BEEF BOLOGNA", price: 4 }],

["159", { id: "159", name: "DELUXE LOW-SODIUM COOKED HAM", price: 5.15 }],

["105", { id: "105", name: "DELUXE LOW-SODIUM WHOLE HAM", price: 5.15 }],

["156", { id: "156", name: "SMOKED VIRGINA HAM", price: 5.15 }],

["150", { id: "150", name: "HONEY MAPLE HAM", price: 5.15 }],

["151", { id: "151", name: "HONEY MAPLE HAM 1/2", price: 5.15 }],

["166", { id: "166", name: "BAVARIAN SMOKED HAM", price: 5.15 }],

["11005", { id: "11005", name: "BLACK FOREST BEECHWOOD HAM L/S", price: 5.15 }],

["158", { id: "158", name: "TAVERN SMOKED HAM", price: 5.15 }],

// (további 4321 elem...)

]

 

 

Az átalakítást a következő módon lehet megtenni:

 

const kedvezmenyesTermekek = Object.entries(termekekKulccsal)

.filter((idTermekPar) => idTermekPar[1].price > 50)

.map((idTermekPar) => [idTermekPar[0], { ...idTermekPar[1], price: idTermekPar[1].price * 0.9 }]);

 

 

Innentől kezdve rendelkezésre állnak az array method-ok, tehát mappelhetünk és filterezhetünk.

Bár az átalakítást elvégeztük, a kód nehezen átlátható, mert csak index alapú kikéréssel tudunk az id-hoz és az értékhez hozzájutni.

A kód átláthatóságán javíthatunk array destructuring-gal beköthetjük a kulcsértékpár értékeit általunk elnevezett paraméterekbe:

 

const kedvezmenyesTermekek = Object.entries(termekekKulccsal)

.filter(([id, termek]) => termek.price > 50)

.map(([id, termek]) => [id, { ...termek, price: termek.price * 0.9 }]);

 

 

Ha készen vagyunk, visszaalakíthatjuk a tömböt objektummá, reduce function-nel.

 

const kedvezmenyesTermekek = Object.entries(termekekKulccsal)

.filter(([id, termek]) => termek.price > 50)

.map(([id, termek]) => [id, { ...termek, price: termek.price * 0.9 }])

.reduce((aggregalt , [id, termek]) => ({...aggregalt, [id]: termek }), {});

 

 

A reduce function helyett használhatunk egy egyszerűbb módszert is, a dedikáltan ezt a feladatot elvégző Object.fromEntries methodot, ami egy az egyben ugyanezt csinálja:

 

const kedvezmenyesTermekek = Object.fromEntries(

Object.entries(termekekKulccsal)

.filter(([id, termek]) => termek.price > 50)

.map(([id, termek]) [id, { ...termek, price: termek.price * 0.9 }])

);

 

 

Miért volt mindez funkcionális?

  1. Azért mert az adat átalakítására magasabb szintű függvényeket használtunk (map, filter, reduce).
  2. Az adatot nem manipuláltuk közvetlenül a memóriában, hanem a régiből előállítottunk újat.
  3. Az átalakítást pure function-ök végezték, amik az input adatból leképezték az output adatot, anélkül, hogy a környezetükkel bármi kölcsönhatásba léptek volna.
  4. A kód nagyon tömör, újrafelhasználható darabokból épül fel és kevesebb a mozgó alkatrészből áll.

 

Nézd meg a teljes videót: