useCallback
+ React.memo
)// Parent.tsx
function App() {
const [state, setState] = useState(0);
const onClick = () => {};
useEffect(() => {
setTimeout(() => {
setState(1);
}, 1000);
}, []);
return (
<div className="App">
<Child onClick={onClick} />
</div>
);
}
// Child.tsx
function Child({ onClick }) {
return (
<>
{Array.from({ length: 100 }, (_, idx) => (
<div key={idx} onClick={onClick}>
Hello, world!
</div>
))}
</>
);
}
App
컴포넌트만 리렌더링됐는데 왜 Child
도 리렌더링될까?App
컴포넌트가 리렌더링되면, onClick
함수도 다시 정의되면서 새로운 참조값을 갖게 됩니다.
React는 Child
컴포넌트에 전달된 onClick
prop이 이전과 다른 값이라고 판단하고, 결과적으로 Child
도 리렌더링하게 됩니다.
Child
의 불필요한 렌더링을 방지해줄 수 있지 않을까?이런 불필요한 리렌더링을 막기 위해서는 함수의 참조값을 고정해줄 필요가 있고, 이때 사용하는 것이 바로 useCallback
Hook입니다.
useCallback
은 함수를 메모이제이션해주어, 의존성이 바뀌지 않는 한 같은 함수 참조값을 재사용하게 해줍니다.
const onClick = useCallback(() => { }, []);
하지만 위와 같이 함수의 참조값을 useCallback
으로 고정해주었음에도, 여전히 Child
컴포넌트가 리렌더링되는 것을 확인할 수 있습니다.
useCallback
을 사용해 Child
컴포넌트에 전달되는 props가 이전과 동일한데, 왜 리렌더링이 발생할까?React는 기본적으로 모든 부모 컴포넌트가 리렌더링되면, 자식 컴포넌트도 함께 리렌더링합니다.
즉, props가 같더라도, React는 자식 컴포넌트를 자동으로 리렌더링에서 제외하지 않습니다.
Child
컴포넌트의 리렌더링을 막으려면 어떻게 해야 할까?이럴 때 사용할 수 있는 방법이 바로 React.memo
입니다.
React.memo
는 전달받은 props가 이전과 동일한 경우, 컴포넌트의 리렌더링을 생략하고 이전에 렌더링된 결과를 재사용하는 고차 컴포넌트(HOC)입니다.
// Child 컴포넌트 React.memo 적용
function Child({ onClick }) {
return (
<>
{Array.from({ length: 100 }, (_, idx) => (
<div key={idx} onClick={onClick}>
Hello, world!
</div>
))}
</>
);
}
export default React.memo(Child)
Child
컴포넌트가 렌더링되기 전에, React.memo
는 전달받은 onClick
prop의 이전 값과 현재 값을 비교합니다.
두 값이 같다면, Child
는 렌더링을 생략하고 이전 결과를 재사용하게 됩니다.
useMemo
+ React.memo
)// Parent.tsx
function App() {
const [state, setState] = useState(0);
const item = {
name : '강수영',
city : '서울'
}
useEffect(() => {
setTimeout(() => {
setState(1);
}, 1000);
}, []);
return (
<div className="App">
<Child item={item} />
</div>
);
}
// Child.tsx
function Child({ item }) {
return (
<>
{Array.from({ length: 100 }, (_, idx) => (
<div key={idx}>
이름: {item.name} <br />
지역: {item.city}
</div>
))}
</>
);
}
export default React.memo(Child)
Child
컴포넌트에 React.memo
를 적용하면, 일반적으로 props가 변하지 않는 한 리렌더링을 방지할 수 있습니다.
하지만 Child
가 여전히 리렌더링되고 있습니다. 그 이유는 Child
컴포넌트 입장에서는 매번 새로운 item
객체를 전달받고 있기 때문입니다.
App
컴포넌트가 리렌더링될 때마다, item
객체는 새로 생성되며, 이는 참조형 데이터이기 때문에 항상 다른 참조값을 가지게 됩니다. React는 이 다른 참조값을 "변경된 props"로 판단하고, 결국 Child
를 리렌더링하게 됩니다.
이러한 상황에서는 useMemo
Hook을 사용해 객체를 메모이제이션해주는 것이 좋습니다. useMemo
는 특정 값이 의존성에 따라 재계산될 필요가 있을 때만 새 값을 생성하며, 불필요한 객체 생성과 리렌더링을 방지할 수 있습니다.
const memoizedItem = useMemo(() => item, []);
참초