개발새발개발/React

[React] 최적화하기 (Optimization) - useMemo, memo, useCallback

birdsfoot 2025. 2. 19.

 

 

 

 

최적화(Optimization)

 

적용

1. 시기 : 하나의 프로젝트를 거의 완성한 상태에서 시행

  • 기능 구현 → 최적화
  • 최적화를 먼저 하면 기능 수정할 때 최적화가 풀리거나 꼬여서 작동을 안할 수도 있음

 

2. 적용 대상 : 꼭 최적화가 필요한 것에만 시행

  • 최적화도 마찬가지로 연산이 필요하기 때문에, 단순한 연산일 경우 그냥 리렌더링 되도록 두는게 더 빠를 수도 있음
  • TodoItem을 생성하는 것과 같이 유저의 행동에 따라 개수가 많아질 수 있는 컴포넌트
  • 혹은 함수들을 많이 가지고 있어 무거운 코드의 경우 적용하는 게 좋음 

 

 

useMemo

  • "메모이제이션" 기법을 기반으로 불필요한 연산을 최적화하는 리액트 훅
  • 반복되는 내용을 저장해뒀다가 필요할 때 꺼내 쓰는 느낌
  • useMemo를 사용하면 연산 자체를 메모이제이션 할 수 있고,
  • 특정 조건을 만족했을 때에만 결과 값을 다시 계산하도록 설정도 가능함

 

사용해보기

todoAnalyzedData 생성

  • 실습을 위해 List에 todoAnalyzedData 생성
import { useState, useMemo } from 'react';
(...)

const List = ({ todo, onUpdate, onDelete }) => {
  // 검색어 저장 변수
  const [search, setSearch] = useState('');
  const onChangeSearch = (e) => {...};
  // 검색 결과 찾는 함수
  const getSearchResult = () => {...};

  // todoAnalyzedData
  const { totalCount, doneCount, notDoneCount } = useMemo(() => {
    console.log('getAnalyzedData 호출');
    const totalCount = todo.length;
    const doneCount = todo.filter((item) => item.isDone).length;
    const notDoneCount = totalCount - doneCount;

    return {
      totalCount,
      doneCount,
      notDoneCount,
    };
  }, [todo]);

  return (
    <div className="List">
      <h3>Todo List🌱</h3>
      <div>
        <div>total : {totalCount}</div>
        <div>done :{doneCount}</div>
        <div>notDone :{notDoneCount}</div>
      </div>
	(...)
    </div>
  );
};

export default List;

 

  • 현재는 useMemo가 적용된 코드이지만, useMemo를 적용하지 않으면 분석과 관계없는 동작을 수행할 때에도 컴포넌트 전체가 리렌더링되는 것을 확인할 수 있음
  • useMemo를 사용하면 컴포넌트의 비효율적인 리렌더링을 없애 최적화된 코드 작성이 가능해짐

 

useMemo 사용

1. import 해오기

import { useMemo } from 'react';

 

 

2. useMemo 호출

 

useMemo(()=> {}, [])
  • 첫 번째 인수로는 콜백 함수, 두 번째 인수로는 배열을 넣음
  • 이 배열은 의존성 배열(deps)로, deps가 수정될 때 useMemo의 콜백 함수가 실행됨
  • 라이프사이클에서 배웠던 useEffect의 훅과 비슷함
    • useEffect : deps 값이 바뀌면 콜백 함수를 다시 실행

 

const a = useMemo (()=> {return 1}, [])
  • 콜백 함수가 반환하는 값을 useMemo가 그대로 반환하기 떄문에 변수에 담아서 사용 가능

 

3. 구조분해 할당으로 필요한 값 가져오기

  // todoAnalyzedData
  const { totalCount, doneCount, notDoneCount } = useMemo(() => {
    console.log('getAnalyzedData 호출');
    const totalCount = todo.length;
    const doneCount = todo.filter((item) => item.isDone).length;
    const notDoneCount = totalCount - doneCount;

    return {
      totalCount,
      doneCount,
      notDoneCount,
    };
  }, [todo]);

 

  • totalCount, doneCount, notDoneCount 값만 사용할 것이므로 구조분해할당으로 값 담을 수 있음

 

 

memo

  • 컴포넌트를 인수로 받아 최적화된 컴포넌트로 만들어 반환
  • 부모 컴포넌트가 리렌더링 되더라도 자신이 받는 props가 변경되지 않으면 리렌더링이 일어나지 않음 
const MemoizedComponent = memo(Component)

 

 

사용해보기

1. import memo

import { memo } from 'react';

 

 

2. export memo

export default memo(Header);

 

 

예시

import './Header.css';
import { memo } from 'react';

const Header = () => {
  return (
    <div className="Header">
      <h3>오늘은🗓️</h3>
      <h1>{new Date().toDateString()}</h1>
    </div>
  );
};

export default memo(Header);

 

 

 

 

객체타입에서의 memo

  • 객체타입은 주소값을 가지고 있음.
  • 주소값은 리렌더링 될 때마다 바뀜
  • 그러므로 얕은 비교를 통해 값을 비교하는 memo는 이걸 걸러낼 수 없어 최적화가 안됨
  • 즉, 위와 같이 memo를 붙인다고 해서 memo되지 않음

 

해결방법 1

export default memo(TodoItem, (prevProps, nextProps) => {
  if (prevProps.id !== nextProps.id) return false;
  if (prevProps.isDone !== nextProps.isDone) return false;
  if (prevProps.content !== nextProps.content) return false;
  if (prevProps.date !== nextProps.date) return false;

  return true;
});

 

  • memo 함수의 두번째 인수로 콜백함수를 전달해서
  • 리렌더링 될 때마다 prevProps(과거의 props)와 nextProps(현재의 props)를 전달해서 함수의 반환값에 따라 변동 여부를 확인하는 방법
  • data가 수정되거나 개수가 많을 때 비효율적

 

해결방법2

useCallback 사용하기

 

 

useCallback

 

1. import useCallback

import { useCallback } from 'react';

 

 

2. useCallback 호출하기

const func = useCallback(()=> {},[])

 

  • 첫 번째 인수로는 최적화하고 싶은(불필요한 재생성을 막고 싶은) 함수를 넣기
  • 두 번째 인수로는 deps 배열을 넣어주기
  • 첫 번째 인수로 전달한 콜백 함수를 그대로 생성해서 반환하기 때문에 변수에 담을 수 있음
  • 두 번째 인수인 deps가 변경될 때만 콜백 함수 실행 
  • deps를 빈 배열로 두면 처음 마운트 될 때 딱 한번만 실행 됨

 

3. export 하기

export default memo(TodoItem);
  • 이전처럼 props를 받을 컴포넌트에서 memo메서드만 작성해서 export 하면 끝

 

 

Todolist app에 적용해보기

import { useState, useRef, useReducer, useCallback } from 'react';
(...)

const mockTodo = [...];

function reducer(state, action) {...}

function App() {
  const [state, dispatch] = useReducer(reducer, mockTodo);
  const idRef = useRef(3);

  const onCreate = useCallback((content) => {
    dispatch({
      type: 'CREATE',
      data: {
        id: idRef.current++,
        content: content,
        isDone: false,
        createdDate: new Date().getTime(),
      },
    });
  }, []);

  const onUpdate = useCallback((targetId) => {
    dispatch({
      type: 'UPDATE',
      targetId: targetId,
    });
  }, []);

  const onDelete = useCallback((targetId) => {
    dispatch({
      type: 'DELETE',
      targetId: targetId,
    });
  }, []);

  return (
    <div className="App">
	(...)
    </div>
  );
}

export default App;

댓글