useMemo, useCallback, React.memo

yu_yu·2020년 11월 9일
1

useMemo와 useCallback은 이전 값을 기억해서 성능을 최적화하는 용도로 사용된다.

useMemo

useMemo 훅은 계산량이 많은 함수의 반환값을 재활용하는 용도로 사용된다.

import React, {useMemo} from 'react';
import { runExpensiveJob } from './util';

function MyComponent({v1, v2}) {
  const value = useMemo(() => runExpensiveJob(v1,v2), [v1, v2]);
  return <p>{`value is ${value}`}</p>;
}

useMemo 훅의 첫 번째 매개변수로 함수를 입력한다. useMemo 훅은 이 함수가 반환한 값을 기억한다. useMemo 훅의 두 번째 매개변수는 의존성 배열이다. 의존성 배열의 값이 변경되지 않으면 이전에 반환된 값을 재사용한다. 만약 배열의 값이 변경됐으면 첫 번째 매개변수로 입력된 함수를 실행하고 그 반환값을 기억한다.

useCallback

useCallback은 리액트의 랜더링 성능을 위해 제공되는 훅이다. 컴포넌트가 렌더링될 때마다 새로운 함수를 생성해서 자식 컴포넌트의 속성값으로 입력하는 경우가 많다.

다음은 useCallback 훅이 필요한 예이다.

import React, {useSatate} from 'react';
import {saveToServer} from './api';
import UserEdit from './UserEdit';

function Profile(){
  const [name, setName] = useState('');
  const [age, setAge] = useState(0);
  
  return (
    <div>
      <p>{`name is ${name}`}</p>
      <p>{`age is ${age}`}</p>
      <UserEdit 
        onSave={() => saveToServer(name, age)}
        setName={setName}
        setAge={setAge}
      />
    </div>
  );
}

Profile 컴포넌트가 렌더링될 때마다 UserEdit 컴포넌트의 onSave 속성값으로 새로운 함수가 입력된다. 따라서 UserEdit 컴포넌트에서 React.memo를 사용해도 onSave 속성값이 항상 변경되고 그 때문에 불필요한 렌더링이 발생한다. onSave 속성값은 name이나 age값이 변경되지 않으면 항상 같아야 한다.
useCallback 훅을 사용하면 불필요한 렌더링을 막을 수 있다. 다음은 useCallback 훅을 사용한 코드다.

function Profile(){
  const [name, setName] = useState('');
  const [age, setAge] = useState(0);
  const onSave = useCallback(() => saveToServer(name, age), [name, age]);
  return (
    <div>
    	<p>{`name is ${name}`}</p>
    	<p>{`age is ${age}`}</p>
	<UserEdit onSave={onSave} setName={setName} setAge={setAge} />
    </div>
  );
}

이전에 onSave 속성값으로 전달했던 것과 같은 함수를 useCallback 훅의 첫번째 매개변수로 입력한다. useCallback 훅의 두 번째 매개변수는 의존성 배열이다. 의존성 배열이 변경되지 않으면 이전에 생성한 함수가 재사용된다. 따라서 name과 age값이 변경되지 않으면, UserEdit 컴포넌트의 onSave 속성값으로 항상 같은 함수가 전달된다.

React.memo

React는 먼저 컴포넌트를 렌더링(rendering) 한 뒤, 이전 렌더된 결과와 비교하여 DOM 업데이트를 결정한다. 만약 렌더 결과가 이전과 다르다면, React는 DOM을 업데이트한다. 다음 렌더링 결과와 이전 결과의 비교는 빠르다. 하지만 어떤 상황에서는 이 과정의 속도를 좀 더 높일 수 있다.컴퍼넌트가 React.memo()로 래핑 될 때, React는 컴퍼넌트를 렌더링하고 결과를 메모이징(Memoizing)한다. 그리고 다음 렌더링이 일어날 때 props가 같다면, React는 메모이징(Memoizing)된 내용을 재사용한다.

export function Movie({ title, releaseDate }) {
  return (
    <div>
      <div>Movie title: {title}</div>
      <div>Release date: {releaseDate}</div>
    </div>
  );
}

export const MemoizedMovie = React.memo(Movie);

MemoizedMovie의 렌더링 결과는 메모이징 되어있다. 만약 title이나 releaseData 같은 props가 변경 되지 않는다면 다음 렌더링 때 메모이징 된 내용을 그대로 사용하게 된다.

메모이징 한 결과를 재사용 함으로써, React에서 리렌더링을 할 때 가상 DOM에서 달라진 부분을 확인하지 않아 성능상의 이점을 누릴 수 있다.

비교 함수 커스터마이징

React.memo의 두 번째 매개변수로 속성값 비교 함수를 입력한다. 속성값 비교 함수에서 참을 반환하면 이후 단계를 생략하고 이전 렌더링 결과를 재사용한다. 만약 속성값 비교 함수를 입력하지 않으면 얕은 비교를 수행하는 기본 함수가 사용된다.

비교 방식을 수정하고 싶다면 React.memo() 두 번째 매개변수로 비교함수를 만들어 넘겨주면 된다.

function moviePropsAreEqual(prevMovie, nextMovie) {
  return (
    prevMovie.title === nextMovie.title &&
    prevMovie.releaseDate === nextMovie.releaseDate
  );
}

const MemoizedMovie2 = React.memo(Movie, moviePropsAreEqual);

언제 React.memo()를 써야할까

컴퍼넌트가 같은 props로 자주 렌더링되거나, 무겁고 비용이 큰 연산이 있는 경우, React.memo()로 컴퍼넌트를 래핑할 필요가 있다.

언제 React.memo()를 사용하지 말아야 할까

위에서 언급한 상황에 일치하지 않는다면 React.memo()를 사용할 필요가 없을 가능성이 높다.

렌더링될 때 props가 다른 경우가 대부분인 컴포넌트를 생각해보면, 메모이제이션 기법의 이점을 얻기 힘들다.
또한, 기술적으로는 가능하지만 클래스 기반의 컴퍼넌트를 React.memo()로 래핑하는것은 적절하지 않다. 클래스 기반의 컴퍼넌트에서 메모이제이션이 필요하다면 PureComponent를 확장하여 사용하거나, shouldComponentUpdate() 메서드를 구현하는 것이 적절하다.

profile
재밌게! 개발 탐구 생활

0개의 댓글