reduce mal connue, mal aimée
Je travaille beaucoup avec Vue, mais ma constatation est vraie pour tous ces frameworks, toutes ces librairies en vogue du moment :
on est souvent confronté à transformer nos données
On passe des réponses d'api http à notre store, de notre store à nos composants, entre nos composants.....
Je vois beaucoup de code fait avec des while, des for, des i, j, k, des copier coller merdiques... pour transformer les données, et moi j'aime pas.
De façon générale, je trouve que l'API offerte par Array est formidable et pas assez connue, et que combinée aux facilités d'écriture que nous offrent les outils tels que babel, webpack..., on peut quand même sortir de la grosse boucle C++ des familles.
Le forEach, filter et le map sont de plus en plus souvent utilisés, mais il y en a une méthode que je vois très peu utilisée et que je trouve pourtant formidable: reduce
1/ Le code sample dans la doc est certes peu attractif cf https://developer.mozilla.org/fr/doc...x/Array/reduce
Code:
1 2 3 4 5 6 7 8 9 10 11
|
const array1 = [1, 2, 3, 4];
const reducer = (accumulator, currentValue) => accumulator + currentValue;
// 1 + 2 + 3 + 4
console.log(array1.reduce(reducer));
// expected output: 10
// 5 + 1 + 2 + 3 + 4
console.log(array1.reduce(reducer, 5));
// expected output: 15 |
Forcément, on se dit super ca sert à faire des sommes.
Alors oui, avec des exemples comme ca, c'est réducteur, mais il faut bien lire la doc:
Citation:
Syntaxe
Code:
1 2
| arr.reduce(callback)
arr.reduce(callback, valeurInitiale) |
callback
La fonction à exécuter sur chaque valeur de la liste (sauf le premier si aucune valeurInitiale n'est fournie), elle prend quatre arguments en entrée :
accumulateur
La valeur précédemment retournée par le dernier appel du callback, ou valeurInitiale, si elle est fournie (voir ci-après) (c'est la valeur « accumulée » au fur et à mesure des appels
valeurCourante
La valeur de l'élément courant actuellement manipulé dans le tableau.
...blabla
valeurInitialeFacultatif
Une valeur utilisée comme premier argument lors du premier appel de la fonction callback. Si aucune valeur initiale n'est fournie, le premier élément du tableau est utilisé (et la boucle de traitement ne le parcourera pas). Si on appelle reduce() sur un tableau vide sans fournir de valeur initiale, on aura une erreur.
Donc la valeur de départ peut être n'importe quoi, par exemple un objet.
2/ Imaginons cette structure de donnée:
Code:
1 2
|
const shoppingCart = [{id: 1, unitPrice: 6, count: 5, name: 'jambon', ...}, {id: 2, count: 6, name: 'cornichon', unitPrice: 2, ...}, ...]; |
Si je vous demande de récupérer le nombre total d'articles et le prix total, vous faites comment ?
Vous faites plutôt un truc comme ca ?
Code:
1 2 3 4 5 6 7 8 9 10 11
|
const shoppingCart = [{id: 1, unitPrice: 4, count: 5, name: 'jambon', ...}, {id: 2, count: 6, name: 'cornichon', unitPrice: 2}, ...];
const res = {
nbArticles: 0,
totalPrice: 0
};
for (let i=0; i<shoppingCart.length; i++) {
res.nbArticles += shoppingCart[i].count;
res.totalPrice += shoppingCart[i].count * shoppingCart[i].unitPrice
}
console.log(res); |
Qui fait un truc comme ca:
Code:
1 2 3 4 5 6 7 8 9 10 11
|
const shoppingCart = [{id: 1, unitPrice: 4, count: 5, name: 'jambon', ...}, {id: 2, count: 6, name: 'cornichon', unitPrice: 2}, ...];
// renvoyer le nombre total d'articles dans le panier et le prix du panier:
const res = shoppingCart.reduce(
({ nbArticles, totalPrice }, { unitPrice = 0, count = 0 }) => ({
nbArticles: nbArticles + count,
totalPrice: totalPrice + (count * unitPrice)
}),
{nbArticles: 0, totalPrice: 0}
)
console.log(res); |
Je trouve que le type de code avec un reduce est moins risqué, et c'est d'autant plus vrai que les opérations sont complexes.
3/ Changer le type des structures
Un autre cas ou j'utilise reduce, c'est pour passer d'un objet à un tableau ou d'un tableau à un objet :
Code:
1 2 3 4 5 6 7
|
const shoppingCart = [{id: 1, unitPrice: 6, count: 5, name: 'jambon', ...}, {id: 2, count: 6, name: 'cornichon', unitPrice: 2, ...}, ...];
// obtenir un objet shoppingCart dont les clés sont l'id de chaque article dans le panier
const shoppingMap = shopingCart.reduce((mapAcc, item) => ({
...mapAcc,
[item.id]: item
}), {}) |
L'inverse, passer d'un objet à un tableau ce fait naturellement avec les fonctions standard Object.entries, .values, .keys ...
Code:
1 2 3 4
|
return Object.values(shoppingMap);
// si on veut également conserver la clé
return Object.entries(shoppingMap).map(([key, value]) => ({ ...value, key }) |
Et après on peut encore faire d'autres trucs,
...la valeur de départ peut être n'importe quoi, par exemple une promesse.
Le use case est un peu différent, vous avez une liste de trucs, et pour chacun de ces trucs, vous devez charger ces données, mais disons que pour préserver votre serveur, vous décidez de toujours executer en séquentiel le traitement de votre tableau.
reduce est super pour ca:
Code:
1 2 3 4 5 6 7 8 9 10 11
|
// une liste de trucs pour lesquels je dois appliquer sequentiellement une à une tache asynchrone
const things = [0, 1, 2, 3, 'aab', {}, ...];
things.reduce( // on fait un reduce, mais notre valeur de départ est une promesse résolue, donc
(prev, thing) => prev.then( // quand la tache précédente est finie
() => doSomethingWith(thing), // on renvoie une promesse avec l'élément suivant
),
Promise.resolve() // la valeur de départ permet de démarrer le then initial
).then(() => {
// console.log('fini') // quand on a fait la tache asynchrone
}) |
Et vous, vous connaissez reduce ? vous l'utilisez ?