React를 쓰다 보면 "리렌더를 줄여야 된다" 라는 말을 자주 하고 듣게 됩니다.
React에서 리렌더는
화면을 다시 그린다(x)
함수를 다시 실행한다(o)가 맞습니다.
React 컴포넌트는 함수라서 리렌더가 곧 컴포넌트 함수 다시 호출을 의미하기 때문입니다.
리렌더가 발생하는 상황은 아래와 같습니다.
useState setter 호출props가 바뀔 때context값이 바뀔 때여기서 React가 무엇이 바뀌었는지 판단하기 위해 참조라는 개념을 알아야 합니다.
number,string,boolean,null,undefined,symbol ...
1 === 1 // true
"hi" === "hi" // true
object,array,function,Map,Set ...
{} === {} // false
[] === [] // false
(() => {}) === (() => {}) // false
React와 / Zustand에서 같은지 비교할 때 자주 쓰는 기준이
useRef는 값이 바뀌어도 리렌더를 일으키지 않음
.current를 바꿔도 리렌더 발생Xconst posRef = useRef({x: 0, y: 0});
posRef.current.x = 10; // 리렌더X
useMemo는 렌더마다 새 객체를 만들면 매번 참조가 바뀌는 것을 방지 (필요할 때만 새로 만듦)
의존성이 바뀔 때만 새 값을 생성. 그 이외에는 이전 값을 재사용
const config = useMemo(() => ({a,b}),[a,b]);
useCallback은 렌더마다 새 함수가 만들어지는 것을 방지
의존성이 바뀔 때만 새 함수를 생성. 그 이외에는 이전 함수를 재사용
const onClick = useCallbak(() => {
add(value);
},[value]);
Zustand의 useStore(selector)는
1. 내가 고른 값(selector)를 저장
2. store가 업데이트될 때마다 selector를 다시 실행
3. 이전 결과와 현재 결과가 다르면 리렌더
이런 방식으로 동작합니다.
근데 3번의 다르다라는 판단은 Object.is 로 비교합니다.
그래서 selector가 object/array/function 같은 참조 타입을 매번 새로 만들어 반환하면,
값이 같아 보여도 참조가 달라져서 매번 다르다고 판단되어 리렌더가 발생할 수 있습니다.
const { a,b } = useAppStore((s) => ({ a: s.a, b:s.b}));
해결 방법: useShalow | shallow로 얕은 비교
const { a, b } = useAppStore(useShallow((s) => ({ a: s.a, b: s.b })));
const { a,b } = useAppStore((s) => ({a: s.a, b:s.b}),shallow);
구독 하지 않고 필요할 때 현재의 값을 읽고 싶을때는 아래와 같은 방법이 있습니다.
const state = useAppStore.getState();
console.log(state.count);
useEffect(() => {
const unsub = useAppStore.subscribe(
(s) => s.count,
(count) => { ... }
);
return unsub;
}, []);
보통 UI 리렌더가 아닌 사이드 이펙트 처리에 사용.
shallow/ useShallow 사용getState()는 단발성 값 읽기(리렌더 X, 자동 갱신 X)subscribe()는 리렌더 없이 값 변경 감지 가능하지만 cleanup(unsub)이 필수