본문 바로가기
개발적인

Reduce로 코드 클린하게 만들기

by klm hyeon woo 2024. 7. 2.
잠깐 정리를 먼저 해보겠습니다 🫨

· reduce 메소드는 배열의 각 요소에 대해 콜백 함수를 실행하고 하나의 결과 값을 반환합니다.

 

코드리뷰를 받으면서 생각보다 고려해서 작성했던 코드가 reduce를 만나 엄청 간결해지는 과정을 보고, 평소에는 reduce를 사용하는 빈도가 많이 없어서 이번 기회에 한번 복습하고자 이렇게 포스팅을 하게 되었습니다. 이번 포스팅에서는 reduce에 대해 가볍게 알아보고, 어떤 방식으로 코드가 간결해졌는데 리뷰를 해보려고 합니다.

arr.reduce(callback [, initialValue]);

reduce는 빈 요소를 제외하고 배열 내에 존재하는 각 요소에 대해 callback 함수를 한 번씩 실행하는데, 콜백 함수는 다음과 같은 인수를 받습니다.

 

· accumulator : 콜백의 반환 값을 누적합니다. 콜백의 이전 반환값 또는, 콜백의 첫번째 호출이면서 initialValue를 제공한 경우에는 initialValue의 값을 의미합니다.

· currentValue : 처리할 현재 요소를 의미합니다. (배열 인덱스에 따라 초기 값이 달라지기 때문에 currentIndex와 연관이 있어요)

· currentIndex (optional) : 처리할 현재 인덱스를 의미합니다, initialValue를 제공한 경우에는 0, 아니면 1부터 시작합니다.

· array (optional) : reduce를 호출한 배열을 의미합니다.

 

콜백의 최초 호출 때 accumulator과 currentValue는 다음 두 가지 값 중 하나를 가질 수 있습니다. 만약 reduce 함수 호출에서 initialValue를 제공한 경우, accumulatorinitialValue와 같고 currentValue는 배열의 첫 번째 값과 같습니다. initialValue를 제공하지 않았다면, accumulator는 배열의 첫 번째 값과 같고 currentValue는 두 번째와 같습니다.

Reduce의 작동 방식

[0, 1, 2, 3, 4].reduce((acc, cur, idx, arr) => acc + cur);

위와 같은 코드가 있을 때, 콜백은 총 4번 호출이되며 각 호출의 인수와 반환 값은 아래와 같습니다.

callback accumulator currentValue currentIndex array 반환 값
첫 번째 호출 0 1 1 [0, 1, 2, 3, 4] 1
두 번째 호출 1 2 2 [0, 1, 2, 3, 4] 3
세 번째 호출 2 3 3 [0, 1, 2, 3, 4] 6
네 번쨰 호출 3 4 4 [0, 1, 2, 3, 4] 10

reduce의 두 번째 인수로 초기 값을 제공하는 경우에는 다음과 같은 결과 값을 얻을 수 있습니다.

[0, 1, 2, 3, 4].reduce((acc, cur, idx, arr) => acc + cur, 10);
callback accumulator currentValue currentIndex array 반환 값
첫 번째 호출 10 0 0 [0, 1, 2, 3, 4] 10
두 번째 호출 10 1 1 [0, 1, 2, 3, 4] 11
세 번째 호출 11 2 2 [0, 1, 2, 3, 4] 13
네 번쨰 호출 13 3 3 [0, 1, 2, 3, 4] 16
다섯 번째 호출 16 4 4 [0, 1, 2, 3, 4] 20

응용을 한다면 객체 배열의 값 합산도 가능합니다.

let initialValue = 0;
let sum = [{ x: 1}, { x: 2 }, { x: 3}].reduce((acc, cur) => acc + cur.x, initialValue);
console.log(sum) // 6

중첩 배열을 하나의 배열로 만들기 위한 계산을 할 수도 있습니다.

let flattened = [[0, 1], [2, 3], [4, 5]].reduce((acc, cur) => acc.concat(cur));
console.log(flattened) // [0, 1, 2, 3, 4, 5]

속성으로 객체를 분류하는 방법에 대한 예제도 있습니다.

let people = [
	{ name: 'Alice', age: 21 },
    { name: 'Max', age: 20 },
    { name: 'Jane', age: 20 },
];

function groupBy(objArr, property) {
	return objArr.reduce((acc, obj) => {
    	let key = obj[property];
        if (!acc[key]) acc[key] = [];
        acc[key].push(obj);
        return acc;
    }, {});
};

let groupedPeople = groupBy(people, "age");
// {
//   20: [
//     { name: 'Max', age: 20 },
//     { name: 'Jane', age: 20 }
//   ],
//   21: [{ name: 'Alice', age: 21 }]
// }

프로미스 객체를 순차적으로 실행할 수도 있습니다.

function runPromiseInSequence(arr, input) {
  return arr.reduce(
    (promiseChain, currentFunction) => promiseChain.then(currentFunction),
    Promise.resolve(input),
  );
}

// promise function 1
function p1(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 5);
  });
}

// promise function 2
function p2(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 2);
  });
}

// function 3  - will be wrapped in a resolved promise by .then()
function f3(a) {
  return a * 3;
}

// promise function 4
function p4(a) {
  return new Promise((resolve, reject) => {
    resolve(a * 4);
  });
}

const promiseArr = [p1, p2, f3, p4];
runPromiseInSequence(promiseArr, 10).then(console.log); // 1200

Reduce를 통해 변화된 코드

기존 코드의 목적은 검색어를 입력하면 유저 리스트를 줄여주면서 동시에 유저 리스트의 인덱싱을 뽑아 마스킹 리스트의 인덱싱에 대입하여 유저 리스트를 추출해내는 것이었습니다. 그래서 이를 고려한 코드를 먼저 아래와 같이 작성을 해보았습니다.

this.scaledMemberList = this.originalMemberList.map((member, idx) => {
  if (member.memberName === this.search
  || member.memberEmail === this.search
  || member.memberDepartment === this.search) return this.memberList[idx];
  return null;
}).filter((member) => member !== null);

객체에서 검색어와 일치하는 경우에 대한 인덱스를 추출하고, 이를 필터를 통해 멤버 리스트를 추출하는 로직입니다. 여기서 reduce를 이용하여 코드를 조금 수정을 진행한다면 아래와 같이 수정을 진행할 수 있습니다.

this.scaledMemberList = this.originalMemberList.reduce((acc, cur) => {
  if (member.memberName === this.search
  || member.memberEmail === this.search
  || member.memberDepartment === this.search) {
    return [...acc, cur];
  }
  return acc;
}, []);

이를 화살표 함수로 더 줄여서 표현을 한다면 아래와 같이 최종적으로 수정된 모습을 확인할 수 있습니다. 콜백 함수를 진행하면서 일치하는 검색어가 존재한다면 배열에 추가하고, 그게 아니라면 배열 그대로를 리턴하는 형태의 코드로 클린하게 변경된 것을 확인할 수 있습니다.

this.scaledMemberList = this.originalMemberList.reduce((acc, cur) => (
  cur.memberName === this.search
  || cur.memberEmail === this.search
  || cur.memberDepartment === this.search ? [...acc, cur] : acc), []);

레퍼런스

 

Array.prototype.reduce() - JavaScript | MDN

reduce() 메서드는 배열의 각 요소에 대해 주어진 리듀서 (reducer) 함수를 실행하고, 하나의 결과값을 반환합니다.

developer.mozilla.org

 

댓글