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

in #javascript6 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 가 사용성면에서 훨씬 유리하다.

Sort:  

@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!

Coin Marketplace

STEEM 0.31
TRX 0.12
JST 0.034
BTC 64418.55
ETH 3157.64
USDT 1.00
SBD 4.06