[React] Hooks | useMemo를 이용한 최적화 방법

Re_Go·2024년 6월 16일
0

React

목록 보기
10/12
post-thumbnail
post-custom-banner

1. useMemo란?

useMemo란 컴포넌트가 리렌더링 될 때 렌더링 되는 부분만 반영하여 불필요한 함수의 계산을 방지하는 효율화 작업을 지원합니다.

그럼 다음 기본 틀을 한 번 살펴보겠습니다.

const memoizedValue = useMemo(() => {
  // 계산 비용이 높은 연산을 수행하여 결과 값을 반환하는 코드
  return result;
}, [dependency1, dependency2, ...]);

useMemo는 useEffect와 그 틀이 비슷한데요. 우선 첫번째 매개변수에는 계산 비용, 즉 연산을 수행할 때 비용을 많이 먹는 실행 코드문을 입력하고, 두번째 매개변수로는 의존성 배열의 값이 바뀔 때마다, 그러니까 해당 부분이 렌더링 될 때에만 앞서 입력한 첫번째 매개변수(코드)가 실행되어 그 결과값을 반환하게 하는 훅인데요.

차이점이 있다면 useEffect는 의존성 배열의 값이 변화될 때만 특정 렌더링 시점(라이프 사이클의 특정 시점)에서 부수 효과를 만들어 내는 것이고, useMemo는 의존성 배열의 값이 변화될 때만 특정 코드의 연산을 수행해 불필요한 연산 호출을 방지한다는대에 차이점이 있습니다.

이렇게 말씀드리면 감이 안오실 텐데요. 코드 비교를 통해 한 번 그 부분을 살펴보겠습니다.

2. useMemo를 사용할 때

import React, { useReducer, useRef, useMemo, useEffect } from 'react';

const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + action.value,
        incrementCount: state.incrementCount + 1
      };
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - action.value,
        decrementCount: state.decrementCount + 1
      };
    case 'SET_INPUT_VALUE':
      return { ...state, inputValue: action.value };
    case 'RESET_COUNTS':
      return {
        count: 0,
        inputValue: '', 
        incrementCount: 0,
        decrementCount: 0,
        resetCount: state.resetCount + action.value,
      };
    default:
      return state;
  }
};

function Counter() {
  const initialState = {
    count: 0,
    inputValue: '',
    incrementCount: 0,
    decrementCount: 0,
    resetCount: 0
  };

  const [state, dispatch] = useReducer(reducer, initialState);
  const { count, inputValue, incrementCount, decrementCount, resetCount } = state;
  const inputRef = useRef(null);

  const onClickButton = (val) => {
    dispatch({ type: val > 0 ? 'INCREMENT' : 'DECREMENT', value: Math.abs(val) });
  };

  const onEnterPress = (e) => {
    if (e.key === 'Enter') {
      dispatch({ type: 'SET_INPUT_VALUE', value: inputRef.current.value });
    }
  };

  const resetCounts = () => {
    dispatch({ type: 'RESET_COUNTS', value : 1 });
  };

// useMemo로 메모이제이션된 함수
  const memoCount = useMemo(() => {
    console.log('Resetting...');
    return resetCount;
  }, [resetCount]);

  // useEffect를 사용하여 렌더링 시점을 콘솔에 출력
  useEffect(() => {
    console.log('Component rendered');
  });

  return (
    <div>
      <p>Input value: {inputValue}</p>
      <button onClick={() => onClickButton(1)}>Increment</button>
      <button onClick={() => onClickButton(-1)}>Decrement</button>
      <button onClick={resetCounts}>Reset Counts</button>
      <div>
        <input ref={inputRef} type="text" placeholder="Type something" onKeyDown={onEnterPress} />
      </div>
      <p>Count: {count}</p>
      <p>Increment Count: {incrementCount}</p>
      <p>Decrement Count: {decrementCount}</p>
      <p>Reset Count: {memoCount}</p>
    </div>
  );
}

export default Counter;

위의 코드는 이전 시간에 사용했던 Counter 코드에서 Reset 버튼을 추가하고, 초기값과 상태값과 리듀서 함수에 추가한 코드인데요.

이때 reset 버튼을 누르면 그 누른 횟수를 저장하는 상태값 resetCount를 받아오는 함수의 리턴값을 출력하고자 하는데요. 이때 위에 사용된 useMemo 코드를 살펴보면 다음과 같습니다.

const memoCount = useMemo(() => {
    console.log('Resetting...');
    return resetCount;
  }, [resetCount]);
  ㆍ
  ㆍ
  ㆍ
<p>Reset Count: {memoCount}</p>

위의 useMemo의 첫번째 매개변수에는 resetCount 상태값을 반환하는 코드가, 두번째 의존성 배열에는 resetCount 상태값이 지정되어 있는데요.

그래서 사용자가 reset버튼을 제외한 나머지 버튼을 아무리 눌러도 해당 useMemo의 함수도 렌더링 되지 않다가, reset 버튼을 누를 때 렌더링 되는 resetCount(의존성 배열의 값)의 변화에 의해 해당 콜백 함수가 그제서야 실행되는 모습을 확인할 수 있습니다.

  1. 초기 화면

  1. reset 버튼을 제외한 나머지 버튼을 누를 때

  1. reset 버튼을 누를 때에야 호출되는 (연산되는) useMemo의 콜백함수

3. useMemo를 사용하지 않을 때

만약 위의 코드에서 useMemo가 아니라 그냥 함수를 호출하도록 하면 어떻게 될까요?

다음과 같이 코드를 재구성 해보겠습니다.

import React, { useReducer, useRef } from 'react';

const reducer = (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {
        ...state,
        count: state.count + action.value,
        incrementCount: state.incrementCount + 1
      };
    case 'DECREMENT':
      return {
        ...state,
        count: state.count - action.value,
        decrementCount: state.decrementCount + 1
      };
    case 'SET_INPUT_VALUE':
      return { ...state, inputValue: action.value };
    case 'RESET_COUNTS':
      return {
        count: 0,
        inputValue: '',
        incrementCount: 0,
        decrementCount: 0,
        resetCount: state.resetCount + action.value,
      };
    default:
      return state;
  }
};

function Counter() {
  const initialState = {
    count: 0,
    inputValue: '',
    incrementCount: 0,
    decrementCount: 0,
    resetCount: 0
  };

  const [state, dispatch] = useReducer(reducer, initialState);
  const { count, inputValue, incrementCount, decrementCount, resetCount } = state;
  const inputRef = useRef(null);

  const onClickButton = (val) => {
    dispatch({ type: val > 0 ? 'INCREMENT' : 'DECREMENT', value: Math.abs(val) });
  };

  const onEnterPress = (e) => {
    if (e.key === 'Enter') {
      dispatch({ type: 'SET_INPUT_VALUE', value: inputRef.current.value });
    }
  };

  const resetCounts = () => {
    dispatch({ type: 'RESET_COUNTS', value: 1 });
  };

  // resetProcess 함수를 정의하여 매번 호출되는 것을 확인
  const resetProcess = () => {
    console.log('Resetting...');
    return resetCount;
  };

const resetRecord = resetProcess();

  return (
    <div>
      <p>Input value: {inputValue}</p>
      <button onClick={() => onClickButton(1)}>Increment</button>
      <button onClick={() => onClickButton(-1)}>Decrement</button>
      <button onClick={resetCounts}>Reset Counts</button>
      <div>
        <input ref={inputRef} type="text" placeholder="Type something" onKeyDown={onEnterPress} />
      </div>
      <p>Count: {count}</p>
      <p>Increment Count: {incrementCount}</p>
      <p>Decrement Count: {decrementCount}</p>
      <p>Reset Count: {resetRecord}</p>
    </div>
  );
}

export default Counter;

위 코드에서 useMemo 대신 해당 함수를 일반적인 방법으로 아래와 같이 작성해 보았는데요.

const resetProcess = () => {
    console.log('Resetting...');
    return resetCount;
  };

const resetRecord = resetProcess();
ㆍ
ㆍ
ㆍ
<p>Reset Count: {resetRecord}</p>

이렇게 될 경우 useMemo를 사용하지 않은 것에 비해 reset 버튼 뿐만 아니라 다른 버튼을 누를 때도 마찬가지로 렌더링이 발생할 때마다 해당 함수가 불필요하게 실행되는 것을 확인할 수 있습니다.

1 초기 화면

  1. reset 버튼을 제외한 나머지 버튼을 누를 때에도 함수 호출을 확인할 수 있습니다.

이처럼 useMemo를 사용할 때와 사용하지 않을 때를 간략하게나마 알 수 있었는데요.

다만 해당 예제로 useMemo의 사용 전후를 명확히 아는 것은 어려워서 좀 더 명확한 사용법에 대해서는 다음에 다른 예제를 예로 들어 내용을 수정하도록 하겠습니다.

profile
인생은 본인의 삶을 곱씹어보는 R과 타인의 삶을 배워 나아가는 L의 연속이다.
post-custom-banner

0개의 댓글