Den mächtigen Reducer verstehen

Avatar of Sarah Drasner
Sarah Drasner am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Ich habe kürzlich jemanden betreut, der Schwierigkeiten mit der .reduce()-Methode in JavaScript hatte. Nämlich, wie man von diesem

const nums = [1, 2, 3]
let value = 0

for (let i = 0; i < nums.length; i++) {
  value += nums[i]
}

...zu diesem:

const nums = [1, 2, 3]
const value = nums.reduce((ac, next) => ac + next, 0)

Sie sind funktional äquivalent und summieren beide alle Zahlen im Array auf, aber es gibt einen kleinen Paradigmenwechsel zwischen ihnen. Betrachten wir die Reducer für einen Moment, denn sie sind mächtig und wichtig für Ihre Programmierung. Es gibt buchstäblich Hunderte von anderen Artikeln über Reducer, und ich werde einige meiner Favoriten am Ende verlinken.

Was ist ein Reducer?

Das Erste und Wichtigste, was man über einen Reducer verstehen muss, ist, dass er immer nur einen Wert zurückgibt. Die Aufgabe eines Reducers ist es zu reduzieren. Dieser eine Wert kann eine Zahl, ein String, ein Array oder ein Objekt sein, aber es wird immer nur einer sein. Reducer sind für viele Dinge wirklich großartig, aber sie sind besonders nützlich, um eine gewisse Logik auf eine Gruppe von Werten anzuwenden und mit einem anderen einzelnen Ergebnis zu enden.

Das ist das andere, was man erwähnen muss: Reducer verändern per Natur aus nicht Ihren Anfangswert; vielmehr geben sie etwas anderes zurück. Gehen wir das erste Beispiel durch, damit Sie sehen können, was hier passiert. Das Video unten erklärt es

Es mag hilfreich sein, das Video anzusehen, um zu sehen, wie der Ablauf erfolgt, aber hier ist der Code, den wir uns ansehen

const nums = [1, 2, 3]
let value = 0

for (let i = 0; i < nums.length; i++) {
  value += nums[i]
}

Wir haben unser Array (1, 2, 3) und den ersten Wert, zu dem jede Zahl im Array addiert wird (0). Wir gehen die Menge des Arrays durch und addieren sie zum Anfangswert.

Versuchen wir das mal etwas anders

const nums = [1, 2, 3]
const initialValue = 0

const reducer = function (acc, item) { 
  return acc + item
}

const total = nums.reduce(reducer, initialValue)

Jetzt haben wir dasselbe Array, aber diesmal verändern wir den ersten Wert nicht. Stattdessen haben wir einen initialValue, der nur am Anfang verwendet wird. Als Nächstes können wir eine Funktion erstellen, die einen Akkumulator und ein Element aufnimmt. Der Akkumulator ist der gesammelte Wert, der im letzten Aufruf zurückgegeben wird und der Funktion mitteilt, was der nächste Wert sein wird, zu dem addiert wird. In diesem Fall der Addition können Sie es sich wie einen Schneeball vorstellen, der einen Berg hinunterrollt und jeden Wert auf seinem Weg aufnimmt, während er mit jedem aufgenommenen Wert größer wird.

snowball accumulating values

Wir verwenden .reduce(), um die Funktion anzuwenden und von diesem Anfangswert aus zu starten. Dies kann mit einer Pfeilfunktion verkürzt werden

const nums = [1, 2, 3]
const initialValue = 0

const reducer = (acc, item) => { 
  return acc + item
}

const total = nums.reduce(reducer, initialValue)

Und dann noch weiter verkürzt! Implizite Rückgaben sind der Hit!

const nums = [1, 2, 3]
const initialValue = 0

const reducer = (acc, item) => acc + item

const total = nums.reduce(reducer, initialValue)

Jetzt können wir die Funktion direkt dort anwenden, wo wir sie aufgerufen haben, und wir können auch diesen Anfangswert direkt hineinlegen!

const nums = [1, 2, 3]

const total = nums.reduce((acc, item) => acc + item,

Ein Akkumulator kann ein einschüchternder Begriff sein, also können Sie ihn sich wie den aktuellen Zustand des Arrays vorstellen, während wir die Logik auf die Aufrufe der Callback-Funktion anwenden.

Der Call Stack

Falls nicht klar ist, was passiert, lassen Sie uns ausgeben, was bei jeder Iteration passiert. Der Reduce-Vorgang verwendet eine Callback-Funktion, die für jedes Element im Array ausgeführt wird. Die folgende Demo hilft, dies klarer zu machen. Ich habe auch ein anderes Array ([1, 3, 6]) verwendet, da die Zahlen gleich dem Index sein könnten, was verwirrend sein könnte.

Siehe den Pen showing acc, item, return von Sarah Drasner (@sdras) auf CodePen.

Wenn wir das ausführen, sehen wir diese Ausgabe in der Konsole

"Acc: 0, Item: 1, Return value: 1"
"Acc: 1, Item: 3, Return value: 4"
"Acc: 4, Item: 6, Return value: 10"

Hier ist eine visuellere Aufschlüsselung:

  1. Es zeigt, dass der Akkumulator mit unserem Anfangswert, 0, beginnt
  2. Dann haben wir das erste Element, das 1 ist, also ist unser Rückgabewert 1 (0 + 1 = 1)
  3. 1 wird bei der nächsten Ausführung zum Akkumulator
  4. Jetzt haben wir 1 als Akkumulator und 3 ist das Element, da es das nächste im Array ist.
  5. Der zurückgegebene Wert wird 4 (1 + 3 = 4)
  6. Das wird wiederum zum Akkumulator und das nächste Element bei der Ausführung ist 6
  7. Das ergibt 10 (4 + 6 = 10) und ist unser Endwert, da 6 die letzte Zahl im Array ist

Einfache Beispiele

Nun, da wir das drauf haben, schauen wir uns einige gängige und nützliche Dinge an, die Reducer tun können.

Wie viele X haben wir?

Nehmen wir an, Sie haben ein Array von Zahlen und möchten ein Objekt zurückgeben, das die Häufigkeit dieser Zahlen im Array meldet. Beachten Sie, dass dies genauso gut für Strings gelten könnte.

const nums = [3, 5, 6, 82, 1, 4, 3, 5, 82]

const result = nums.reduce((tally, amt) => {
  tally[amt] ? tally[amt]++ : tally[amt] = 1
  return tally
}, {})

console.log(result)

Siehe den Pen simplified reduce von Sarah Drasner (@sdras) auf CodePen.

Moment mal, was haben wir da gerade gemacht?

Anfänglich haben wir ein Array und das Objekt, in das wir seine Inhalte legen werden. In unserem Reducer fragen wir: Existiert dieses Element? Wenn ja, lassen Sie es uns inkrementieren. Wenn nicht, fügen Sie es hinzu und setzen Sie es auf 1. Geben Sie am Ende die gezählte Anzahl jedes Elements zurück. Dann führen wir die Reduce-Funktion aus und übergeben sowohl den Reducer als auch den Anfangswert.

Nehmen Sie ein Array und wandeln Sie es in ein Objekt um, das einige Bedingungen anzeigt

Nehmen wir an, wir haben ein Array und möchten ein Objekt basierend auf einer Reihe von Bedingungen erstellen. Reduce kann dafür hervorragend geeignet sein! Hier möchten wir aus jeder Instanz einer Zahl im Array ein Objekt erstellen und sowohl eine ungerade als auch eine gerade Version dieser Zahl anzeigen. Wenn die Zahl bereits gerade oder ungerade ist, dann ist das, was wir im Objekt haben werden.

const nums = [3, 5, 6, 82, 1, 4, 3, 5, 82]

// we're going to make an object from an even and odd
// version of each instance of a number
const result = nums.reduce((acc, item) => {
  acc[item] = {
    odd: item % 2 ? item : item - 1,
    even: item % 2 ? item + 1 : item
  }
  return acc
}, {})

console.log(result)

Siehe den Pen simplified reduce von Sarah Drasner (@sdras) auf CodePen.

Dies gibt die folgende Ausgabe in der Konsole aus

1:{odd: 1, even: 2}
3:{odd: 3, even: 4}
4:{odd: 3, even: 4}
5:{odd: 5, even: 6}
6:{odd: 5, even: 6}
82:{odd: 81, even: 82}

Okay, was passiert also?

Während wir jedes Element im Array durchgehen, erstellen wir eine Eigenschaft für gerade und ungerade, und basierend auf einer Inline-Bedingung mit einem Modulo-Operator speichern wir entweder die Zahl oder inkrementieren sie um 1. Der Modulo-Operator ist dafür sehr gut geeignet, da er schnell auf gerade oder ungerade prüfen kann – wenn sie durch zwei teilbar ist, ist sie gerade, wenn nicht, ist sie ungerade.

Weitere Ressourcen

Oben habe ich auf andere Artikel verwiesen, die nützliche Ressourcen sind, um sich mit der Rolle von Reducern vertraut zu machen. Hier sind einige meiner Favoriten

  • Die MDN-Dokumentation ist dafür wunderbar. Ehrlich gesagt, es ist einer ihrer besten Beiträge, meiner Meinung nach. Sie beschreiben auch etwas detaillierter, was passiert, wenn Sie keinen Anfangswert angeben, was wir in diesem Beitrag nicht behandelt haben.
  • Daniel Shiffman erklärt Dinge immer erstaunlich auf Coding Train.
  • A Drip of JavaScript macht das auch gut.