
바뀐 부분만 새로 만들고, 안 바뀐 부분은 기존 걸 그대로 재사용하는 것.
예 )
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은 기존 참고 그대로 유지한다.
즉, 바뀐 부분만 새 객체고 나머지는 기존 참조를 재사용한다.
‘얕은 복사’랑 비슷해 보이는데, 목적이 다르다.
얕은복사는 그냥 복사인 반면, 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
불변성 업데이트를 쉽게 하게 해주는 라이브러리.
(* 불변성 업데이트 : 원본 prev가 절대 직접 수정되지 않는 업데이트)
const next = {
...prev,
user: {
...prev.user,
age: 21,
},
}
이런 식의 상태 업데이트를 immer를 쓴다면 아래와 같이 할 수 있다.
import { produce } from 'immer'
const next = produce(prev, function (draft) {
draft.user.age = 21
})
원본처럼 보이지만 실제 원본은 아닌 임시 객체.
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은 내부적으로 immer를 사용한다.
const counterSlice = createSlice({
name: 'counter',
initialState: { count: 0 },
reducers: {
increment: function (state) {
state.count += 1
},
},
})
겉보기에는 state를 직접 바꾸는 것 같지만 내부적으로는 immer가 처리해서 안전하게 새 state를 만들어준다.
서버에서 받아온 데이터를 queryKey 기준으로 저장해두고, 재사용/최신화/상태관리까지 해주는 메모리 저장소.
같은 데이터를 또 필요로 할 때, 굳이 서버를 또 치지 않고 cache에서 먼저 꺼내 쓸 수 있다.
예)
useQuery({
queryKey: ['user', 1],
queryFn: fetchUser,
})
{
['user', 1]: {
data: { id: 1, name: ‘뽀야미’ },
status: 'success',
updatedAt: 1710000000
}
}
useQuery({
queryKey: ['user', 1],
queryFn: fetchUser,
staleTime: 1000 * 60,
})
-> 이 경우는 1분 동안은 아직 데이터가 신선하다고 보는 것.
TanStack Query는 queryKey로 cache를 구분한다.
['todos'] -> 전체 할 일 목록
['todos', 1] -> 1번 할 일 상세
['todos', { page: 1 }] -> 1페이지 목록
이렇게 cache가 각각 따로 관리된다.
그냥 data만 들고 있는 게 아니라, 데이터의 생명주기 전체를 관리해준다.
예를 들면 로딩 상태, 에러 상태, 성공 상태, 마지막 업데이트 시간, stale 여부, refetch 여부, background fetching, retry, garbage collection 같은 것.
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