[React] useMemo !

홍인열·2022년 3월 28일
0

원하는 화면과 기능은 만들수 있는데 왜 렌더 횟수가 더 많은지 모를경우가 많았다. 이제 렌더링을 최적화에 대해서 공부한다 먼저 useMemo!

useMemo

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
// 출처 공식문서

useMemo는 첫번째 인자로 callback 함수를 두번째인자로 의존성 배열을 받는다. 그리고 첫번째 인자인 callback함수의 return 값을 메모이제이션한다. 두번째인자인 의존선배열에 있는 값이 봐끼는 경우만 useMemo가 제 실해되어 다시 메모이제이션된다.

Memoization ?

메모이제이션은 의존성배열이 봐끼지않으면 해당 값을 저장하고 있는것이다. 이저장된 값은 리렌더가 되더라도 새로 생성성되지않고 저장된 값을 다시 사용하게된다.
공식문서 예시에도 나와있듯이 computeExpensiveValue, 즉 복잡하고 무거운 계산의 결과값같은 경우 렌더시마다 동일한 값이지만 새로 계산을 하는것은 비효율 적이기때문에 메모이제이션을 통해 효율적으로 사용할 수 있도록 하는것이다.

사용 경우 1

복잡한 계산이 필요한 값을 이용할때, 이 값은 매번 렌더가 될 때마다 복잡한 계산이 실행된 결과 값인경우!
결과값은 그대론데 매번 새로 계산해준다면 너무나 비효율적일 것!!

import React, { useState } from 'react';

const computeExpensiveValue = (count) => {
  for (let i = 0; i < 999999999; i++) {}
  console.log('복잡한계산');
  return count;
};

const Test = () => {
  const [count, setCount] = useState(0);
  const [capture, setCapture] = useState(0);

  const currentCount = computeExpensiveValue(count);

  const countUp = () => {
    setCount(count + 1);
  };

  const captureCount = () => {
    setCapture(count);
  };

  return (
    <div>
      <p>Current Count : {currentCount}</p>
      <p>Capture Count : {capture}</p>
      <button onClick={countUp}>count Up</button>
      <button onClick={captureCount}>capture count</button>
    </div>
  );
};

export default Test;

capture count 버튼을 누르면 Current Count를 Capture로 지정해주면 되는 간단한 동작이다.
하지만! 함수 컴포넌트가 리렌더링이된다는건 함수가 다시 실행 된다는 것! ➪ 함수내의 변수는 모두 초기화된다.
currentCount도 값이 봐끼지 않았지만 초기화된후 재할 당이 되면서 복잡한계산이 재실행 된다.😱
결과적으로 변하지 않은 값이지만 계산을 다시하는 아주 비효율적인 동작을 하게 되는 것이다.

useMemo 사용!

import React, { useMemo, useState } from 'react'; // useMemo import

const computeExpensiveValue = (count) => {
  for (let i = 0; i < 999999999; i++) {}
  console.log('복잡한계산');
  return count;
};

const Test = () => {
  const [count, setCount] = useState(0);
  const [capture, setCapture] = useState(0);

  // useMemo를 사용하여 변수지정, 의존성으로 count 지정.
  const currentCount = useMemo(() => computeExpensiveValue(count), [count]);

  const countUp = () => {
    setCount(count + 1);
  };

  const captureCount = () => {
    setCapture(count);
  };

  return (
    <div>
      <p>Current Count : {currentCount}</p>
      <p>Capture Count : {capture}</p>
      <button onClick={countUp}>count Up</button>
      <button onClick={captureCount}>capture count</button>
    </div>
  );
};

export default Test;

currentCount값이 useMemo의 반환 값으로 지정을 해두었다. 의존성으로 count를 지정해두어 count가 변화하면 useMemo가 재실행 된다. gif를 보면 count Up 버튼을 눌럿을때 복잡한계산이 실행되고, capture count를 누르면 복잡한 계산이 재실행 되지 않는걸 확인 할 수있다. useMemo를 사용하면 리렌더가 되더라도 해당 변수가 초기화 되지않고 메모리에 저장되어있는 값을 그대로 사용하게된다.
불필요한 계산이 줄었다!!

사용 경우 2

import React, { useEffect, useMemo, useState } from 'react';

const UseMemo2 = () => {
  const [random, setRandom] = useState(0);
  const [count, setCount] = useState(0);

  const fruits = ['apple', 'banana', 'melon', 'peach', 'mango'];

  const fruit = { fruit: fruits[random] };


  const shuffleHandler = () => {
    setRandom(Math.floor(Math.random() * 5));
  };

  const countUp = () => {
    setCount(count + 1);
  };

  useEffect(() => {
    console.log(`useEffect 실행`);
  }, [fruit]);

  return (
    <div>
      <p>Current Fruit : {fruit.fruit}</p>
      <p>Count : {count}</p>
      <button onClick={shuffleHandler}>shuffle!</button>
      <button onClick={countUp}>countUp!</button>
    </div>
  );
};

export default UseMemo2;

useEffectfruit를 의존성 배열로 갖고 있다. shuffle 버튼을 통해 fruit 값을 변경하면 useEffect가 실행되는 것 당연하다. 하지만! countUp을 클릭했을때도 useEffect가 실행되는 것을 확인 할 수 있다.
fruit는 객체다. 컴포넌트가 리렌더 될때마다 새로 참조값이 생성되게 된다. 객체가 가지고 있는 키-값 은 동일하지만 참조주소가 달라지는 것이다. 이렇기 때문에 countUp 버튼을 눌렀을때 리렌더가되고, fruit는 새로운 참조 주소값을 갖는 새로운 객체가 되기 때문에 useEffect가 의도치 않게 실행 되는 것이다.

useMemo 사용!

import React, { useEffect, useMemo, useState } from 'react';

const UseMemo2 = () => {
  const [random, setRandom] = useState(0);
  const [count, setCount] = useState(0);

  const fruits = ['apple', 'banana', 'melon', 'peach', 'mango'];

  //useMemo 사용
  const fruit = useMemo(
    () => ({
      fruit: fruits[random],
    }),
    [random]
  );

  const shuffleHandler = () => {
    setRandom(Math.floor(Math.random() * 5));
  };

  const countUp = () => {
    setCount(count + 1);
  };

  useEffect(() => {
    console.log(`useEffect 실행`);
  }, [fruit]);

  return (
    <div>
      <p>Current Fruit : {fruit.fruit}</p>
      <p>Count : {count}</p>
      <button onClick={shuffleHandler}>shuffle!</button>
      <button onClick={countUp}>countUp!</button>
    </div>
  );
};

export default UseMemo2;

useMemo를 사용해서 fruit를 할당하고, 의존성 배열에 random을 추가했다. 이렇게 되면 countUp버튼 클릭에 따른 리렌더시 furit객체는 memoization 되어 새로운 객체 참조 주소값이 생성되지않아서 useEffect 실행되지 않아 렌더 최적화가 가능해진다!

profile
함께 일하고싶은 개발자

0개의 댓글