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)을 참고하면 정확한 타이핑 구조를 익히는 데 도움이 된다.