useMemo, useCallback을 언제 써야되나?

JungHoon Park
9 min readJun 8, 2021

Kent C. Dodds의 블로그 글을 읽고 검증해봤다.

Kent C. Dodds의 블로그를 보다가 흥미로운 글을 발견했다. React에서 언제 useMemo와 useCallback을 쓰는 게 좋은지에 대한 글이다.

이 두 개의 함수는 보통 성능 최적화를 위해 사용한다. 콜백함수의 반환 값을 저장해놨다가 배열 “[]”안의 디펜던시가 전과 같은지의 여부에 따라 렌더링 여부를 결정한다.

이번 기회에 Chrome dev tools를 사용해 그가 올린 예제를 기본, useCallback, useMemo, useCallback+useMemo 상태일 때 성능을 측정했다.

목차

  1. 테스트 환경 및 측정 방법
  2. Candy Dispenser
  3. Dual Counter
  4. Counter 100개
  5. 결론

1. 테스트 환경 및 측정 방법

Next + React을 build한 후 Chrome 시크릿 모드에서 performance 탭을 활용해 진행했다. 또한 Next 환경 구축은 다음의 커멘드를 사용했다.

npx create-next-app --typescript
yarn build && yarn start

데이터는 측정 중간에 Major GC 가 발생하지 않은 click 이벤트의 main thread 요구시간으로 측정했다. (낮을 수록 빠르다.)

측정 방법

2. Candy Dispenser

그의 글의 첫 번째 사례이다. grab 버튼을 누르면 해당 제품이 메뉴에서 제거된다. 기본 상태가 더 빠르다고 하는 대 과연 그럴지 궁금했다.

Candy Dispenser

기본 상태와 useCallback을 사용했을 때를 측정했다. 테스트는 grab 버튼을 4번 클릭해 진행했다.

 단위: ms
╔═════════════╦═══════╦══════╦══════╦══════╦══════╦═══════════╗
║ ║ 1 ║ 2 ║ 3 ║ 4 ║ avg ║ total avg ║
╠═════════════╬═══════╬══════╬══════╬══════╬══════╬═══════════╣
║ default ║ 7.43 ║ 5.57 ║ 3.44 ║ 3.17 ║ 4.9 ║ 5.18 ║
║ ╠═══════╬══════╬══════╬══════╬══════╣ ║
║ ║ 12.15 ║ 3.05 ║ 3.44 ║ 2.94 ║ 5.4 ║ ║
║ ╠═══════╬══════╬══════╬══════╬══════╣ ║
║ ║ 12.07 ║ 2.91 ║ 2.73 ║ 3.3 ║ 5.25 ║ ║
╠═════════════╬═══════╬══════╬══════╬══════╬══════╬═══════════╣
║ useCallback ║ 10.63 ║ 5.38 ║ 4.2 ║ 4.43 ║ 6.16 ║ 5.6 ║
║ ╠═══════╬══════╬══════╬══════╬══════╣ ║
║ ║ 9.43 ║ 4.97 ║ 3.1 ║ 3.22 ║ 5.18 ║ ║
║ ╠═══════╬══════╬══════╬══════╬══════╣ ║
║ ║ 10.46 ║ 4.84 ║ 3.12 ║ 3.38 ║ 5.45 ║ ║
╚═════════════╩═══════╩══════╩══════╩══════╩══════╩═══════════╝
평균 차트

테스트 결과 그의 말대로 useCallback을 처리하는 과정이 이것을 사용함으로써 얻을 수 있는 이득보다 더 소요됐다. 즉, 기본 상태가 총 평균 0.42ms 더 빨랐다.

3. Dual Counter

이 예제에서는 버튼을 클릭할 때마다 두 Counter 버튼을 렌더링하므로 useMemo와 useCallback을 사용해도 좋은 이유라고 했다. 정말 그런지 확인해봤다.

Dual Counter

테스트는 왼쪽 오른쪽 번갈아 버튼을 총 3번 클릭해 진행했다.

단위: ms
╔═════════════════════╦══════╦══════╦══════╦══════╦═══════════╗
║ ║ 1 ║ 2 ║ 3 ║ avg ║ total avg ║
╠═════════════════════╬══════╬══════╬══════╬══════╬═══════════╣
║ default ║ 5.13 ║ 1.81 ║ 1.97 ║ 2.97 ║ 2.95 ║
║ ╠══════╬══════╬══════╬══════╣ ║
║ ║ 4.23 ║ 2.09 ║ 1.64 ║ 2.65 ║ ║
║ ╠══════╬══════╬══════╬══════╣ ║
║ ║ 6.05 ║ 1.78 ║ 1.85 ║ 3.23 ║ ║
╠═════════════════════╬══════╬══════╬══════╬══════╬═══════════╣
║ useMemo ║ 6.61 ║ 2.68 ║ 1.86 ║ 3.71 ║ 3.55 ║
║ ╠══════╬══════╬══════╬══════╣ ║
║ ║ 6.77 ║ 1.94 ║ 1.66 ║ 3.46 ║ ║
║ ╠══════╬══════╬══════╬══════╣ ║
║ ║ 6.58 ║ 2.23 ║ 1.67 ║ 3.49 ║ ║
╠═════════════════════╬══════╬══════╬══════╬══════╬═══════════╣
║ useCallback ║ 6.11 ║ 1.66 ║ 2.07 ║ 3.28 ║ 2.89 ║
║ ╠══════╬══════╬══════╬══════╣ ║
║ ║ 3.78 ║ 1.58 ║ 2.35 ║ 2.57 ║ ║
║ ╠══════╬══════╬══════╬══════╣ ║
║ ║ 5.49 ║ 1.56 ║ 1.41 ║ 2.82 ║ ║
╠═════════════════════╬══════╬══════╬══════╬══════╬═══════════╣
║ useMemo+useCallback ║ 7.18 ║ 1.64 ║ 1.47 ║ 3.43 ║ 3.38 ║
║ ╠══════╬══════╬══════╬══════╣ ║
║ ║ 5.77 ║ 1.64 ║ 2.08 ║ 3.16 ║ ║
║ ╠══════╬══════╬══════╬══════╣ ║
║ ║ 7.05 ║ 2.11 ║ 1.44 ║ 3.55 ║ ║
╚═════════════════════╩══════╩══════╩══════╩══════╩═══════════╝
평균 차트

이 테스트에서 흥미로운 것은 useMemo+useCallback을 썼을 때가 젤 빠르지 않았고 기본 상태도 빠른 속도를 보여줬다.

4. Counter 100개

지금까지의 테스트에서 알 수 있듯이 useMemo, useCallback을 사용해도 소수점 단위로 빨라질 뿐 눈에 띌 만한 성능 차이를 보이지 못했다. 그렇다면 무거운 컴포넌트에서는 큰 차이를 보여줄지 궁금했다.

100 Counters

테스트는 첫 번째 버튼만을 총 5번 클릭해 진행했다.

단위: ms
╔═════════════════════╦═══════╦══════╦══════╦══════╦═══════╦══════╗
║ ║ 1 ║ 2 ║ 3 ║ 4 ║ 5 ║ avg ║
╠═════════════════════╬═══════╬══════╬══════╬══════╬═══════╬══════╣
║ default ║ 11.69 ║ 9.1 ║ 7.51 ║ 8.35 ║ 12.34 ║ 9.8 ║
╠═════════════════════╬═══════╬══════╬══════╬══════╬═══════╬══════╣
║ useMemo ║ 12.10 ║ 9.2 ║ 9.32 ║ 8.75 ║ 8.02 ║ 9.48 ║
╠═════════════════════╬═══════╬══════╬══════╬══════╬═══════╬══════╣
║ useCallback ║ 10.13 ║ 7.33 ║ 6.85 ║ 8.21 ║ 6.66 ║ 7.83 ║
╠═════════════════════╬═══════╬══════╬══════╬══════╬═══════╬══════╣
║ useMemo+useCallback ║ 8.31 ║ 6.05 ║ 2.87 ║ 4.23 ║ 4.12 ║ 5.12 ║
╚═════════════════════╩═══════╩══════╩══════╩══════╩═══════╩══════╝
평균 차트

이 경우에서는 useMemo+useCallback 조합이 평균 5.12ms로 기본 상태와 비교해 약 47.76% 빨랐다. 또한 useMemo, useCallback을 사용할 수록 기본 상태에 비해 더 빠른 성능을 나타낸다.

5. 결론

이 연구에서 볼 수 있듯이, 작은 규모에서는 useMemo, useCallback를 사용하면 성능이 오히려 나빠지거나 소수점 단위의 차이로 효과가 미미하게 좋아졌지만, 규모가 커질 수록 조금씩 성능상의 이점이 있다는 것을 확인할 수 있었다. 그러나 Counter 100개 예제에서 볼 수 있듯이 1 자릿수의 성능 향상만을 보였다.

그의 블로그에서도 언급했듯이 useMemo, useCallback의 과도한 사용은 성능 향상의 이점보다는 유지보수를 어렵게 할 수 있으므로 성능 최적화가 필요한 부분에만 쓰는 것을 권장한다.

--

--