ivory's Log
그게 무엇이라도 항상 쉬운 일이다.

ivory's DevLog

useMemo, useCallback 파헤치기 - useMemo편

ivorycode 2021. 11. 10. 15:35
반응형

나는 종종 개인 프로젝트, 실무업무를 할 때, 상태 값 확인과 혹시 모를 오류를 대비하기 위해 console.log()를 자주 찍어두는 습관이 있다. 그러던 어느 날, 여느 때처럼 상태 값 확인을 위해 함수에서 console.log()을 출력하다가 불필요한 렌더링이 된다는 사실을 발견하였다. 처음엔 큰 오류도 없고 별 탈없이 state 값을 변경시킨다고 생각하여 괜찮다는 생각도 순간 했었다. 하지만 다시 냉정히 되돌아서서 고민해봤다.

- ‘a만 바꾸고 싶은데, 필요 없는 부분도 리렌더링 되는 게 과연 효율적이고 좋은 렌더링 과정인가?’
- ‘이런 코드가 티끌 모아 태산이 되어버린다면? 혹시 성능에서도 문제가 발생하진 않을까?
- 'React 프로젝트에서 최적화를 하려면, 어떤 방법이 있을까?' 

 

위의 고민을 거쳐 내릴 결론은 당연히 리렌더링은 좋지 않다는 것이었다. 정말 단순하게 생각하자!! 당신이 필요한 일을 할 때, 불필요한 행동도 매번 반복한다면 굉장히 체력적으로 지치고 비효율적 업무라고 생각하게 될 것이다. 개발도 마찬가지다. 이런 불필요한 호출이나 리렌더링은 메모리적으로 굉장한 낭비인데, 아직 제대로 활용해본 경험이 너무 적었다. 그러니 지금부터 차근차근 알아보면서 앞으로 적용해보도록 하자!! 오늘은 useMemo부터 알아보자.

 

이미지 출처: www.google.com

useMemo

메모이제이션(Memoization)

useMemo를 사용해보기 전에, 메모이제이션의 개념에 대해 빠르게 알아보자. 메모이제이션이란 프로그래밍을 할 때 반복되는 결과를 메모리에 저장해 두고, 다음에 같은 결과가 나올 때 다시 계산할 필요 없이 빨리 실행하는 기법이다. 일종의 캐싱과 같다.

 

🆘  캐싱(Caching)

캐싱이란 캐시(Cache)라고 하는 좀 더 빠른 메모리 영역으로 데이터를 가져와 접근하는 방식을 말한다. 캐싱은 오랜 시간이 걸리는 작업의 결과를 저장하여 시간과 비용을 회피하는 기법이므로 고성능 애플리케이션을 만들 때, 가장 중요한 요소로 꼽힌다.

 

useMemo 예제

백문이 불여일견, 바로 useMemo를 적용하기 전과 적용 후를 확인하여 성능을 비교해보자.

CodeSandbox 최종 예제 코드
- 최종 예제 코드를 작성했습니다. 디렉토리와 js파일명을 참고해 주시고, 포스팅 내용과 함께 확인해주세요!!
https://codesandbox.io/embed/proud-cdn-ncf2p?fontsize=14&hidenavigation=1&theme=dark

 

useMemo 적용 전 코드

// src/ShowApp.js

import React, { useState } from "react";

const ShowApp = () => {
  const [number, setNumber] = useState(0);
  const [text, setText] = useState("");

  const increment = () => {
    setNumber((num) => num + 1);
  };

  const decrement = () => {
    setNumber((num) => num - 1);
  };

  const onChangeText = (e) => {
    setText(e.target.value);
  };

  const getNumber = (number) => {
    console.log("getNumber / useMemo x");
    return number;
  };

  const getText = (text) => {
    console.log("getText / useMemo x");
    return text;
  };

  const displayNumber = getNumber(number);
  const displayText = getText(text);

  return (
    <div>
      <div>
        <button onClick={increment}>+</button>
        <button onClick={decrement}>-</button>
        <input type="text" placeholder="input text" onChange={onChangeText} />
      </div>
      <div>
        {displayNumber}
        <br />
        {displayText}
      </div>
    </div>
  );
};

export default ShowApp;

코드에 관해 빠르고 간단하게 요약해보면 increment, decrement 함수를 통해 숫자 더하기, 빼기 기능을 추가시켰다. 그리고 onChangeText는 input 태그에 onChange하여 value값을 바꾼다. 마지막으로 getNumber, getText 함수에는 각각 결과값을 return 하도록 작성하여 화면과 console창에서 확인할 수 있도록 작성했다. 자 이제 화면을 확인하고 테스트해보자!!

 

불필요한 렌더링이 일어나고 있다. 이미지 출처: 내가 작성한 CodeSandbox.io 코드

위의 이미지를 보면 숫자만 변경했을 때나 텍스트만 변경했을 때도 getNumber, getText 함수가 모두 실행되어 console이 출력되는 것을 확인하였다. 한 마디로 불필요한 렌더링이 발생하고 있다는 사실이다! 왕초보인 내게도 이 상황은 뭔가 이상하고 비효율적이며 낭비라고 생각한다. 지금은 단순한 덧셈, 뺄셈 기능이지만 거대한 규모의 프로젝트에서 이런 불필요한 과정이 쌓이고 쌓인다면 잠재적인 오류를 불러올 수 있다. 이럴 때, 사용하는 hook이 바로 useMemo다. 바로 적용시켜보자.

 

useMemo 적용 후 코드

// src/MemoApp.js

import React, { useState, useMemo } from "react";

const MemoApp = () => {
  const [number, setNumber] = useState(0);
  const [text, setText] = useState("");

  const increment = () => {
    setNumber((num) => num + 1);
  };

  const decrement = () => {
    setNumber((num) => num - 1);
  };

  const onChangeText = (e) => {
    setText(e.target.value);
  };

  const getNumber = (number) => {
    console.log("getNumber / useMemo o");
    return number;
  };

  const getText = (text) => {
    console.log("getText / useMemo o");
    return text;
  };

  const displayNumber = useMemo(() => getNumber(number), [number]);
  const displayText = useMemo(() => getText(text), [text]);

  return (
    <div>
      <div>
        <button onClick={increment}>+</button>
        <button onClick={decrement}>-</button>
        <input type="text" placeholder="input text" onChange={onChangeText} />
      </div>
      <div>
        {displayNumber}
        <br />
        {displayText}
      </div>
    </div>
  );
};

export default MemoApp;

위의 코드와 달라진 점은 getNumber와 getText를 호출해주던 변수 displayNumber, displayText에 useMemo를 적용했다는 점이다. 일단 화면부터 확인해보자.

 

불필요한 렌더링이 사라졌다!! 이미지 출처: 내가 작성한 CodeSandbox.io 최종 코드

이번에는 불필요한 렌더링 없이 호출한 함수의 값만 리턴하는 것을 확인할 수 있다! 이렇듯 useMemo는 위에서도 비슷한 상황을 언급했지만, 복잡한 로직의 재계산이나 계산과정이 너무 무겁고 복잡할 때, useMemo를 사용하면 성능 최적화의 도움받을 수 있다.

useMemo 적용할 때, 반드시 의존성 배열에 넘겨줄 값을 넣어줘야 한다. 의존성 배열의 값이 변경될 때, 메모이제이선 된 값을 다시 계산하기 때문이다.

useMemo 적용 코드 부분 참고!!
const displayNumber = useMemo(() => getNumber(number), [number]);
const displayText = useMemo(() => getText(text), [text]);

 

useMemo 사용 유무 최종 비교

 

최종 비교 화면 이미지 출처: 내가 작성한 CodeSandbox.io 최종 코드

정리

- useMemo는 React의 렌더링 성능 최적화를 위해 사용하는 hook이다.

- 재계산 로직이 복잡하거나 메모리 사용이 과도할 때, useMemo를 적용하면 성능 최적화의 도움받을 수 있다.

- useMemo는 의존성 배열의 값이 변경되었을 때, 메모이제이선된 값을 다시 계산한다.

반응형

'ivory's DevLog' 카테고리의 다른 글

[JavaScript] - Closure  (0) 2021.12.01
useMemo, useCallback 파헤치기 - useCallback편  (0) 2021.11.23
왕초보와 ESLint 설정해보기  (0) 2021.11.03
Redux의 흐름과 예제  (1) 2021.09.28
Redux  (0) 2021.09.27