
힙(Heap)
────────────
0xA1 → { test: 'a' }
0xB1 → { test: 'b' }
스택(Stack)
────────────
let obj = 0xA1 // obj 변수는 '0xA1' 참조(주소) 보관
=== 검사const a = { x: 1, y: { z: 2 } };
const b = { x: 1, y: { z: 2 } };
shallowEqual(a, b); // false (y 참조가 다름)
const copy = { ...orig };const copy = JSON.parse(JSON.stringify(orig)); (한계 존재)shallow 비교const { tableData, ... } = historyStore(
( { state, handler } ) => ({
tableData: state.tableData, // 또는 state.tableData[index]
/* ... */
}),
shallow
);
selector가 반환한 객체의 최상위 프로퍼티를 얕은 비교produce와 참조 변경Zustand의 setter(예: setTableListData) 내부에서 Immer produce를 쓰면,
어디를 수정하느냐에 따라 새로운 참조가 어디까지 발생하는지가 달라집니다.
| 수정 형태 | 동작 설명 | 참조 변경 범위 | shallow 감지 |
|---|---|---|---|
① 최상위 재할당draft.state.tableData = newData; | tableData 프로퍼티 전체에 새 배열(또는 객체) 할당 | 최상위 tableData | ✅ |
② 1단계 요소 교체draft.state.tableData[i] = newItem; | Immer는 배열 전체를 복사한 뒤, i번째 요소만 바꾼 새 배열 생성 | 최상위 tableData | ✅ |
③ 2단계 이하 중첩 속성 수정draft.state.tableData[i].foo = x; | 해당 요소 객체만 복사(새 인스턴스), 최상위 배열 참조는 그대로 유지 | 요소 객체만 복사 | ❌ (최상위 같음) |
state.tableData vs state.tableData[index] 구독 비교tableData: state.tableData 구독tableData: state.tableData[index] 구독// A. 배열 전체 재할당
produce(prev, draft ⇒ draft.state.tableData = [...])
// → newData 배열 요소 객체를 어떻게 쓰느냐에 따라
// same-element-ref → no render | new-element-ref → render
// B. 요소 교체 (index 일치 시)
produce(prev, draft ⇒ draft.state.tableData[idx] = newItem)
// → 요소 idx 참조 변경 → render
// C. 요소 내부 속성만 변경
produce(prev, draft ⇒ draft.state.tableData[idx].foo = x)
// → 최상위 참조 유지 → no render (특정 요소 구독 시 render if idx matches)
// 힙 ┌──────────────────────────┐
// 0x01 → [A, B] │ A = {test:'a'} @0xA1 │
// └──────────┤ B = {test:'b'} @0xB1 │
// 스택 └──────────────────────────┘
// let sub0 = state.tableData[0]; // sub0 → 0xA1
const newArr = [...state.tableData];
// newArr @0x02, but newArr[0] @0xA1
produce(prev, draft ⇒ { draft.state.tableData = newArr; });
let newSub0 = state.tableData[0]; // 0xA1
// sub0(0xA1) === newSub0(0xA1) → shallow 같음 → no render
const newArr = state.tableData.map(item ⇒ ({ ...item }));
// newArr @0x03, newArr[0] @0xC1 (새 객체)
produce(prev, draft ⇒ { draft.state.tableData = newArr; });
let newSub0 = state.tableData[0]; // 0xC1
// sub0(0xA1) !== newSub0(0xC1) → shallow 다름 → render
produce(prev, draft ⇒ {
draft.state.tableData[0].test = 'x';
});
// 임시 객체 A 복제 @0xD1, but array stays @0x01
let newSub0 = state.tableData[0]; // 0xD1
// sub0(0xA1) !== newSub0(0xD1) → 해당 요소 구독 시 render;
// 그러나 배열 전체 구독 시 array ref same → no render
리액트에서 값이 변경되면, 새로운 참조값으로 데이터를 넣어주기때문에
옵저버가 그것이 변경되었다고 인식할수있는것이다.
javscript의 기본형 참조형을 생각하게되었을때
당연히 데이터가 안바뀔거라고 나는 다시 생각했으나,
리액트의 기본 동작원리를 생각하게되면,
const {
testData,
} = testStore(
({ state, handler }) => ({
testData: state.testData
}),
shallow,
);
일때에, testData의 데이터가 변경되어서 새로운 값이 되었을때,
testData는 새로운 참조주소를 받게된다.
‼️그래서 랜더가 될지안될지 파악하게된다‼️