iTranslated by AI
TS: Mastering the Array.reduce() Method
Introduction
Have you ever used reduce when manipulating arrays in JavaScript or TypeScript?
You might be someone who thinks, "I've seen it used in core parts of code, but I don't really understand what kind of function it is."
Speaking from my own experience, I have worked on several React and ReactNative development projects.
Sometimes new employees are assigned to these projects, and I teach them starting from the level of "Let's learn JS and TS first."
When it comes to arrays, everyone usually starts with an approach like "looping through with for or forEach to process data," and from there they gradually learn to use map, filter, etc., depending on the purpose. However, most of them get stuck at reduce.
When I wondered why that is, several reasons came to mind, such as "The name reduce is not as intuitive as filter or some" or "It's hard to approach because reduce can do anything."
In short, I think "it's difficult to understand what it's actually doing."
Therefore, in this article, I would like to explain reduce—the most versatile function in the array world—while looking at TypeScript samples.
Before that...
Before explaining reduce, I'd like to introduce some other famous functions.
I will omit forEach, as it is the most fundamental one.
Also, for the following code snippets, we will basically use a type called Student and an array containing three instances of its data as shown below.
It is an object type with name and score properties.
type Student = {
name: string;
score: number;
}
const data: Student[] = [
{ name: 'Taro', score: 75 },
{ name: 'Hanako', score: 62 },
{ name: 'John', score: 59 }
]
Note that I won't be covering every single argument for each function (for example, there is a thisArgs argument to bind this when executing the callback function, but it is omitted here).
map to Create a New Array
map creates a "new array" from the original array.
The values returned within the function passed as the first argument to map are gathered into an array (if nothing is returned, undefined is inserted).
const scoreList: number[] = data.map((d: Student): number => {
return d.score;
});
console.log(scoreList); // --> [75, 62, 59]
filter and find to Get Specific Elements
Both functions extract only the array elements that return true within the function passed as the first argument.
filter returns an array, while find returns a single element.
const under65List: Student[] = data.filter((d: Student): boolean => {
return d.score < 65;
});
console.log(under65List); // --> [{ name: 'Hanako', score: 62 }, { name: 'John', score: 59 }];
const taro: Student = data.find((d: Student): boolean => {
return d.name === 'Taro';
});
console.log(taro); // --> { name: 'Taro', score: 75 }
some and every to Determine if an Array Meets the Target Condition
Both return a boolean value.
By executing the function passed as the first argument on each value in the array, some returns true if "at least one element satisfies the condition," while every returns true if "all elements satisfy the condition."
const someOver70: boolean = data.some((d: Student): boolean => {
return d.score > 70;
});
console.log(someOver70); // --> true
const everyOver70: boolean = data.every((d: Student): boolean => {
return d.score > 70;
});
console.log(everyOver70); // --> false
reduce Returns a New Single Element
Finally, the explanation for reduce.
In terms of usability, I think reduce is quite close to map.
You pass a function that takes at least two arguments as the first argument.
These two arguments are called the "accumulator" and the "current value", respectively.
The accumulator holds either the "initial value" or the "value returned by the previous iteration," and the current value, as the name suggests, holds the "array element currently being processed."
Additionally, the second argument specifies the initial value of the "accumulator."
// Get the sum of everyone's scores
const totalScore: number = data.reduce((acc: number, val: Student): number => {
// acc is the "initial value or previous return value" and val is the "array element"
return acc + val.score;
}, 0);
console.log(totalScore); // --> 196
The reason reduce is called versatile is that all the functions introduced so far can be replaced with reduce.
For example, if you want to get an array of score values like in the map example, you can achieve it as follows:
const scoreList: number[] = data.reduce((acc: number[], val: Student): number[] => {
return [...acc, val.score];
}, []);
console.log(scoreList); // --> [ 75, 62, 59 ]
Similarly, filter, find, some, and every can be replaced as shown below:
const under65List: Student[] = data.reduce((acc: Student[], val: Student): Student[] => {
if(val.score < 65) return [...acc, val];
return acc;
}, []);
console.log(under65List); // --> [{ name: 'Hanako', score: 62 }, { name: 'John', score: 59 }];
const taro: Student | undefined = data.reduce((acc: Student | undefined, val: Student): Student | undefined => {
if(acc) return acc;
return val.name === 'Taro'? val : undefined;
}, undefined);
console.log(taro); // --> { name: 'Taro', score: 75 }
const someOver70: boolean = data.reduce((acc: boolean, val: Student): boolean => {
if(acc) return acc;
return val.score > 70;
}, false);
console.log(someOver70); // --> true
const everyOver70: boolean = data.reduce((acc: boolean, val: Student): boolean => {
if(!acc) return acc;
return val.score > 70;
}, true);
console.log(everyOver70); // --> false
Initial Value
The second argument, the initial value, is optional.
In that case, the accumulator's initial value will be the "element at index 0 of the array," and the current value will start from the "element at index 1 of the array."
Since the loop's starting position changes, the number of iterations decreases by 1 when the initial value is not set compared to when it is.
- Without an initial value
| Iteration | acc | val |
|---|---|---|
| 1 | Element at index 0 | Element at index 1 |
| 2 | return value of the 1st iteration | Element at index 2 |
- With an initial value
| Iteration | acc | val |
|---|---|---|
| 1 | Initial value | Element at index 0 |
| 2 | return value of the 1st iteration | Element at index 1 |
Summary
In this article, I explained reduce based on a TypeScript codebase.
It is a function that people often find difficult to understand because it can do anything, but once you learn and master it, it becomes extremely useful.
Note that this explanation focused on behavior and did not mention performance.
I hope this article helps you in learning reduce and other functions.
Discussion