구조적 공유(Structural Sharing)는 새 버전의 데이터를 만들 때 바뀌지 않은 부분은 이전 버전의 객체를 그대로 가리키게 하는 기법이다. 바뀐 노드와 그 노드까지 올라가는 경로만 새로 만들고, 나머지 가지는 두 버전이 함께 쓴다. 불변 데이터를 다루면서도 매번 전체를 복사하지 않게 해 준다.
이전: [A, B, C] 다음: [A, B', C]
next !== prev (배열 자체는 새 객체)
next[0] === prev[0] A 재사용
next[1] !== prev[1] B만 새 객체
next[2] === prev[2] C 재사용React에서 왜 중요한가
React는 바뀌었는지를 대개 참조 비교(Object.is)로 판단한다. memo는 props를 하나씩 Object.is로 비교하고, useMemo·useEffect 같은 훅도 의존성을 하나씩 Object.is로 비교한다(memo·useMemo·useCallback). 안 바뀐 항목이 같은 참조로 남아 있으면 <ProductCard product={product} /> 목록에서 바뀐 카드만 다시 렌더된다. 반대로 내용이 같아도 참조가 새로 생기면 메모이제이션이 매번 깨진다.
TanStack Query의 structuralSharing
서버에서 다시 받은 JSON은 파싱 직후 전부 새 객체다. TanStack Query는 이전 데이터와 새 데이터를 깊게 비교해 같은 부분은 이전 참조로 바꿔 넣는다. 아무것도 안 바뀌었으면 data 자체가 이전 객체 그대로다. 그래서 refetch가 일어나도 실제로 바뀐 데이터를 쓰는 컴포넌트만 리렌더된다.
- 기본값
true.structuralSharing: false로 끄거나(oldData, newData) => 결과함수를 넘겨 직접 구현할 수 있다 queryFn이 JSON으로 표현되는 값을 반환할 때만 동작한다. 그 밖의 값(Date,Map, 클래스 인스턴스 등)은 내용이 같아도 늘 바뀐 것으로 본다select를 쓰면 원본 결과와select결과에 한 번씩, 두 번 적용된다- 아주 큰 데이터에서는 비교 자체가 병목이 될 수 있다. 참조 유지가 필요 없으면 끈다
다른 곳의 구조적 공유
- 상태를 불변으로 갱신하면 같은 효과가 저절로 난다.
{ ...state, user: { ...state.user, name } }는 바깥 객체와user만 새로 만들고 다른 필드는 이전 참조를 그대로 둔다(React 상태 갱신) - 같은 아이디어가 함수형 언어의 영속 자료구조(Persistent Data Structure)에 있다. 연결 리스트 앞에 원소를 붙이면 꼬리는 복사하지 않고 두 리스트가 공유한다. 트리는 바뀐 노드까지의 경로만 복사한다(path copying). Clojure의 벡터, Immutable.js가 이 방식이다(연결 리스트)
출처: React — memo: Minimizing props changes · TanStack Query — Render Optimizations: structural sharing · TanStack Query — useQuery · TanStack Query — Important Defaults · React — useMemo · TkDodo — React Query Render Optimizations · Wikipedia — Persistent data structure · Immutable.js