structural sharing이란

hong·2026년 4월 22일

javascript

목록 보기
12/12
post-thumbnail

structural sharing이란

바뀐 부분만 새로 만들고, 안 바뀐 부분은 기존 걸 그대로 재사용하는 것.

예 )

const prev = {
user: { name: '뽀야미', age: 1 },
theme: { mode: 'dark' }
}

여기서 age만 바꾼다고 할 때,

const next = {
user: { name: '뽀야미', age: 2 },
theme: { mode: 'dark' }
}

이렇게 바꾸면 모든 객체를 새로 만든 꼴이다.

반면 structural sharing 방식으로 하는 경우엔,

const next = {
  ...prev,
  user: {
    ...prev.user,
    age: 21
  }
}

이렇게 한다. user는 새로 생성되고, theme은 기존 참고 그대로 유지한다.
즉, 바뀐 부분만 새 객체고 나머지는 기존 참조를 재사용한다.

중요한 이유

  1. 성능 : React에서는 memo, useMemo, useCallback, selector 최적화 같은 곳에서 참조 비교가 중요하다. structural sharing은 안 바뀐 부분의 참조를 유지해서 이런 최적화가 잘 동작하게 도와준다.
  2. 불변성 유지 : 원본 안 건드리고 변경 이력 관리 가능하다.
  3. 메모리 절약 : 전체 복사 안 하고 필요한 부분만 새로 만든다.

중요한 포인트

‘얕은 복사’랑 비슷해 보이는데, 목적이 다르다.
얕은복사는 그냥 복사인 반면, structural sharing은 변경된 경로만 새로 만들기 위한 전략이다.

👩‍💻깊은 복사와 얕은 복사

얕은 복사 : 겉 껍데기만 새로 만들고, 안쪽 객체는 기존 주소를 그대로 공유하는 것.

const prev = {
  user: { name: ‘뽀야미’, age: 1 },
  theme: { mode: 'dark' }
}

const next = { ...prev }

console.log(prev === next) // false
console.log(prev.user === next.user) // true
console.log(prev.theme === next.theme) // true

깊은 복사 : 안쪽에 들어 있는 객체들까지 전부 새로 만드는 것.

const prev = {
  user: { name: ‘뽀야미’, age: 1 },
  theme: { mode: 'dark' }
}

const next = structuredClone(prev)

console.log(prev === next) // false
console.log(prev.user === next.user) // false
console.log(prev.theme === next.theme) // false

언제 많이 쓰는지?

  • React state 업데이트
  • Redux reducer
  • Immer 내부 동작
  • Tanstack query cache

👩‍💻 immer란 ?

불변성 업데이트를 쉽게 하게 해주는 라이브러리.

(* 불변성 업데이트 : 원본 prev가 절대 직접 수정되지 않는 업데이트)

const next = {
  ...prev,
  user: {
    ...prev.user,
    age: 21,
  },
}

이런 식의 상태 업데이트를 immer를 쓴다면 아래와 같이 할 수 있다.

import { produce } from 'immer'

const next = produce(prev, function (draft) {
  draft.user.age = 21
})

👩‍💻 draft는 뭐지 ?

원본처럼 보이지만 실제 원본은 아닌 임시 객체.

produce(prev, function (draft) {
  draft.user.age = 21
})

여기서 prev는 원본, draft는 수정 가능한 가짜 초안. immer가 내부적으로 변경 내역을 추적하고, 최종적으로 새 객체를 반환한다.

const prev = {
  user: { name: ‘뽀야미’, age: 1 },
  theme: { mode: 'dark' },
}

const next = produce(prev, function (draft) {
  draft.user.age = 2
})

console.log(prev === next) // false
console.log(prev.user === next.user) // false
console.log(prev.theme === next.theme) // true

즉 user는 바뀌었으니 새 객체, theme은 안 바뀌었으니 기존 참조를 재사용한다.

👩‍💻 Redux toolkit과도 관련이 있다고?

Redux toolkit은 내부적으로 immer를 사용한다.

const counterSlice = createSlice({
  name: 'counter',
  initialState: { count: 0 },
  reducers: {
    increment: function (state) {
      state.count += 1
    },
  },
})

겉보기에는 state를 직접 바꾸는 것 같지만 내부적으로는 immer가 처리해서 안전하게 새 state를 만들어준다.

👩‍💻 Tanstack query cache란 ?

서버에서 받아온 데이터를 queryKey 기준으로 저장해두고, 재사용/최신화/상태관리까지 해주는 메모리 저장소.
같은 데이터를 또 필요로 할 때, 굳이 서버를 또 치지 않고 cache에서 먼저 꺼내 쓸 수 있다.

예)

useQuery({
  queryKey: ['user', 1],
  queryFn: fetchUser,
})

{
  ['user', 1]: {
    data: { id: 1, name: ‘뽀야미’ },
    status: 'success',
    updatedAt: 1710000000
  }
}

좋은 점

  • 같은 페이지에서 같은 데이터 여러 군데 쓰면 매번 fetch 안해도 된다.
  • 서버 응답 기다리기 전에 이전 데이터 먼저 보여주고 필요하면 다시 최신화 가능하다.
  • cache라고해서 영원히 믿는건 아니고 이 데이터가 아직 신선한지 관리해준다.
useQuery({
  queryKey: ['user', 1],
  queryFn: fetchUser,
  staleTime: 1000 * 60,
})

-> 이 경우는 1분 동안은 아직 데이터가 신선하다고 보는 것.

queryKey

TanStack Query는 queryKey로 cache를 구분한다.

['todos'] -> 전체 할 일 목록
['todos', 1] -> 1번 할 일 상세 
['todos', { page: 1 }] -> 1페이지 목록

이렇게 cache가 각각 따로 관리된다.

그냥 변수 저장이랑 다른 점은?

그냥 data만 들고 있는 게 아니라, 데이터의 생명주기 전체를 관리해준다.
예를 들면 로딩 상태, 에러 상태, 성공 상태, 마지막 업데이트 시간, stale 여부, refetch 여부, background fetching, retry, garbage collection 같은 것.

  • refetch : 서버에 다시 요청해서 최신 데이터 받아오는 것
  • background fetching : 기존 데이터는 먼저 보여주고, 화면 뒤에서 새 데이터 다시 받아오는 것
  • retry : 요청 실패했을 때 자동으로 다시 시도하는 것
  • garbage collection : 사용 중이지 않은 cache를 일정 시간 후 메모리에서 정리하는 것

TanStack Query와 structural sharing

TanStack Query는 새 데이터를 cache에 반영할 때 structural sharing을 활용해, 안 바뀐 부분의 참조를 유지하려고 한다. 덕분에 불필요한 리렌더를 줄이는 데 도움이 된다.

좋은 글 ❤️

https://blog.klipse.tech/javascript/2021/02/26/structural-sharing-in-javascript.html
https://tanstack.com/query/v5/docs/framework/react/guides/render-optimizations
https://www.milk717.me/tanstack-query-structural-sharing

profile
프론트엔드 개발을 하고 있습니다 ⌨️

0개의 댓글