javascript의 Array에 내장된 함수 중 reduce가 제일 손이 안갔다. 어떤 기능에선 reduce를 쓰면 될것같은데 설명을 찾아보니 어렵기도 하고 대체 왜이렇게 만든걸까 싶어서 제쳐두고 forEach등을 사용해서 해당기능을 만들어서 쓰곤 했다. 그래도 있는데 한번 써봐야하지않을까 싶어서 이번기회에 한번 사용법도 제대로 알아보고 정리해보려고 한다.
reduce의 인자에는 콜백함수와 optional한 initialValue가 있다.
아래에 나올 오류가 생길 수 있기때문에 특별한 상황이 아니라면 initialValue를 함께 제공하는것이 안전하다고 한다.
reduce 내부에 들어가는 콜백함수를 reducer라고 한다. (물론 리덕스의 그 reducer와는 다르다)
reducer 내부의 인자는 총 네개가 있다.각각 accumulator, currentValue, currentIndex, array이다. forEach 등 다른함수와 비슷하게 현재 값, 현재 인덱스, 원본 배열이 있지만 제일앞에 누산기(accumulator, acc라고도 쓴다)가 들어간다. 이것이 reduce의 핵심이다. 리듀서 함수의 반환 값은 누산기에 할당되고, 이 누산기는 순회 중 계속 유지되어 최종 결과는 이 누산기의 값이 반환되게 된다.
기본 동작은 배열을 순환하면서 콜백함수를 실행한다. 이 때 콜백함수의 반환값을 accumulator에 저장하며 최종적으로 이 accumulator의 값을 반환하게 된다.
이 때, 처음 시작에 있어서 initialValue가 주어졌을때와 그렇지 않을때로 크게 달라진다.
initialValue가 주어진 경우
initialValue가 주어지면, 최초에 해당 값을 accumulator로 사용하며 array의 맨 첫번째(0번인덱스)부터 순회하며 콜백함수를 실행한다. 매번 실행된 콜백함수의 반환값은 accumulator에 저장되고 다음 콜백함수로 넘어간다. 그리고 최종적으로 순회가 끝난 후accumulator의 값이 반환된다.
initialValue가 주어지지 않았을 경우
이 때는 accumulator값이 없으니 콜백 값을 누적해나갈 수가 없다. 그래서 이 때는 배열의 맨 첫번째 값을 accumulator값으로 하고, 두번째 인덱스부터 순회하며 콜백을 실행한다. 그러니까 첫번째를 건너뛰게 되는것이다. 이러한 이유로 initialValue가 있거나 없는 경우에 따라 순회가 달라진다. 이 부분이 조금 어려운 것 같다.
배열이 빈 배열인 경우
빈 배열일 때는 initialValue 값을 그대로 반환한다. 이때 initialValue가 없다면? TypeError가 발생한다. initialValue가 주어지지 않으면 첫번째 값을 대신 사용하는데 그것도 빈 배열이라 없으니 error를 발생시키는 것 같다. 그러니 빈 배열일 경우도 생각하여 initialValue를 함께 제공하는것이 안전할 것이다.
배열에 값이 하나만 있는 경우
값이 하나만 있을 때 initialValue가 있다면 initialValue를 accumulator값으로 사용하여 첫번째 값을 순회한 후 그 값을 반환하게 될것이다. 하지만, 값이 하나만 있는데 initialValue가 주어지지 않는다면 첫번째 값을 initialValue로 사용하며 건너뛰기때문에 콜백함수는 실행되지않고 그 첫번째 값을 바로 반환하게 된다.
빈 배열일때 initialValue가 있거나, initalValue는 없는데 배열값이 하나뿐인 경우에는 해당 값을 그대로 반환한다. 어느쪽이든 값이 하나만 주어진다면 그걸 그대로 반환하는 것. 둘 다 값이 없다면 TypeError가 발생한다.
사용법은 accumulator를 활용해서 계속해서 값을 누적시켜나가는 모든 계산이 가능하며, 배열 안의 값이 배열로 이루어져있다면 이들을 이어붙이는것도 가능하고, 원하는 값만 뽑아서 카운트하거나 계산하는 것도 가능하다. 오브젝트와 배열을 사용하여 내부 값을 통해 계산하여 분리한 후 다시 배열로 만들수도 있다.
많이 보이는 몇가지 예시를 가져와봤다.
const sum = [1,2,3,4,5].reduce(function(acc, value){
return acc + value;
}, 0);
// sum : 15
const initialValue = 0;
const sum = [{x:1}, {x:2}, {x:3}].reduce(function(acc, value){
return acc + value.x;
})
// sum : 6
여기서 만약 initialValue를 주지 않고 실행하게 되면, 첫번째 값인 {x:1}이 그대로 acc로 사용되고, 거기에 2, 3을 더하게 되어 오브젝트에 값이 더해진 이상한 string이 반환되고 원하는 동작이 일어나지 않는다. 그러니 이런 형태에선 꼭 initialValue를 주도록 하자.
const flattened = [[0,1],[2,3],[4,5]].reduce(function(acc, value){
return acc.concat(value);
},[])
// flattened : [0, 1, 2, 3, 4, 5]
일단은 function으로 작성했지만 화살표함수도 된다!
forEach, map과 같이 익숙한 것만 사용하곤 했는데 배열을 사용하면서 reduce가 어울릴만한 곳이 꽤 있었다. 익숙해진다면 꽤나 유용하게 쓸 수 있을듯하다.