부모 컴포넌트에 상태가 하나 있는데, 그게 바뀔 때마다 자식들이 다 리렌더되길래 무거운 자식 하나를 React.memo로 감쌌습니다. 근데도 부모가 리렌더될 때마다 이 자식이 계속 다시 그려져요. React DevTools의 Highlight updates로 보면 매번 깜빡입니다.
자식에 넘기는 props는 이렇게 생겼어요.
<HeavyList
data={items}
style={{ marginTop: 8 }}
onSelect={(id) => setSelected(id)}
/>data는 그대로고 style이나 onSelect도 겉보기엔 안 바뀌는데 왜 memo가 안 먹는 걸까요? memo가 원래 이런 걸 막아주는 거 아니었나요?
댓글 4
그래서 자식에 넘기는 참조들을 안정화해줘야 합니다. onSelect는 useCallback으로 감싸고, setSelected만 부르는 거면 의존성 배열은 비워도 돼요(setter는 안정적이라). style 객체는 컴포넌트 밖에 상수로 빼두거나 useMemo로 고정하면 됩니다. 아예 marginTop 정도는 CSS 클래스로 옮기는 게 제일 깔끔하고요. 이렇게 세 prop 다 참조가 유지되면 그때 memo가 제대로 작동합니다.
그리고 memo에 기대기 전에 구조로 푸는 방법이 더 깔끔할 때가 많아요. 자주 바뀌는 상태를 그 상태가 실제로 필요한 하위 컴포넌트로 내려버리면(상태를 밑으로 밀기), 부모가 통째로 리렌더되는 일 자체가 줄어서 memo가 필요 없어집니다. 또 무거운 트리를 children으로 받는 패턴을 쓰면, 부모 상태가 바뀌어도 children으로 전달된 부분은 참조가 유지돼서 다시 안 그려져요.
정리하면 지금 HeavyList처럼 진짜 무겁고 자주 부모 때문에 덩달아 그려지는 놈만 memo + 참조 안정화 해주시고, 나머지는 그냥 두세요. DevTools Profiler로 실제 병목을 확인한 다음 그 지점에만 적용하는 게 조기 최적화를 피하는 길입니다.