reduce 구문
array.reduce((accumulator, currentValue, index, array) => {
// 실행할 코드
return newAccumulator; // 누적된 값을 반환
}, initialValue);
특징 : reduce() 는 배열을 순차적으로 처리한다.
매개변수 설명:
- accumulator: 누적 값. 첫 번째 호출에서는 initialValue로 설정되고, 이후에는 이전 reduce() 호출의 누적된 반환값이 갱신(update) 됩니다.
- currentValue: 현재 처리 중인 배열의 요소입니다.
- index (선택적): 현재 처리 중인 요소의 인덱스입니다.
- array (선택적): reduce()를 호출한 원본 배열입니다.
- initialValue: 초기값. accumulator의 초기값을 설정합니다. 이 값을 지정하지 않으면, 배열의 첫 번째 요소가 accumulator로 사용됩니다.
ex) 배열의 합을 구하는 reduce()
const numbers = [1, 2, 3, 4, 5];
const sum = numbers.reduce((acc, currentValue) => {
return acc + currentValue; // 누적된 값(acc)에 currentValue를 더함
}, 0); // 초기값은 0
console.log(sum); // 출력: 15
동작 과정:
- 초기값은 설정한 0입니다. 즉, acc 변수는 0부터 시작합니다.
- reduce()는 배열 numbers를 순차적으로 처리하면서 각 숫자를 누적하여 합을 계산합니다.
각 반복에서의 acc와 currentValue:
- 첫 번째 숫자 1:
- acc는 0, currentValue는 1.
- acc + currentValue는 0 + 1 = 1. 그래서 누적값은 1이 됩니다.
- 두 번째 숫자 2:
- acc는 1, currentValue는 2. acc은 이전 호출에서 누적된 반환값이 옵니다.
- acc + currentValue는 1 + 2 = 3. 누적값은 3이 됩니다.
- 세 번째 숫자 3:
- acc는 3, currentValue는 3.
- acc + currentValue는 3 + 3 = 6. 누적값은 6이 됩니다.
- 네 번째 숫자 4:
- acc는 6, currentValue는 4.
- acc + currentValue는 6 + 4 = 10. 누적값은 10이 됩니다.
- 다섯 번째 숫자 5:
- acc는 10, currentValue는 5.
- acc + currentValue는 10 + 5 = 15. 누적값은 15가 됩니다.
➡️ 사실 누적값을 구하는 데 reduce()를 사용하지 않고도, 전통적인 반복문 (for, forEach 등)을 사용해서 쉽게 구할 수 있습니다. reduce()는 보다 함수형 프로그래밍 스타일로 데이터를 처리할 때 유용하지만, 반복문을 사용하는 것이 더 직관적일 때도 많습니다.
예를 들어, 배열의 합을 구하는 경우 for 반복문을 사용하는게 일반적임.
예시 1: for문 으로 배열 합 구하기
const numbers = [1, 2, 3, 4, 5];
let sum = 0;
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i]; // 각 요소를 누적해서 더함
}
console.log(sum); // 출력: 15
위 코드에서 for문은 배열을 순차적으로 돌면서 각 요소를 sum 변수에 더하고 있습니다. 이 방식은 매우 직관적이고 기존에 잘 알려진 방법입니다.
예시 2: forEach 로 배열 합 구하기
const numbers = [1, 2, 3, 4, 5];
let sum = 0;
numbers.forEach((number) => {
sum += number; // 각 요소를 누적해서 더함
});
console.log(sum); // 출력: 15
forEach를 사용하면 for문과 마찬가지로 배열의 모든 요소를 순차적으로 처리하면서 누적값을 구합니다. 이런 단순한 처리는 굳이 reduce를 사용하지 않아도 됨을 알 수있습니다.
➡️ 그런데 왜 reduce를 사용할까?
반복문만 사용해도 누적값을 구할 수 있지만, reduce()는 배열을 처리하는 방식을 좀 더 선언적이고 함수형 스타일로 만듭니다. 즉, 배열을 처리하는 로직을 함수처럼 선언적으로 표현할 수 있다는 점에서 코드가 간결하고 재사용성이 높아집니다.
reduce()는 여러 가지 복잡한 누적 작업을 한 줄로 처리할 수 있습니다. 예를 들어, 배열을 합치거나, 객체로 변환하는 등 다양한 복잡한 연산을 간결하게 처리할 수 있습니다.
예시 3: reduce로 객체 변환하기
const fruits = ['apple', 'banana', 'orange','banana'];
const fruitCount = fruits.reduce((acc, fruit) => {
acc[fruit] = (acc[fruit] || 0) + 1; // 기존에 acc 객체에 해당 과일의 카운트가 존재하면 그 값을 사용하고, 없으면 0을 기본값으로 사용
return acc;
}, {}); // 초기값 : 빈 배열
console.log(fruitCount); // 결과 { apple: 1, banana: 2, orange: 1 }
설명 : 과일이름 배열 fruits를 reduce로 과일 이름이 key, 그 개수를 value로 가지는 객체를 만들 수 있습니다.
결과 { apple: 1, banana: 2, orange: 1 }
- acc는 누적값입니다. 이 예제에서는 초기값을 {}로 설정했기에 빈 객체로 초기화됩니다.
- fruit는 fruits 배열의 각 요소, 즉 과일 이름이 하나씩 전달됩니다. -> apple, banana, orange, banana
- reduce()는 배열의 모든 요소를 순차적으로 처리하면서 최종적으로 acc(누적 객체)를 반환합니다.
- acc[fruit]는 현재 fruit(과일)의 key에 해당하는 값을 의미합니다. 예를 들어, 처음에 fruit은 'apple'이므로, acc['apple']이 됩니다.
acc[fruit] = (acc[fruit] || 0) + 1; 구문 분석
- acc[fruit]:
- acc은 누적되는 객체이고, fruit은 배열에서 현재 처리하는 과일의 이름입니다. 예를 들어, 첫 번째 반복에서는 fruit이 'apple'이고, 두 번째 반복에서는 'banana'가 됩니다.
- acc[fruit]는 그 과일 이름을 키로 사용하여 acc 객체에서 해당 과일의 개수를 찾으려고 하는 부분입니다.
- (acc[fruit] || 0):
- acc[fruit]는 현재 acc 객체에 fruit 키가 이미 있으면 그 값을 가져옵니다. 예를 들어, 첫 번째로 'apple'을 만났다면 acc['apple']이 아직 없으므로 undefined입니다.
- || 0 부분은 acc[fruit]이 **undefined**일 경우, 즉 acc 객체에 해당 키가 없으면 0을 반환하는 역할을 합니다. 그래서 새로운 과일을 만났을 때, 그 과일의 카운트를 0부터 시작하게 됩니다.
- 즉, (acc[fruit] || 0)은 현재 과일의 카운트가 있으면 그 값을, 없으면 0을 반환합니다.
- + 1:
- + 1을 통해 이전에 acc[fruit]에 있던 값에 1을 더하여 과일의 개수를 하나 증가시킵니다. 예를 들어, acc[fruit]가 2라면, 2 + 1 = 3이 됩니다. apple 을 한번도 만나지 않았다면 0+1 로 apple : 1 이 되겠쥬
결론
단순히 누적값을 구하는 경우에는 반복문을 사용해도 충분하지만, 좀 더 복잡한 누적 연산이나 배열의 변형 작업이 필요할 때는 reduce()를 활용하는 것이 좋습니다.
실무에서 reduce를 사용한 예제
★ 예제 : 2개씩(쿠폰/포인트) 1raw를 이루는 input 박스에 대한 Validation 적용
[경우 1]

➡️ 쿠폰번호 / 포인트가 한 쌍이 되는데, 쿠폰번호 혹은 포인트 둘 중 하나라도 입력해야 한다.
[경우 2]

➡️ raw 별로 validation 체크가 필요함.
[경우 3]

➡️ raw 당 최소 1건씩 모두 입력했기에 유효함.
위와 같이 유효성 검사를 하고 싶을 때 사용한 소스코드는 다음과 같다.(reduce 사용!)
소스코드
function testRewardInput() {
const inputs = document.querySelectorAll("#rewardArea input"); // 모든 input 선택, rewardArea 태그 하위에 위치한 모든 input 태그에 유효성 검사 ㄱㄱ
/* inputs이 선택한 혜택개수에 따라 동적으로 생성되는 부분이었어서 적용한 부분으로, 생략 가능
if (inputs.length === 0) { // input이 하나도 없으면
alert("입력할 필드가 없습니다.");
return;
}
*/
// input을 2개씩 묶어서 검사하기 위해 배열 변환
const inputPairs = Array.from(inputs).reduce((acc, input, index) => {
if (index % 2 === 0) {
acc.push([input]); // 짝수 번째 input이면 새 배열 추가
} else {
acc[acc.length - 1].push(input); // 홀수 번째 input이면 기존 배열에 추가
}
return acc;
}, []);
let isValid = true; // 기본적으로 유효하다고 가정
inputPairs.forEach(pair => {
const [couponInput, pointInput] = pair;
const coupon = couponInput.value.trim();
const point = pointInput.value.trim();
if (!(coupon || point)) { // 둘 다 비었으면 오류
couponInput.style.border = "2px solid red";
pointInput.style.border = "2px solid red";
isValid = false; // 하나라도 비었으면 isValid를 false로 설정
} else { // 하나라도 입력되었으면 유효
couponInput.style.border = "1px solid #ccc";
pointInput.style.border = "1px solid #ccc";
}
});
// 최종적으로 유효성 검사 결과 출력
if (isValid) {
alert("모든 입력이 유효합니다.");
} else {
alert("입력값에 오류가 있습니다.");
return false; // 유효성 처리
}
}