프레임워크/React

[React] 렌더링 최적화 Hook - useCallback, useMemo

은돌1113 2022. 1. 18. 23:16

Clean Coding에 대한 책을 읽고, 정리를 하다 보니

useState, useEffect만으로는 성능 최적화나 클린한 코드가 되지 않는구나를 뼈저리게 느끼게 되었고

찾아보니 성능 최적화를 위한 Hook이기 때문에 필수적으로 사용 할 필요는 없지만.

필수가 아니라는 이유 만으로 해당 기술의 장단점을 파악하지 않고, 넘어가는 건 좋지 못한 것 같아 정리를 하게 되었다!

 

 

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

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

leehwarang.github.io


  [ useMemo와 useCallback을 배우기 전에 알아야 하는 것 ]  

  1. 함수형 컴포넌트는 그냥 함수이다. 즉, 함수형 컴포넌트는 단지 jsx를 반환하는 함수이다.
  2. 컴포넌트가 렌더링 된다는 것은 누군가가 그 함수(컴포넌트)를 호출하여 실행되는 것을 의미한다.
    함수가 실행 될 때마다 내부에 선언되어 있는 변수나 함수 같은 표현식도 매번 재선언 되어 사용된다.
  3. 컴포넌트는 자신의 state가 변경 되거나, 부모 컴포넌트로 부터 받은 props가 변경 될 때마다 리렌더링 된다.
    (하위 컴포넌트에 최적화 설정(useMemo)를 설정 해주지 않으면 부모에게서 받는 props가 변경 되지 않더라도 리렌더링이 되는 게 기본이다. → 부모 컴포넌트의 state나 함수가 변경 되면 하위 컴포넌트도 재렌더링 된다.)

  [ useMemo ]  

https://ko.reactjs.org/docs/hooks-reference.html#usememo

"메모리제이션된 값을 반환한다"는 문장이 핵심입니다. 다음과 같은 상황에서 보면

하위 컴포넌트는 상위 컴포넌트로 부터 a와 b라는 두 개의 props를 전달 받습니다.

 

하위 컴포넌트에서는 a와 b를 전달 받아 서로 다른 함수에 각각의 값을 계산하여 새로운 값을 return하는 역할을 합니다.

하위 컴포넌트는 props로 넘겨 받는 인자가 하나라도 변경 될 때마다 리렌더링 되기 때문에 props.a만 변경 되더라도 변경 되지 않는 props.b까지 다시 값이 전달되어 재계산 해야 합니다.

 

이럴 경우 "props.b는 이전에 계산된 값을 쓰면 되지 않나??"라는 의문이 들 수 있습니다.

 

useMemo를 설명 할 수 있는 간단한 예제를 보겠습니다.

App 컴포넌트는 Info 컴포넌트에게 사용자로부터 입력 받은 color와 movie 값을 props로 넘겨주고, Info 컴포넌트는 전달 받은 color와 movie를 적절한 한글로 바꾸어 문장으로 보여줍니다.

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

App 컴포넌트의 입력창에서 color 값만 바뀌어도 getColorKor, getMovieGenreKor 두 함수가 모두 실행되고 movie 값만 바뀌어도 두 함수 모두 실행 됩니다.

 

이때 useMemo를 사용하여 Info 컴포넌트의 코드에서 colorKor과 movieGenroKor을 계산하는 부분을 아래와 같이 적용한다면

import React, { useMemo } from "react";

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

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

useMemo를 사용하면 의존성 배열에 넘겨준 값이 변경 되었을 때만 메모리제이션된 값을 다시 계산합니다.

 

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

(위의 예제는 자식 컴포넌트가 부모 컴포넌트에서 props를 전달 받는 상황을 설명하고 있지만, 하나의 컴포넌트에서 두 개 이상의 state가 있을 때도 활용 할 수 있다.)

 


  [ useCallback ]  

"메모리제이션된 함수를 반환한다"라는 문장이 핵심입니다. 서론에서 useMemo와 useCallback을 배우기 전에 알아야 하는 것들 중 두번째에서 "컴포넌트가 렌더링 될 때마다 내부에 선언되어 있는 변수나 함수와 같은 표현식도 매번 다시 선언 되어 사용된다"라고 했는 데

 

useMemo를 설명하고 있는 같은 예제에서 App.js의 onChangeHandler 함수는 내부의 color, movie의 상태값이 변경 될 때마다 재선언 된다는 것을 의미합니다. 하지만 onChangeHandler 함수는 파라미터(인자)로 전달 받은 이벤트 객체(e)의 target.id 값에 따라 setState를 실행 해주기 때문에 첫 마운트 될 때 한번만 선언하고 재사용하면 되지 않을까요??

// 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);
  }, []);
  
  출처
  https://leehwarang.github.io/2020/05/02/useMemo&useCallback.html

App.js에서 onChangeHandler 함수의 선언부를 위와 같이 바꾸면 첫 마운트 될 때만 선언된다.

예시와 같은 이벤트 핸들러 함수나 API를 요청하는 함수를 주로 useCallback으로 선언하는 코드가 꽤 있다.

 

하지만, useMemo와 마찬가지로 비싼 계산을 하는 게 아니라면 권장하지 않는다.

위와 같은 수준의 함수 재선언을 막기 위해서 useCallback을 사용하는 것은 크게 의미 있지 않다. 

 

공식 문서에서도 언급 하듯이

"만약 자식 컴포넌트가 React.memo()를 사용하여 최적화가 되어 있고, 부모 컴포넌트에서 자식 컴포넌트에게 callback 함수를 props로 넘길 때, 부모 컴포넌트에서 useCallback으로 함수를 선언하는 것이 유용하다"는 의미이다.

(함수가 매번 재선언 되면 자식 컴포넌트는 넘겨 받은 함수가 달라졌다고 인식 하기 때문이다.)

 

  • React.memo()로 함수형 컴포넌트 자체를 감싸면 넘겨 받는 props가 변경 되지 않았을 때는 부모 컴포넌트가 메모리제이션된 함수형 컴포넌트(이전에 렌더링된 결과)를 사용한다.
  • 함수는 오로지 자기 자신만이 동일하기 때문에 상위 컴포넌트에서 callback 함수를 (같은 함수이더라고) 재선언 한다면(리렌더링 된다면) props로 callback 함수를 넘겨 받는 자식 컴포넌트 입장에서는 props가 변경 되었다고 인식한다.

+ 추가적으로 React에서 렌더링 성능 최적화 하는 방법

 

React 렌더링 성능 최적화하는 7가지 방법 (Hooks 기준)

오늘은 그동안 React를 공부하고 알아왔던, class기반이 아닌 hooks 기반의 성능 최적화에 대한 방법들을 포스팅 하고자 한다.먼저 컴포넌트의 리렌더링 되는 조건은 아래와 같다.부모에서 전달받은

velog.io