본문으로 건너뛰기
yceffort
PostsSeriesTagsAbout🧪 Research
EN

Tweaks

theme
accent palette
film grain
minimal mode
BACK TO INDEX
◆ ESSAY
--min
--year
KOoriginal

mailMail icongithubtwitter
yceffort
•
© 2026
•
https://yceffort.kr
BACK TO INDEX
◆ ESSAY

reduce에 spread 를 쓰면 안되는 이유

avatar
yceffort
2021-06-22 · 4분
4min
2021year
KOoriginal
javascriptweb-performance

자바스크립트 프로젝트를 개발하다보면, 배열을 하나의 객체로 묶어야될 필요성이 생길 때가 있다. 아래 예제를 들어보자.

const items = [
  {name: 'obama', age: 10, country: 'USA'},
  {name: 'trump', age: 14, country: 'USA'},
  {name: 'moon', age: 37, country: 'KOREA'},
  {name: 'clinton', age: 64, country: 'USA'},
  {name: 'bush', age: 49, country: 'USA'},
]

이렇게 배열로 되어있는 것을 이름을 키로 하는 객체로 바꾼다고 한다면, 아마 다들 reduce를 생각할 것이다.

const result = items.reduce((prev, item) => {
  prev[item.name] = {age: item.age, country: item.country}
  return prev
}, {})

// {
//   obama: { age: 10, country: 'USA' },
//   trump: { age: 14, country: 'USA' },
//   moon: { age: 37, country: 'KOREA' },
//   clinton: { age: 64, country: 'USA' },
//   bush: { age: 49, country: 'USA' }
// }

혹은 for loop를 활용하여 똑같이 할 수 있다.

const result2 = {}
for (let item of items) {
  result2[item] = {age: item.age, country: item.country}
}

둘 중에 뭐가 더 읽기 좋은 코드냐고 묻는다면, 당연히 후자일 것이다. 내가 아무리 reduce를 좋아한다고 해도 그건 반박 불가능한 사실이다. 그러나 요즘 리액트 커뮤니티의 성장으로 인해 함수형 프로그래밍 스타일로 코드를 작성하는 일이 잦아짐에 따라서 reduce를 쓰는일이 많아지고 있다.

그러나 문제는 아래의 스타일이다.

const result3 = items.reduce(
  (prev, item) => ({
    ...prev,
    [item.name]: {age: item.age, country: item.country},
  }),
  {},
)

이제부터 이런 코드를 reduce...spread라고 부르겠다.

이 spread operator는 Object.assign과 유사하게 동작하는데, 소스 객체의 속성을 순환하면서 각 속성을 타켓의 객체에 하나씩 복사해 넣는다. 위 코드의 경우에는, 새로운 객체를 계속해서 복사하는 것이다. 위 코드의 문제는 무엇일까?

위코드의 동작을 살펴본다면, 대충 아래와 같을 것이다.

# 첫번째
result = {}

{...result, 'obama': {age: 10, country: 'USA'}}

# 두번째
result = {'obama': {age: 10, country: 'USA'}}

{...result, 'trump': {age: 14, country: 'USA'}}

# 세번째

result = {'obama': {age: 10, country: 'USA'}, 'trump': {age: 14, country: 'USA'}}

{...result, 'moon': {age: 37, country: 'KOREA'}}

##....

의사 코드이기 때문에 대충 보면 된다.

루프가 반복될때마다, 모든 루프에서 정확히 n회 실행되는 것은 아니지만, 이렇게 될 경우 O(n2)O(n^2)O(n2)라 볼 수 있다.

바벨이나 타입스크립트 트랜스파일러를 사용하면 해당 코드를 어떻게 변환할까?

그전에 먼저, tc39에 나와있는 객체 전개 연산자의 스펙을 살펴보자.

let aClone = {...a}
let aClone = Object.assign({}, a)

위 구문도 거의 동일한 작업을 수행하므로, 전개 연산자와 비슷한 시간 복잡성을 가질 것이다. 즉, 새 객체를 생성한다음, 나머지 객체의 키를 새객체에 반복해서 복사하는 것이다.

  • babel transpile
  • typescript

따라서 reduce에서 전개연산자로 합성해서 리턴하는 것은 굉장히 느린 코드라고 볼 수 있다.

여담으로, 이와 관련된 키보드 배틀(?) 이 트위터에서 열린 적이 있다.

https://twitter.com/fildon_dev/status/1396252890721918979

문제의 코드

const users = [
  {id: 1, name: 'Tony Stark', active: false},
  {id: 2, name: 'Bruce Banner', active: true},
  {id: 3, name: 'Natasha Romanoff', active: false},
  {id: 4, name: 'Chris Evans', active: true},
  {id: 5, name: 'Chris Hemsworth', active: false},
  {id: 6, name: 'Clark Gregg', active: false},
]

// good?
const result1 = users.reduce((acc, curr) => {
  if (curr.active) {
    return acc
  }
  return {...acc, [curr.id]: curr.name}
})

// bad?
const result2 = users
  .filter((user) => !user.active)
  .map((user = (user) => ({[user.id]: user.name})))
  .reduce(Object.assign, {})

그래서 뭐가 더 성능이 좋고 빠를까? 저 두개가 최선의 방법일까? 만약에 나라면,,,

🤔

관련 글

  • #javascript#frontend#web-performance

    IntersectionObserver 싱글톤 패턴과 WeakMap으로 메모리 누수 방지하기

    수백 개의 요소를 효율적으로 관찰하면서 메모리 누수도 방지하는 방법

    2026-01-17·20분
  • #javascript#web-performance

    거대한 JSON 응답을 효율적으로 처리하는 방법

    JSON.parse()가 버거워할 때 살아남는 법

    2026-01-11·29분
  • #react#javascript#web-performance

    React의 "서버로 회귀"는 역사의 반복인가?

    RSC가 PHP/JSP 시절로의 회귀인지, 아니면 나선형 발전일까? 아닐까? 뭘까

    2026-01-04·37분
  • ◆ 웹 서비스 성능 분석
    #web-performance#javascript#nextjs

    웹 서비스 성능 분석 (2)

    관심 가져주셔서 감사합니다. 🙇🏻‍♂️

    2025-05-12·47분

새 글을 놓치고 싶지 않으시다면 RSS로 구독해 주세요.

RSS 구독 →

yceffort — 프론트엔드 엔지니어입니다. 발표·기술 자문·기고 문의는 이곳에서 받고 있습니다.

← Back to the blogIssue on GitHub →