나의 개발일지

[React] react-memo / useMemo / useCallback 복습하기 본문

React/복습

[React] react-memo / useMemo / useCallback 복습하기

heew0n 2023. 12. 22. 10:38

react-memo

 

  • memo(React.memo) : 컴포넌트를 캐싱

부모 컴포넌트가 리렌더링 되면 자식 컴포넌트는 모두 리렌더링 되는데

자식 컴포넌트는 바뀐 것이 없는데 리렌더링이 된다면 너무 비효율적임

이러한 문제점을 돕는 게 바로 memo

 

만들어 볼 예제

 

 

src > App.jsx

import { useState } from "react";
import Box1 from "./components/Box1";
import Box2 from "./components/Box2";
import Box3 from "./components/Box3";

function App() {
  console.log("App 컴포넌트가 렌더링되었습니다");
  const [count, setCount] = useState(0);

  const onPlusButton = () => [setCount(count + 1)];
  const onMinusButton = () => [setCount(count - 1)];
  return (
    <>
      <h2>카운트 예제입니다!</h2>
      <p>현재 카운트 : {count}</p>
      <button onClick={onPlusButton}>+</button>
      <button onClick={onMinusButton}>-</button>
      <div style={{ display: "flex", marginTop: "10px;" }}>
        <Box1 />
        <Box2 />
        <Box3 />
      </div>
    </>
  );
}

export default App;

 

 

 

 

App컴포넌트의 카운트 함수를 실행했는데 자식으로 있는 Boxes도 렌더링이 되었다

이를 위해 Boxes에 React.memo(Box(n)) 을 사용하면 된다!

 

 

 

src > components > Box1

import React from "react";

const style = {
  width: "100px",
  height: "100px",
  backgroundColor: "pink",
  color: "white",
};

function Box1() {
  console.log("Box1 컴포넌트가 렌더링되었습니다");

  return <div style={style}>Box1</div>;
}

export default React.memo(Box1);

 

 

 

 

src > components > Box2

import React from "react";

const style = {
  width: "100px",
  height: "100px",
  backgroundColor: "lightgreen",
  color: "white",
};

function Box2() {
  console.log("Box2 컴포넌트가 렌더링되었습니다");

  return <div style={style}>Box2</div>;
}

export default React.memo(Box2);

 

 

 

 

 

src > components > Box3

import React from "react";

const style = {
  width: "100px",
  height: "100px",
  backgroundColor: "lightblue",
  color: "white",
};

function Box3() {
  console.log("Box3 컴포넌트가 렌더링되었습니다");

  return <div style={style}>Box3</div>;
}

export default React.memo(Box3);

 

 

 

 

 

 

처음에만 렌더링이 되고 이후엔 렌더링이 되지 않음을 확인할 수 있다

 

 


 

 

useMemo

: memo --> memoization (기억하다)

  특별한 곳에 메모리를 저장한다

  이미 저장된 값을 단순히 꺼내와서 쓴다 (캐싱한다)

 

 

import React, { useState } from "react";

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

  const location = isKorea ? "한국" : "외국";

  return (
    <div>
      <h2>하루에 몇 끼 먹어요?</h2>
      <input
        type="number"
        value={number}
        onChange={(e) => {
          setNumber(e.target.value);
        }}
      />
      <hr />
      <h2>어느 나라에 있어요?</h2>
      <p>나라 : {location}</p>
      <button
        onClick={() => {
          setIsKorea(!isKorea);
        }}
      >
        비행기 타자
      </button>
    </div>
  );
}

export default App;

 

 

type이 number로 된 input창과 비행기 타자라는 버튼을 누르면 외국 또는 한국으로 바뀌는 토글 버튼이 있다

여기서 인풋창을 조작할 때는 비행기 타자 버튼이 같이 리렌더링 되지 않게 하기 위해서

useEffect를 쓴다

 

 

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

 

 

location이 변경될 때만 리렌더링이 된다.

하지만 만약,

const location = isKorea ? "한국" : "외국";

이 부분이 객체라면 상황은 완전 달라진다

 

 

 

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

 

 

input창의 값이 변경됨에 따라 useEffect도 실행되었다

 

그 이유는

원시데이터 타입과 객체 타입의 메모리 저장 방식이 다르기 때문이다

 

 

 

 

 

원시데이터는 변수에 데이터가 바로 들어가지만 객체는 크키가 크기 때문에

메모리 상의 공간에 할당이 되고 그 메모리 주소가 변수에 할당이 되는 것이다

 

 

 

 

 

 

원시타입은 같은 데이터 값을 바라보고 있기 때문에 두 변수가 일치하지만

객체타입은 변수가 메모리 상의 주소로 저장이 되고 그 메모리 주소는 각각 다르므로 일치하지 않는다 

그래서 location의 객체도 같은 데이터 같지만 사실상 다른 값이므로

useEffect는 location이 참조하고 있는 주소가 바꼈다고 생각하여 리렌더링이 되는 것이다

이때 useMemo를 사용하여 location의 데이터를 저장하는 것이다

 

 

 

useMemo(( )=>{ } , [ ])

첫 번째 인자는 콜백함수, 두 번째 인자는 의존성 배열을 갖는다.

 

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

 

 

 

이제 바뀌지 않는 것을 확인할 수 있다!

 

 

 


 

 

 

참고한 영상 

 

https://www.youtube.com/watch?v=e-CnI8Q5RY4&list=PLZ5oZ2KmQEYjwhSxjB_74PoU6pmFzgVMO&index=6

 

 

 


 

useCallback

 

import React, { useState } from "react";

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

  const someFunction = () => {
    console.log(`someFunction: number: ${number}`);
    return;
  };

  return (
    <div>
      <input
        type="number"
        value={number}
        onChange={(e) => {
          setNumber(e.target.value);
        }}
      />
      <br />
      <button onClick={someFunction}>Call someFunc</button>
    </div>
  );
}

export default App;

 

 

 

input창에 입력된 숫자만큼 call someFunc 버튼을 누르면 콘솔창에 값이 찍히는 코드이다

 

 

  useEffect(() => {
    console.log("someFunction이 변경되었습니다.");
  }, [someFunction]);

 

 

 

 someFunction에 useEffect를 주었는데도 input의 값을 변경했더니 콘솔창에 찍히는 걸 확인할 수 있다

즉, 리렌더링이 계속 되고 있다

왜 그런걸까?

우리는 함수형 컴포넌트를 사용하고 있고, app이라는 함수가 호출될 때마다 정의돼있는 변수가 다시 초기화된다

someFunction도 함수객체를 담은 변수이기 때문에 초기화가 되어 리렌더링이 되는 것이다

또한 함수객체는 크기가 크기 때문에 변수에 바로 저장되는 것이 아니고 메모리 공간에 담기기 때문에 다른 데이터로 인식하여 우리가 원하는 결과가 나오지 않는 것이다

 

 

useCallback 사용하기

 const someFunction = useCallback(() => {
    console.log(`someFunction: number: ${number}`);
    return;
  }, []);

 

 

 

someFunction을 메모이제이션하여 저장한다 ( 재사용을 한다 )

더 이상 리렌더링되지 않는다.

하지만 또 하나의 문제점은 숫자를 변경하고 버튼을 눌렀을 때

콘솔창에 0이라고 출력이 된다

그 이유는 메모이제이션을 했을 때, number의 값이 0이었기 때문!!!

 

 

 

 

 const someFunction = useCallback(() => {
    console.log(`someFunction: number: ${number}`);
    return;
  }, [number]);

 

 

의존성 배열에 number를 넣어주어서 number의 값이 바뀔 때마다

리렌더링이 되는 것을 확인할 수 있다.

 

 

 

 


 

 

 

참고한 영상

https://www.youtube.com/watch?v=XfUF9qLa3mU

 

 

 

 

 


 

'React > 복습' 카테고리의 다른 글

[React] useContext 복습하기  (0) 2023.12.21
[React] 리액트 복습하기 (Props)  (0) 2023.11.04