hmk run dev

useMemo와 useCallback 본문

React

useMemo와 useCallback

hmk run dev 2022. 3. 22. 10:55

React를 사용한다는 가정하에

프런트엔드 최적화를 위해 해야 할 것들을 주기적으로 작성해볼 포스팅입니다 :)

 

useMemo & useCallback

위 두 개의 리액트 훅을 사용하기 전에 이 사실들을 알고넘어갈 필요가 있습니다.

 

1. 함수형 컴포넌트는 함수다. 단지 jsx를 반환하는 함수입니다.

2. 컴포넌트가 렌더링 된다는 것은 누군가가 그 함수(컴포넌트)를 호출하여 실행된다는 것을 말합니다.

즉, 함수가 실행될 때마다 내부에 선언되어 있던 표현식(변수, 함수 등)도 매번 다시 선언되어 사용됩니다.

3. 컴포넌트는 자신의 상태 값(state)이 변경되거나, 부모에게서 받은 props가 변경될 때마다 리 렌더링이 됩니다.

 

이렇듯 리액트에선 렌더링이 빈번하게 발생할 수 있으며 렌더링 시 선언해놓은 함수나, 변수 등이 매번 다시 선언되어

불필요한 리소스가 발생할 수 있는데 이러한 낭비를 해결해 줄 수 있는 React Hook이 useMemo와 useCallback이라고 할 수 있습니다.

 

useMemo

메모이제이션된 값을 반환

 

다음과 같은 상황이라고 가정해봅시다.

- 하위 컴포넌트는 상위 컴포넌트로 a, b라는 props를 전달받습니다.

- a와 b를 전달받으면 서로 다른 함수로 각각 값을 가공해 새로운 값을 보여주는 역할을 합니다.

- 만약 props로 넘겨받는 a와 b 중 둘 중 하나라도 변경이 감지되면 렌더링이 됩니다.

- 즉, a가 변경되면 b도 같이 변경되어 변경된 건 a인데 변경이 불필요한 b에 자원을 낭비하게 됩니다.

 

위와 같은 자원낭비 현상을 useMemo를 활용해 해결한 코드를 봅시다.

 

- App 컴포넌트는 Info컴포넌트로 사용자에게 입력받은 color와 movie를 props로 넘겨줍니다.

- Info 컴포넌트는 넘겨받은 color와 movie를 한글로 바꾸어서 보여줍니다. 

// App.js

import Info from "./Info";

const App = () => {
  const [color, setColor] = useState("");
  const [movie, setMovie] = useState("");

  const onChangeHandler = e => {
    if (e.target.id === "color") setColor(e.target.value);
    else setMovie(e.target.value);
  };

  return (
    <div className="App">
      <div>
        <label>
          What is your favorite color of rainbow ?
          <input id="color" value={color} onChange={onChangeHandler} />
        </label>
      </div>
      <div>
        What is your favorite movie among these ?
        <label>
          <input
            type="radio"
            name="movie"
            value="Marriage Story"
            onChange={onChangeHandler}
          />
          Marriage Story
        </label>
        <label>
          <input
            type="radio"
            name="movie"
            value="The Fast And The Furious"
            onChange={onChangeHandler}
          />
          The Fast And The Furious
        </label>
        <label>
          <input
            type="radio"
            name="movie"
            value="Avengers"
            onChange={onChangeHandler}
          />
          Avengers
        </label>
      </div>
      <Info color={color} movie={movie} />
    </div>
  );
};

export default App;

 

// Info.js

const getColorKor = color => {
    console.log("getColorKor");
    switch (color) {
      case "red":
        return "빨강";
      case "orange":
        return "주황";
      case "yellow":
        return "노랑";
      case "green":
        return "초록";
      case "blue":
        return "파랑";
      case "navy":
        return "남";
      case "purple":
        return "보라";
      default:
        return "레인보우";
    }
  };

  const getMovieGenreKor = movie => {
    console.log("getMovieGenreKor");
    switch (movie) {
      case "Marriage Story":
        return "드라마";
      case "The Fast And The Furious":
        return "액션";
      case "Avengers":
        return "슈퍼히어로";
      default:
        return "아직 잘 모름";
    }
  };


const Info = ({ color, movie }) => {
  const colorKor = getColorKor(color);
  const movieGenreKor = getMovieGenreKor(movie);

  return (
    <div className="info-wrapper">
      제가 가장 좋아하는 색은 {colorKor} 이고, <br />
      즐겨보는 영화 장르는 {movieGenreKor} 입니다.
    </div>
  );
};

export default Info;

- App 컴포 너트의 입력창에서 color값만 바꾸어도  getColorKor, getMovieGenreKor 두 함수가 모두 실행됨

- 반대의 경우에도 마찬가지

 

useMemo를 import후에 아래와 같이 코드를 수정해보자

import React, { useMemo } from "react";

const colorKor = useMemo(() => getColorKor(color), [color]);
const movieGenreKor = useMemo(() => getMovieGenreKor(movie), [movie]);

color값이 바뀔 때는 getColorKor함수만, movie값이 바뀔 때는 getMovieGenreKor함수만 호출되는 것을 확인할 수 있습니다.

 

지금처럼 재계산하는 함수가 아주 간단하다면 성능상의 차이는 아주 미미하겠지만 만약 재계산하는 로직이 복잡하다면 불필요하게 비싼 계산을 하는 것을 막을 수 있다.

오히려 무분별하게 useMemo를 쓰는 것은 성능상으로 안 좋을 수 있습니다.

 

props를 넘겨받는 상황이 아닌 하나의 컴포넌트에서 두 개 이상의 state가 있을 때도 활용할 수 있습니다.

 


useCallback

메모이제이션된 함수를 반환

 

앞서 말했듯이 컴포넌트가 렌더링 될 때마다 내부에 선언돼있던 함수 표현식과, 변수 등이 매번 다시 선언되어 사용됩니다.

 

App.js의 onChangeHandler는 onchange(변화 감지)를 할 때마다 재선언이 됩니다.

이러한 불필요한 재선언을 방지하고 첫 마운트 시 한 번만 선언하고 재사용이 되게 하는 방법 중 하나가 useCallback이라고 할 수 있겠습니다.

 

useCallback을 import하여 onChangeHandler에 적용한 코드입니다.

// App.js

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

const onChangeHandler = useCallback(e => {
    if (e.target.id === "color") setColor(e.target.value);
    else setMovie(e.target.value);
  }, []);

실무에선 주로 이벤트 핸들러 함수나 api요청 함수에 useCallback을 사용하는 코드가 꽤 많습니다.

 

하지만 useMemo처럼 비싼 계산, 많은 리소스가 투입되는 함수가 아니라면 무분별하게 사용하는 것은 그리좋지 못한 

선택입니다.

 

- React.memo()로 함수형 컴포넌트 자체를 감싸면 넘겨 받는 props가 변경되지 않았을 때는 상위컴포넌트가 

메모리제이션된 함수형 컴포넌트를 사용하게 됨

 

- 함수는 오로지 자기 자신만이 동일하기 때문에 상위 컴포넌트에서 callback 함수를 재선선한다면 props로 callback 함수를 넘겨 받는 하위 컴포넌트 입장에서는 props가 변경 되었다고 인식합니다.

 

 

 

 

 

출처

https://leehwarang.github.io/2020/05/02/useMemo&useCallback.html

 

이제는 사용해보자 useMemo & useCallback - 이화랑 블로그

이제는 사용해보자 useMemo & useCallback 이제 useState와 useEffect에 완전히 익숙해졌다고 느꼈는데, 컴포넌트 내에서 저 두 개의 hook 만으로도 props나 state를 다루는 로직에 관련된 기본적인 기능을 모두

leehwarang.github.io

 

'React' 카테고리의 다른 글

Next.js 정적 생성(Static Generation) - getStaticProps, getStaticPaths  (0) 2022.03.01
next.js의 SSR  (0) 2022.03.01
Next.js의 장점과 프로젝트 구조  (0) 2022.03.01
async, await  (0) 2021.05.20
쿠키방식 로그인 코드  (0) 2021.04.17
Comments