리액트 useMemo 정리

버건디·2022년 12월 13일
0

리액트

목록 보기
31/58
post-thumbnail

리액트에서 상태 관리를 할때 보통 useState와 useEffect 를 사용한다.

하지만 위의 hook 들은 상태가 변경될때마다 리렌더링 되어서 다른 요소들한테까지 영향을 끼칠 수 있다.

❗️ 함수형 컴포넌트는 함수다❗️

함수형 컴포넌트가 렌더링이 된다는 것은, 함수가 호출이 된다는 뜻이고 함수가 호출되면 그 안에 있는 변수들은 모두 초기화 된다.

function Add () {
  let a = 1;
  
  a++;

  console.log(a);
};

Add(); // 2 
Add(); // 2

위처럼 함수 안에서 a라는 변수가 두번 호출 되어도, 결국 값은 똑같은 2가 된다.

함수가 다시 호출될때 내부 변수가 초기화 되기 때문이다.


컴포넌트는 자신의 state 가 변경되거나, 부모에서 받아온 props가 변경될때마다 리렌더링 된다.

하지만 state1, state2, state3이 존재하는 상태에서 state1을 변경시켜주었는데 state2, state3도 재계산된다면??

이는 성능저하를 불러 일으킬 수 있다.

이럴때 사용하는 것이 useMemo 이다


🔍 useMemo 란?

useMemo 의 Memo는 Memoization이다.

- Memoization(메모이제이션)이란?

메모이제이션은 컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다. 동적 계획법의 핵심이 되는 기술이다.

즉 동일한 값을 반복적으로 호출해야한다면, 해당 값을 메모리에 저장해놓고 필요할때마다 다시 계산하는게 아니라 메모리에서 꺼내서 사용하는 것이다.

즉, 자주 사용하는 값을 캐싱해두었다가 그 값이 필요해질때마다 꺼내서 쓰는 것을 의미한다.

  const value = useMemo(() => {
		// 리턴하는 값
  }, []);

useMemo는 두가지 인자를 받는데, 첫번째 인자에는 값을 리턴하는 콜백함수, 두번째 인자로는 의존성 배열이다.

배열 안에 있는 값이 업데이트 될때마다 콜백함수를 다시 호출하는 것이다.

메모이제이션 되어있는 값을 업데이트 해서 다시 메모이제이션을 해두는 것이다.

이렇게 되면 useMemo를 각각 값마다 사용해주어서 성능 최적화를 하면 되지않을까? 라는 생각이 들었는데,

컴퓨터내에서의 캐시 메모리도 비용이 비싼것처럼, useMemo의 메모이제이션도 마찬가지로 값을 재활용 하기 위해 따로 메모리를 할당하는 것이기 때문에 적재적소에 사용하지 않는다면 비효율적일 수 있다.

import { useState } from "react";

const hardCalculate = (number) => {
  console.log("어려운 계산");
  for (let i = 0; i < 10000000; i++) {
    // 지연시간
  }

  return number + 10000;
};

const easyCalculate = (number) => {
  console.log("쉬운 계산");
  return number + 1;
};

function App() {
  const [hardNumber, setHardNumber] = useState(1);
  const [easyNumber, setEasyNumber] = useState(1);

  const hardSum = hardCalculate(hardNumber);
  const easySum = easyCalculate(easyNumber);

  return (
    <>
      <h1>어려운 계산기</h1>

      <input
        type={"number"}
        value={hardNumber}
        onChange={(e) => {
          setHardNumber(parseInt(e.target.value));
        }}
      ></input>

      <span> + 10000 = {hardSum}</span>

      <h1>쉬운 계산기</h1>

      <input
        type={"number"}
        value={easyNumber}
        onChange={(e) => {
          setEasyNumber(parseInt(e.target.value));
        }}
      ></input>

      <span> + 10000 = {easySum}</span>
    </>
  );
}

export default App;

위의 코드에서 hardCalculate는 무거운 값을 담은 함수고, easyCalculate는 값을 바로 리턴해주는 함수인데,

쉬운 계산기 안에 있는 input 값을 변경할때 컴포넌트자체가 다시 렌더링이 되기 때문에, hardCalculate 함수도 다시 불려오는 문제가 발생한다.

쉬운 계산 인풋을 변경하는데도 어려운 계산 함수 또한 실행이 된다.

// const hardSum = hardCalculate(hardNumber);

  const hardSum = useMemo(() => {
   return hardCalculate(hardNumber);
  }, [hardNumber]);

이럴때 useMemo를 통해서 감싸준다면, hardNumber 값이 변경 될때에만 hardCalculate 함수가 실행 되도록 할 수 있다.

만약에 hardNumber가 변경되지 않았는데 렌더링이 된다면, 그전에 가지고 있던 hardSum의 값을 재사용하게 된다.

useMemo로 감싸주니 쉬운 계산의 인풋을 변경할때 easySum 값만 다시 렌더링 되는것을 볼 수 있다.


import { useEffect, useState } from "react";

function App() {
  const [number, setNumber] = useState(0);
  const [isKorea, setIsKorea] = useState(true);

  const location = {
    country: isKorea ? "한국" : "외국",
  };

  useEffect(() => {
    console.log("useEffect 호출");
  }, [location]);

  return (
    <>
      <h2>하루에 몇끼 먹어요?</h2>
      <input
        type={"number"}
        value={number}
        onChange={(e) => {
          setNumber(e.target.value);
        }}
      ></input>
      <hr />

      <h2>어느 나라에 있어요?</h2>
      <p>나라 : {location.country}</p>
      <button
        onClick={() => {
          setIsKorea(!isKorea);
        }}
      >
        비행기 타자
      </button>
    </>
  );
}

export default App;

만약에 useEffect의 dependency가 객체 타입이라면,

객체 타입은 원시타입과 다르게 내부의 값이 같다고 해서 값이 같은게 아니다.

서로 다른 메모리 주소를 참조하고 있기 때문이다.

const a =1;
const b= 1;
a === b; // true

const a = {
    number : 1
};

const b = {
    number : 1
};

a === b // false

dependency 가 객체이므로, 컴포넌트가 재렌더링이 될때 객체의 주소도 매번 바뀌게 되어서 useEffect로 감싸줬다고 하더라도 다시 재렌더링이 되는것이다.

이럴때 useMemo를 사용해서 메모이제이션을 한다면 문제를 해결할 수 있다.

  const location = useMemo(() => {
    return { country: isKorea ? "한국" : "외국" };
  }, [isKorea]);

  useEffect(() => {
    console.log("useEffect 호출");
  }, [location]);
profile
https://brgndy.me/ 로 옮기는 중입니다 :)

0개의 댓글