프론트엔드 개발을 하다 보면 서버로부터 받은 방대한 리스트 데이터를 특정 기준(카테고리, 날짜 등)에 따라 분류하여 화면에 보여주어야 할 때가 많습니다. 이때 가장 흔히 사용하는 방식이 Array.prototype.filter() 메서드입니다.
하지만 filter는 호출될 때마다 배열 전체를 순회하므로, 여러 카테고리를 처리하기 위해 filter를 반복해서 사용하면 배열의 크기가 커질수록 성능 부담이 **O(n * k) (n: 배열 크기, k: 카테고리 수)로 선형적으로 증가하게 됩니다. 단순히 특정 카테고리의 데이터를 찾기 위해 매번 전체 배열을 뒤지는 것은 비효율적입니다.
이 문제를 해결하는 가장 좋은 방법은 데이터를 미리 그룹화(Grouping) 해두는 것입니다. 배열을 단 한 번만 순회하여 객체(Object)나 맵(Map) 형태로 데이터를 재구성해두면, 이후에는 키(Key)를 통해 데이터에 직접 접근할 수 있어 접근 속도가 O(1)로 비약적으로 향상됩니다.
기존의 filter 방식과 groupBy 방식의 성능 차이를 비교해 보겠습니다.
데이터 그룹화의 핵심은 배열을 순회하며 각 아이템의 특정 속성을 키로 사용하는 '해시 맵' 형태의 객체를 만드는 것입니다.
filter(category === 'X') 실행 -> 전체 순회.reduce 실행 -> 결과 객체 생성 -> result['X']로 즉시 접근.TypeScript의 제네릭을 활용하여 재사용성이 높고 타입 안정성이 보장되는 groupBy 유틸리티 함수를 구현해 보겠습니다.
const DUMMY = [ { category: "food", name: "짜장면" }, { category: "electronic", name: "TV" }, { category: "food", name: "떡볶이" }, { category: "clothes", name: "tshirt" }, ]; /</strong> * 배열을 특정 키를 기준으로 그룹화하는 유틸리티 함수 */ const groupBy = <T extends Record<string, any>>( array: T[], keyForGrouping: keyof T ): Record<string, T[]> => { return array.reduce<Record<string, T[]>>((group, item) => { const valueForGrouping = item[keyForGrouping]; // 그룹이 존재하지 않으면 빈 배열로 초기화 후 아이템 추가 return { ...group, [valueForGrouping]: group[valueForGrouping] ? [...group[valueForGrouping], item] : [item] }; }, {}); }; // 사용 예시 const groupedData = groupBy(DUMMY, 'category'); console.log(groupedData.food); // [{ category: "food", name: "짜장면" }, ...]
위의 구현에서 ...group (Spread Operator)을 reduce 내부에서 사용하면 매 순회마다 새로운 객체를 생성하므로, 대량의 데이터 처리 시 가비지 컬렉션 부담이 커질 수 있습니다. 실무에서는 다음과 같이 Mutable하게 작성하는 것이 성능상 유리합니다.
const groupByOptimized = <T extends Record<string, any>>( array: T[], keyForGrouping: keyof T ): Record<string, T[]> => { return array.reduce<Record<string, T[]>>((group, item) => { const key = String(item[keyForGrouping]); if (!group[key]) { group[key] = []; } group[key].push(item); return group; }, {}); };
TypeScript의 강력한 타입 시스템과 결합된 groupBy 함수는 데이터 중심의 웹 애플리케이션에서 필수적인 도구입니다. 최근 JavaScript 표준(ES2024)에는 Object.groupBy()와 Map.groupBy()가 공식적으로 추가되었으니, 최신 환경이라면 이를 활용하는 것도 좋은 방법입니다.
참고 자료: