reduce를 쓰면서 유용했던 것 몇가지 정리

in javascript •  7 years ago  (edited)

reduce 정의

배열의 빌트인 함수로서 시그니처는 다음과 같다.

// Array.prototype.reduce
arr.reduce(iterator [, initialValue]);

iterator 는 다음과 같은 인자를 받는다.

accumulator(
    accumulated, // 콜백에서 반환한 누적 value
    currentValue, // 현재 value
    currentIndex, // 현재 index
    context // reduce 를 호출한 현재 배열
)

활용 이모저모

reduce 는 보통 배열의 요소들을 하나의 결과로 합치는 용도로 많이 쓰게 된다.

실제 구글링할 경우 제일 많이 나오는 예제는 요소들의 합계를 구하는 용도로 많이 나온다.

const arr1 = [ 1, 2, 3, 4, 5 ];
arr1.reduce((a, b) => a + b); // 15

이런 간단한 용도 말고 좀더 활용을 한다면 타 배열의 함수 대부분을 대체할 수 있는 막강한 힘이 있다.

filter 를 reduce 로 구현

const isEven = val => val%2 === 0;
[ 2, 4, 5, 6, 8 ].filter(isEven)

[ 2, 4, 5, 6, 8 ].reduce(
    (acc, val) => isEven(val) ? acc.push(val) : acc, acc, []);

이 경우에는 큰 장점이 없어보인다.

그렇지만 여기서 단순 필터링이 아닌 여러 조건별로 값을 변환하거나 수집하는 작업을 동시에 할 경우에는 reduce 함수가 꽤 가독성이 있고 성능도 좋개 구현할 수 있다.

아래 필터링과 값 매핑을 filter + map 하는 예제를 보자

// 첫번째 순회로 값을 필터링하고 나중에 곱하게 된다. 즉 두번의 루프를 돈다.
// 엔진별로 최적화(lazy evaluation) 를 할 수 있지만 그건 고려하지 않기로...
[ 2, 4, 5, 6, 8 ]
    .filter(val => val%2 === 0)
    .map(val => val * 10);

// 루프 한번에 filter, map 완료.
[ 2, 4, 5, 6, 8 ].reduce((acc, val) => {
    if(val%2 === 0) {
        acc.push(val * 10)
    }
    return acc;
}, []);

배열의 크기가 길수록 유리할 것이다.
callback 함수를 적절한 이름과 함께 분리해두면 더욱 가독성이 좋고 재활용의 여지도 생긴다.

타입변환

대상 배열의 타입을 오브젝트나 다른 타입으로 바꿀 수 있는 것도 reduce 의 특권이다.
find, filter, some 등의 메서드도 배열에서 다른 값을 반환하지만 반환 형식이 고정되어 있다.

그에 반해 reduce 는 자신이 선택한 타입을 반환하게 할 수 있다. 보통은 Object to Object, Array to Object 등으로 형식 변환시 주로 쓰게 된다.

다음 코드는 문자열 배열을 짝수 페어끼리 키와 값으로 매핑하는 예제이다.

const saids = [ 'ko', '안녕', 'en', 'hello', 'ja', 'こんにちは' ];

saids.reduce((acc, val, index, orig) => 
    (index === 0 || index%2 === 0) ? 
        acc[val] = orig[index + 1] : acc, acc
}, {});

flatten

java 언어를 아시는 분이라면 stream 등으로 중첩된 스트림을 하나의 스트림으로 펴는 메서드인 flatMap 을 알 것이다.

js 에는 현재 공식으로 추가가 되어있지 않기에 직업 구현해야 한다. 이때 reduce 를 쓰면 간단히 구현된다.

[ [9,2], [8,7] ].reduce((acc, val) => [ ...acc, ...val ], [])

flatMap 은 현재 draft 상태인듯 하다. (https://tc39.github.io/proposal-flatMap/)

함수 결합

reduce 는 부분적용 혹은 커링 작업에도 큰 도움을 준다.

예를 들면 React(https://reactjs.org/) 를 쓰다보면 HOC(https://reactjs.org/docs/higher-order-components.html) 를 자주 쓰게 되는데 이런 경우 인라인으로 함수를 죽 statement 로 써내려가면 굉장히 보기 싫은 코드가 나온다.

이런 경우 reduce 로 미리 그런 함수를 합쳐둘 수 있다.

import React from 'react'
import { connect } from 'react-redux'
import injectStyles from 'jss'
import withHistory from 'react-router-dom'
import injectCopyButton from 'hoc/injectCopy'
import { selector, dispatcher }

const styles = {
    // ... some implements
}

const MyComponent = props => (
    // ... some implements
);

// 파동권 콜백 코드
// 많아질수록 더 괴로워질 것이다.
export default connect(selector, dispatcher)(
    injectStyles(styles)(
        withHistory(
            injectCopyButton(
                MyComponent
            )
        )
    )
);

reduce 를 쓰면 이렇게 가독성을 높여볼 수 있다.

var hocs = () => {
    return Component => {
        return [
            fn(1), fn(10),
        ].reduce((prev, fn) => fn(prev),Component)
    }
};

const hocList = [
    connect(selector, dispatcher),
    injectStyles(styles),
    withHistory,
    injectCopyButton,
]

// HOC 배열을 주게 되면 제일 마지막 요소부터 적용해나가야 하기에 반전시킨다.
// 혹은 reduceRight 를 쓰는 방법도 있다.
// reduceRight 는 요소의 마지막부터 순회를 한다.
export default hocList.reverse().reduce((prev, hoc) => hoc(prev), Component);

조금 더 추상화하면 다음 함수를 만들 수 있다.

const compose = (...funcs) => val => funcs.reduce((pr, fn) => fn(pr), val);

forEach 로는 더 디테일하게 할수있는데?

reduce 보다 더 raw 한 성격의 함수로는 forEach(https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach) 가 있다.

하지만 forEachreduce 와 달리 반환값이 없기에 chaining method 방식으로는 쓸수 없어 활용성이 떨어지며, 보통 initialValue 가 별도의 공간이 선언하게 되어 변수 이름을 차지하게 되기에 reduce 가 사용성면에서 훨씬 유리하다.

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!
Sort Order:  

@rouka 님 오셨군요 ㅋㅋㅋㅋ
가입인사글 부터 남기세요 !! kr-join kr-newbie 태그 ㄱㄱ

Congratulations @rouka! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 1 year!

Click here to view your Board

Do not miss the last post from @steemitboard:

Carnival Challenge - Collect badge and win 5 STEEM
Vote for @Steemitboard as a witness and get one more award and increased upvotes!

Congratulations @rouka! You received a personal award!

Happy Birthday! - You are on the Steem blockchain for 2 years!

You can view your badges on your Steem Board and compare to others on the Steem Ranking

Do not miss the last post from @steemitboard:

Use your witness votes and get the Community Badge
Vote for @Steemitboard as a witness to get one more award and increased upvotes!