[React] useMemo && useCallback && React.memo

3932 단어 취준ReactReact

useMemo && useCallback 이 필요한 이유(컴포넌트의 특성)

  1. 함수형 컴포넌트는 단지 jsx를 반환하는 함수임.
  2. 컴포넌트가 랜더링 된다는 것은 누군가가 함수(컴포넌트)를 호출해서 실행되는 것을 말함. 함수가 실행될 때마다 내부에 선언되어있던 표현식은 매번 다시 선언되어 사용됨.
  3. 컴포넌트는 자신의 state가 변경되거나, 부모의 props가 변경될 때마다 리랜더링 됨. 부모컴포넌트의 state가 변경되어도 자식컴포넌트도 리랜더링됨.(최적화해주지 않는다면)

useMemo

const memoizedValue = useMemo(() => computedExpensiveValue(a, b), [a, b]);

메모리제이션된 값을 반환하는 것이 핵심.

a, b 두 개의 props를 상위컴포넌트로부터 전달받는 하위컴포넌트가 있다고 가정하고, 하위컴포넌트는 a, b를 받아 서로 다른 함수로 각각의 값을 가공하여 새로운 값을 보여주는 역할을 함. 하위컴포넌트는 props로 넘겨받는 인자가 하나라도 변경되면 리랜더링 되는데, props.a만 변경됐는데 props.b도 함수를 호출해서 다시 계산하게됨.

import React, { useMemo } from "react";

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

이런 경우에 useMemo를 import해서 가공하는 함수와 의존성배열을 넘겨주면, 의존성배열에 넘겨준 값이 변경됐을 때만 메모리제이션된 값을 다시 계산함.

useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

메모리제이션된 함수를 반환하는 것이 핵심.

컴포넌트가 랜더링 될 때마다 내부에 선언되어 있던 표현식이 다시 선언되어 사용됨.(컴포넌트의 특성 2번) 컴포넌트 내부에 있는 함수는 변동이 없음에도 컴포넌트가 리랜더링 될 때마다 다시 선언됨.

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

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

이런 경우에 useCallback을 import해서 사용하던 함수의 실행문을 넣어주면 랜더링 될 때마다 선언되는 것을 피할 수 있음. 의존성 배열에 요소를 추가하면 해당 값이 변경될 때 재선언 가능.

상위컴포넌트의 함수가 매번 재선언되면, 내용이 같다고 하더라도 하위컴포넌트는 넘겨받는 함수가 달라졌다고 인식함. 따라서 하위컴포넌트가 React.memo() 등으로 최적화 되어있고, 그 하위 컴포넌트에게 callback 함수를 props로 넘길 경우에, 상위컴포넌트에서 useCallback으로 선언하는 것이 최적화에 도움됨.

React.memo()로 함수형 컴포넌트 자체를 감싸면 넘겨 받는 props가 변경되지 않았을 때는 상위 컴포넌트가 메모리제이션된 함수형 컴포넌트(이전에 렌더링된 결과)를 사용하게 됨.

더 알아보기

React.memo

상위컴포넌트가 리랜더링될 때 변동없는 하위컴포넌트가 다시 리랜더링을 막아 컴포넌트를 최적화 할 수 있음. 무거운 컴포넌트(계산수행, API요청, 브라우저 이벤트 리스닝 등)는 앱의 속도를 느려지게 하기 때문에 최적화해야 하는 것에 속함. 이를 위해 memo를 사용함.

// 하위컴포넌트
import React, { memo, useEffect } from "react";

function NormalComponent({ name }) {
  useEffect(() => { // 최적화되지 않으면 리랜더링 때마다 실행됨
    console.log("I rendered");
  });
  
  return <div>{name}</div>
}

export default memo(NormalComponent); // 컴포넌트를 memo()로 감싸줌

memo로 하위컴포넌트를 감싸면 이 컴포넌트에 보내지는 prop을 기억해 그 prop이 바뀔 때에만 컴포넌트가 랜더링됨.

즉, React.memo는 컴포넌트와 상관없는 것이라면 해당 컴포넌트는 랜더링 안되게끔 해줌.

React.memo는 훅이 아닌 HOC임. HOC란 Higher-Order Components의 약자로, 컴포넌트를 인자로 받아 새로운 컴포넌트를 반환하는 구조의 함수임. 클래스형 컴포넌트도 인자로 사용가능.(useMemo는 훅이기 때문에 함수형 컴포넌트만 사용가능)

const NameTag = React.memo(
  (props) => <div>{props.name}</div>
,
  (prevProps, nextProps) => prevProps.name === nextProps.name
)

두번째 인자로 boolean값을 반환하는 함수를 넣어주면 해당 함수의 결과가 false일 때만 랜더링하도록 지정할 수 있음.(비교방식)

정리

공통점 : 불필요한 랜더링 또는 연산을 제어하는 용도로 성능최적화가 목적임.
useMemo : 이전 값을 반환하는 훅, 이전 값을 기억해뒀다 조건에 따라 재활용하여 성능 최적화(연산량 절감과 리랜더링 방지)
useCallback : 함수 재생성을 방지
React.memo : 컴포넌트 리랜더링 방지, 클래스형 컴포넌트도 사용가능

좋은 웹페이지 즐겨찾기