본문 바로가기
Study/react.js

[React] React hooks 정리 part.2 useMemo(최적화) feat. React.memo

by 박히밍 2023. 2. 28.
반응형

 

[React] React hooks 정리 part.2 useMemo(최적화) feat. React.memo

 

 

[React] React hooks 정리 part.2 useMemo(최적화) feat. React.memo

React Hooks 정리 중! 이번 파트에는 리액트 최적화를 위한 useMemo를 파헤쳐보자 :)

 

 

 

들어가기 전

다른 hooks들이 궁금하다면 하단으로 ㄱㄱ!

 

 

useState

https://heeeming.tistory.com/entry/React-Hook-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85%EC%9D%98-state-%EA%B4%80%EB%A6%AC-%ED%95%A8%EC%88%98-useState-%ED%95%A8%EC%88%98%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EB%B3%B4%EC%9E%90

 

[React Hook] 리액트 훅의 state 관리 함수 useState() 함수를 사용해보자.

[React Hook] 리액트 훅의 state 관리 함수 useState() 함수를 사용해보자. 오늘은 리액트 훅 중 하나인 useState() 함수 사용법을 알아보려 한다. 오늘 글에서는 깊은 동작원리에 대해서는 다루지 않을 예

heeeming.tistory.com

 

 

useEffect /  useRef / useContext + Context API

https://heeeming.tistory.com/entry/React-React-hooks-%EC%A0%95%EB%A6%AC-part1-useState-useEffect-useRef-useContext-Context-API

 

[React] React hooks 정리 part.1 - useState, useEffect, useRef, useContext + Context API

[React] React hooks 정리 part.1 - useState, useEffect, useRef, useContext + Context API 매일 React hook에게 다져지는 내 자신이 안타까워 나는 오늘로서 hook을 잘게 다지기로 하였다. 뚜둔. 하지만 너무 길어서 최적

heeeming.tistory.com

 

 

 

 


 

 

 

🔗 useMemo(최적화) feat. React.memo가 뭘까?

 

동일한 값을 리턴하는 함수를 반복적으로 호출해야 한다면 맨 처음 호출 할 때 해당 값을 메모리에 저장해서 필요할때 꺼내서 사용하는 방법으로 간단히 말하면 자주 필요한 값을 맨 처음 계산할때 캐싱을해 둬서 그 값이 필요할 때 계산을 하는 것이 아니라 캐싱을 해둬서 꺼내서 사용한다는 뜻

 

 

useMemo의 개념

  • 성능을 최적화할 때 사용한다.
  • memo는 memorized의 약자이다.
  • 첫번째 인수에는 함수, 두번째 인수에는 배열을 넣어주면 된다.
    • 두번째 인수에 넣어준 배열의 값이 바뀔때만 함수가 실행된다.
    • 그렇지 않다면 이전의 값을 재사용한다.
    • 기존의 state의 값과 변화된 state의 값을 비교한 후 연산이 오래걸리는 함수를 실행할 지 말지 결정하기 때문에 성능 최적화에 사용된다.

 

 

 

1.  사용법(문법)

import { useMemo } from "react";

//연산이 오래 걸리는 어떠한 결과 값을 리턴해주는 함수
const func = () => {
    return //연산이 오래 걸린 결과 값
  }
  const result = useMemo(() => {
    return func();
  }, [state]) // []배열은 dependency array로 해당 state의 값이 변경 될 때만 재연산한다.

 

⭐️  useRef와 useMemo의 차이점

  • useRef: 일반 값을 기억
  • useMemo: 연산이 오래 걸리는 복잡한 함수의 결과값을 기억

 

⭐️  useEffect와 useMemo의 실행 시점 차이

  • useEffect: 컴포넌트 렌더링이 완료된 뒤 실행
  • useMemo: 컴포넌트 렌더링이 진행 중일 때 실행

 

useMemo 사용 예제

//App.js
import "./App.css";
import { useState } from "react";

const hardCalculate = (number) => {
  console.log("어려운 계산 시작!");
  for (let i = 0; i < 999999999; i++) {} // 생각하는 시간
  return number + 10000;
};
function App() {
  const [hardNumber, setHardNumber] = useState(1);
  const hardSum = hardCalculate(hardNumber);

  return (
    <div className="App">
      <div>
        <h3>어려운 계산기</h3>
        <input
          type="number"
          value={hardNumber}
          onChange={(e) => {
            setHardNumber(parseInt(e.target.value));
          }}
        />
        <span> + 10000 = {hardSum}</span>
      </div>
    </div>
  );
}

export default App;

위와 같이 작성할 경우, hardCalculate 함수 내부의 for문에 의해 return 값이 늦게 리턴되기 때문에 input의 값을 1씩 증가시킬 경우 hardSum 의 값이 화면에 1초씩 늦게 렌더링 될 것이다.

또한 hardNumber state의 값이 변경 될 때마다 hardCalculate 함수가 반복적으로 불려지게 되고 hardSum이라는 변수에 반복적으로 값을 할당해준다. 따라서 hardNumber state가 변경 될 때마다 우리의 App컴포넌트는 재렌더링이 일어날 것이다.

렌더링이 느리다

 

어려운 계산기 콘솔 출력 모습

이제 어려운 계산기 밑에 쉬운 계산기를 추가해보자.

 

//App.js
import "./App.css";
import { useState } from "react";

const hardCaluculate = (number) => {
  console.log("어려운 계산 시작!");
  for (let i = 0; i < 999999999; i++) {} // 생각하는 시간
  return number + 10000;
};
const easyCaluculate = (number) => {
  console.log("짱 쉬운 계산 시작!");
  return number + 1;
};
function App() {
  const [hardNumber, setHardNumber] = useState(1);
  const [easyNumber, setEasyNumber] = useState(1);
  const hardSum = hardCaluculate(hardNumber);
  const easySum = easyCaluculate(easyNumber);

  return (
    <div className="App">
      <div>
        <h3>어려운 계산기</h3>
        <input
          type="number"
          value={hardNumber}
          onChange={(e) => {
            setHardNumber(parseInt(e.target.value));
          }}
        />
        <span> + 10000 = {hardSum}</span>
      </div>
      <div>
        <h3>짱 쉬운 계산기</h3>
        <input
          type="number"
          value={easyNumber}
          onChange={(e) => {
            setEasyNumber(parseInt(e.target.value));
          }}
        />
        <span> + 1 = {easySum}</span>
      </div>
    </div>
  );
}

export default App;

위와 같이 작성 후 쉬운계산기의 카운트를 올려보면 어려운 계산기와는 다르게 빠르게 계산이 될거라 생각하지만

쉬운 계산기의 easyNumber state가 변경 될 때마다 App컴포넌트가 다시 렌더링이 되면서 변수가 초기화가 되기 때문에 hardCaluculate 함수가 다시 불려와지면서 딜레이가 생기는 것이다.

쉬운 계산기도 느리다.
어려운 계산도 함께 렌더링 되고있다.

만약 쉬운 계산기가 실행 될 때 hardSum 함수를 실행시키지 않도록 하려면 useMemo를 사용하면 된다.

 

//App.js
import "./App.css";
import { useMemo, useState } from "react";

const hardCaluculate = (number) => {
  console.log("어려운 계산 시작!");
  for (let i = 0; i < 999999999; i++) {} // 생각하는 시간
  return number + 10000;
};
const easyCaluculate = (number) => {
  console.log("짱 쉬운 계산 시작!");
  return number + 1;
};
function App() {
  const [hardNumber, setHardNumber] = useState(1);
  const [easyNumber, setEasyNumber] = useState(1);
  //const hardSum = hardCaluculate(hardNumber);
  const hardSum = useMemo(() => {
    return hardCaluculate(hardNumber);
  }, [hardNumber]);
  const easySum = easyCaluculate(easyNumber);

  return (
    <div className="App">
      <div>
        <h3>어려운 계산기</h3>
        <input
          type="number"
          value={hardNumber}
          onChange={(e) => {
            setHardNumber(parseInt(e.target.value));
          }}
        />
        <span> + 10000 = {hardSum}</span>
      </div>
      <div>
        <h3>짱 쉬운 계산기</h3>
        <input
          type="number"
          value={easyNumber}
          onChange={(e) => {
            setEasyNumber(parseInt(e.target.value));
          }}
        />
        <span> + 1 = {easySum}</span>
      </div>
    </div>
  );
}

export default App;

 

쉬운 계산기를 눌렀을 경우 어려운 계산기의 hardNumber state가 변경 되지 않은 것을 확인한 useMemo hook이 hardSum함수를 실행시키지 않고 기존의 연산값을 그대로 사용한 것을 확인할 수 있고

어려운 계산기를 눌렀을 경우 hardNumber state가 변경 되었기 때문에 useMemo hook이 hardSum 함수를 실행 시킨 것을 확인 할 수 있다.

빨라짐

 

 

 


 

 

Feat. 자식 컴포넌트의 재 렌더링을 막아보자 (React.memo)

부모 컴포넌트가 재렌더링이 될 때마다, 재렌더링이 일어날 필요 없는 자식 컴포넌트까지 렌더링 되지 않도록 막아서 재렌더링이 필요한 순간에만 재렌더링이 되도록 해보자.

//App.js
import "./App.css";
import { memo, useState } from "react";

const Child = () => {
  console.log("자식 컴포넌트 렌더링!");
  return <div>자식녀석 나타났소.</div>;
};

function App() {
  const [count, setCount] = useState(1);
  return (
    <div className="App">
      <h1>내 자식 녀석 어디갔누?</h1>
      <Child />
      렌더링 증가
      <input type="number" value={count} onChange={() => setCount(count + 1)} />
    </div>
  );
}

export default App;

 

재렌더링 화면

위와같이 작성하면 input의 값이 증가할 때마다 count state가 변경되고 App 컴포넌트가 재렌더링이 되면서

자식컴포넌트 또한 같이 재렌더링이 일어나는 것을 확인할 수 있을 것이다.

 

 

 

//App.js
import "./App.css";
import { memo, useState } from "react";

const Child = memo(() => {
  console.log("자식 컴포넌트 렌더링!");
  return <div>자식녀석 나타났소.</div>;
});

function App() {
  const [count, setCount] = useState(1);
  return (
    <div className="App">
      <h1>내 자식 녀석 어디갔누?</h1>
      <Child />
      렌더링 증가
      <input type="number" value={count} onChange={() => setCount(count + 1)} />
    </div>
  );
}

export default App;

위와같이 Child 컴포넌트에 memo를 이용해 감싸주면 부모컴포넌트가 재렌더링이 되더라도

Child컴포넌트의 렌더링 전과 후의 상태 비교 후 상태 변화가 있다면 재 렌더링을 진행하고, 없다면 재 렌더링을 하지 않기 때문에 메모리를 효율적으로 관리 할 수 있게 된다.

 

재렌더링이 일어나지 않음

 

 

언제 React.memo를 사용해야 할까?

  • 같은 props로 렌더링이 자주 일어나는 컴포넌트
    • 부모 컴퍼넌트에 의해 하위 컴퍼넌트가 같은 props를 전달받아 리렌더링 될 때와 같은 경우
  • 무겁고 비용이 큰 연산이 필요한 컴포넌트의 경우

 

 

 

 

 


 

 

참고자료

유튜브 별코딩 리액트 훅 

https://youtu.be/G3qglTF-fFI

 

 

반응형