Web-Frontend/React.js

[React.js] 렌더링 최적화 - useCallback, 함수형 업데이트

서노리 2023. 1. 20. 21:22
반응형

useCallback

useCallback은 함수를 메모이제이션 하기 위해서 사용하는 React Hooks이다. 

useCallback(() => {}, []);

 

첫 번째 인자로는 콜백함수, 두 번째 인자로는 의존성 배열을 받으며 배열 안에 들어있는 값이 변화하면 메모이제이션된 콜백함수가 반환된다. 

 

useCallback 사용 예제

< 문제 상황 >

크롬의 확장도구인 React Developer Tools에서 사용할 수 있는 highlight updates when components render 기능을 통해 어떠한 작업이 수행될 때 어떤 컴포넌트가 렌더링되는지를 시각적으로 확인해보자. 일기리스트에 있는 목록 중 하나를 삭제하거나 수정하면 해당 작업과 아무 상관이 없는 DiaryEditor 컴포넌트도 렌더링되는 문제가 발생한다.

 

< 불필요한 렌더링이 일어난 이유 >

DiaryEditor 컴포넌트는 App 컴포넌트로부터 onCreate 함수를 prop으로 전달받고있다. 일기리스트 수정, 삭제 작업 등으로 App 컴포넌트가 재생성될 때마다 App 컴포넌트 안에 선언되어있는 onCreate 함수도 재생성되게 되는데 React.memo는 prop을 비교할 때 얕은 비교를 하기 때문에 onCreate에 변화가 있는 것으로 판단하여 DiaryEditor 컴포넌트도 리렌더링 되는 것이다. 즉, 이를 최적화하기 위해서는 onCreate가 재생성되지 않아야한다. 이 때 useCallback으로 onCreate를 감싸줌으로써 이 문제를 해결할 수 있다.

 

 

※ useMemo를 사용하면 안되는 이유?

useMemo는 함수 반환이 아닌 값을 반환하기 때문에 함수 자체를 반환하기 위해서 useCallback을 사용해야 한다.


함수형 업데이트

useCallback을 적용한 위 코드에도 문제가 발생한다. onCreate 함수가 잘 작동하는지 확인하기 위해 새로운 일기를 저장하면 기존에 있던 일기리스트들이 모두 사라지고 새로 작성한 일기만 리스트에 뜨는 것을 확인할 수 있다.

 

DiaryEditor 컴포넌트의 data는 처음 mount 되는 시점의 상태이다. useCallback의 deps에 빈 배열로 전달하면 mount되는 시점에 렌더가 되고 setData 함수에 data는 빈 배열로 전달되기 때문에 새로 일기를 저장하면 있었던 일기들은 지워지고 한개의 일기만 리스트에 뜨게 되는 것이다. 그렇다고 deps에 data의 값을 넣어주면 또 리스트가 삭제되거나 수정될 때마다 리렌더링이 일어나게 되는 딜레마에 빠진다. 이 때 함수형 업데이트를 사용하여 문제를 해결할 수 있다. 함수형 업데이트란 setState 함수에 함수를 전달하는 방식을 말한다. 위 코드에서 setData 부분을 다음과 같이 수정하여 문제를 해결할 수 있었다.

setData((data) => [newItem, ...data]);

수정한 코드는 useCallback의 deps 배열을 비워도 setData에 넘겨준 함수의 인자를 통해 항상 최신의 data를 보장할 수 있게 된다.


 

반응형