Typescript
reduce 만들기
비숑주인
2025. 4. 30. 16:22
배열의 reduce 메서드는 요소를 누적하여 하나의 값으로 축약할 수 있는 강력한 도구이다. TypeScript에서도 이 메서드의 타입을 정확히 정의하는 것은 꽤 흥미로운 연습이 된다. 이번 글에서는 reduce 메서드를 직접 타이핑하며 초깃값 유무에 따라 타입이 어떻게 달라지는지를 살펴본다.
기본 사용 예시
const r1 = [1, 2, 3].myReduce((a, c) => a + c); // 6
const r2 = [1, 2, 3].myReduce((a, c, i, arr) => a + c, 10); // 16
reduce의 콜백 함수는 총 네 개의 인자를 받는다:
- 누적값 (a)
- 현재값 (c)
- 인덱스 (i)
- 원본 배열 (arr)
초깃값을 제공하지 않으면 첫 번째 요소가 초깃값 역할을 하며, 이로 인해 타입 처리에 차이가 생긴다.
간단한 타입 선언
초기에는 다음과 같이 선언할 수 있다.
interface Array<T> {
myReduce(callback: (a: T, c: T, i: number, arr: T[]) => T, iV?: T): T;
}
하지만 이 타입 선언만으로는 다음과 같은 예시에서 오류가 발생한다.
const r3 = [{ num: 1 }, { num: 2 }].myReduce((a, c) => {
return { ...a, [c.num]: 'hi' };
}, {}); // 오류: '{}' is not assignable to { num: number }
이유는 초깃값의 타입과 누적값 타입이 요소 타입과 다를 수 있기 때문이다.
오버로딩으로 타입 분리
초깃값이 있을 때와 없을 때를 구분해 오버로딩하면 문제를 해결할 수 있다.
interface Array<T> {
myReduce(callback: (a: T, c: T, i: number, arr: T[]) => T): T;
myReduce<S>(callback: (a: S, c: T, i: number, arr: T[]) => S, iV: S): S;
}
이제 다음과 같은 예시도 정상적으로 동작한다.
const r3 = [{ num: 1 }, { num: 2 }, { num: 3 }].myReduce(
(a, c) => ({ ...a, [c.num]: 'hi' }),
{}
); // { 1: 'hi', 2: 'hi', 3: 'hi' }
const r4 = [{ num: 1 }, { num: 2 }, { num: 3 }].myReduce(
(a, c) => a + c.num,
''
); // '123'
S는 초깃값이 있는 경우 사용할 누적 타입이며, 콜백의 누적값, 반환값, 초깃값, 그리고 최종 반환 타입 모두에 사용된다.
타입스크립트 기본 정의와 비교
TypeScript의 표준 라이브러리에서는 reduce가 다음처럼 정의되어 있다.
interface Array<T> {
reduce(callbackfn: (prev: T, curr: T, index: number, array: T[]) => T): T;
reduce(callbackfn: (prev: T, curr: T, index: number, array: T[]) => T, initialValue: T): T;
reduce<U>(callbackfn: (prev: U, curr: T, index: number, array: T[]) => U, initialValue: U): U;
}
이 정의는 우리가 만든 오버로딩과 거의 동일하다. 첫 두 개의 오버로드는 사실상 합쳐도 되며, 세 번째는 초깃값의 타입이 다른 경우를 처리한다.
직접 구현도 가능
타입 선언만 해서는 런타임에서 사용할 수 없다. JavaScript에서도 동작하도록 구현해보자.
Array.prototype.myReduce = function <T, S>(
this: T[],
callback: (a: S, c: T, i: number, arr: T[]) => S,
initialValue?: S
): S {
let hasInitial = arguments.length >= 2;
let acc: any = hasInitial ? initialValue : this[0];
let startIndex = hasInitial ? 0 : 1;
for (let i = startIndex; i < this.length; i++) {
acc = callback(acc, this[i], i, this);
}
return acc;
};
이 구현은 초깃값 유무에 따라 동작 방식을 다르게 처리하며, TypeScript 타입 선언과도 잘 맞는다.
정리
- reduce는 누적값을 생성하는 메서드로, 초깃값 유무에 따라 타입 추론이 달라진다.
- 초깃값이 있을 경우 타입 매개변수 S를 도입해 타입 추론을 유연하게 처리할 수 있다.
- 타입 선언만으로는 충분하지 않으며, 실제 구현도 필요하다.
- TypeScript의 표준 선언(lib.es5.d.ts)을 참고하면 정확한 타이핑 구조를 익히는 데 도움이 된다.