본문 바로가기

React & React-native

< React > React.memo() / useCallback() 이란? / useMemo() vs useCallback() 의 차이

 

 

지난 글에서 React.memo( ) 를 사용해도 계속해서 컴포넌트가 재랜더링 될 때에 대해서 해결책을 이야기 해보려고 한다. ( 클로저가 무엇인지 알아야 이해하기 쉽다.)

 

제목에도 적혀있듯이 바로 useCallback() 을 사용하는 것이다!

 

 

useCallback( ) 이란?

- 컴포넌트 실행 전반에 걸쳐 함수를 저장(메모라이제이션)할 수 있게 해주는 훅

- 리액트에게 내가 어떤 함수를 저장할 것이고, 해당 함수를 재 생성할 필요가 없다고 알려줄 수 있다.

- 쉽게 이해하자면 함수가 저장된 메모리 위치 값을 통해 해당 주소에 저장된 함수를 꺼내 비교할 수 있게 해주는 것 같다. ( 원시타입과 참조타입의 차이를 생각해보자 )

 

 

사용하는 방법은 다음과 같다.

 

이전 글의 <app.js> 참고

 

  const toggleBtnHandler = useCallback(() => {
    if (allowToggle) {
      setActiveBtn((preveactiveBtn) => !preveactiveBtn);
    }
  }, [allowToggle]);

 

useCallback() 의 첫번째 인자로 함수를 전달하면, 그 함수를 저장하고

app() 컴포넌트가 다시 실행되면 useCallback()이 리액트가 저장한 함수를 찾아서 같은 함수 객체를 재사용한다.

 

+) js의 함수는 클로저이기 때문에 두번째 인자로 의존성을 설정해주지 않으면 처음 저장한 상태 값 그대로 굳어버린다. 그래서 함수 재생성이 필요할 때에는 두번째 인자 배열에 종속성을 추가해줘야한다.

 

 

< app.js >

 

import React, { useCallback, useState } from "react";

import "./App.css";
import Button from "./components/UI/Button/Button";

import Demooutput from "./components/Demo/DemoOutput";

function App() {
  const [activeBtn, setActiveBtn] = useState(false);
  const [allowToggle, setAllowToggle] = useState(false);

  // 버튼을 누를때마다 App컴포넌트 전체가 다시 랜더링됨
  console.log("APP RUNNING");

  // useCallback 덕분에 toggleBtnHandler가 메모리 안에서 항상 같은 객체임을 보증받음
  // js의 함수는 클로저 이기때문에 의존성에 값을 추가 안해주면 false인 채로 굳어버린다
  const toggleBtnHandler = useCallback(() => {
    if (allowToggle) {
      setActiveBtn((preveactiveBtn) => !preveactiveBtn);
    }
  }, [allowToggle]);

  const allowToggleHandler = () => {
    setAllowToggle(true);
  };

  return (
    <div className="app">
      <h1>Hi there!</h1>
      <Demooutput show={activeBtn}></Demooutput>
      <Button onClick={toggleBtnHandler}>Show What she Saying</Button>
      <Button onClick={allowToggleHandler}>Allow Toggling</Button>
    </div>
  );
}

export default App;

 

 

 

<Demooutput.js>

import React from "react";

// props로 받은 값에 따라 조건부로 랜더링 할 컴포넌트
const Demooutput = (props) => {
  console.log("Demooutput 실행");
  return <p>{props.show ? "This is new!" : ""}</p>;
};

export default React.memo(Demooutput);

 

 

 

<Button.js> - 디자인한 버튼을 재사용하기 위해 컴포넌트로 커스텀 한 것

import React from "react";

import classes from "./Button.module.css";

const Button = (props) => {
  console.log("BUTTON RUNNING");
  return (
    <button
      type={props.type || "button"}
      className={`${classes.button} ${props.className}`}
      onClick={props.onClick}
      disabled={props.disabled}
    >
      {props.children}
    </button>
  );
};

export default React.memo(Button);

 

 

그리고 이제 확인을 해보자

 

 

 

 

이런 화면인데 allow Toggling을 눌러서 토글버튼을 활성화 하게 해줘야 숨겨놓은 문장을 볼 수 있게 했다.

 

코드를 보면 알겠지만 Show What she saying 버튼은 allowToggle 값이 true여야 작동한다.

useCallback()에 의존성으로 allowToggle 을 추가해준 이유는 만약 설정해주지 않았다면, JS의 함수가 클로저이기 때문에 초기값으로 설정해준 false가 들어간 채로 함수가 굳어질 것이기 때문이다.토글허락 버튼을 눌러주면 show라고 적힌 버튼이 작동해야 하기 때문이다.

 

그래서 allowToggle 의 값이 false -> true로 바뀌게 되면 리액트가 useCallback() 으로 저장해둔 함수가 있는 곳으로 가서 비교를 한다음 변경사항이 있다면 변경한 후 저장을 해준다.

 

 

 

useMemo() 란?

- useCallback()과 유사하지만  메모라이제이션 해주는 것이 함수가 아닌 값이다.

- 컴포넌트 실행 전반에 걸쳐 값을 저장(메모라이제이션)할 수 있게 해주는 훅

 

 

나는 처음에  useMemo()를 보고 왜 useState같은 훅이 있는데 값을 저장하는 기능이 있는 훅이 왜 또 있을까? 하는 의문이 들었었다.

 

그럼 useMemo()는 언제쓸까?

 가정을 해보자, 부모 컴포넌트가 재 평가가 된다면 자식 컴포넌트도 마찬가지로 재평가가 된다.

그런데 자식 컴포넌트에 props로 배열을 전달하고 자식 컴포넌트에서 그 배열을 정렬하는 .sort()같이 연산에 많은 비용을 써야 하는 기능을 사용한다면?

props로 전달하는 배열이 매번 재 생성될 것이고 그럼 자식 컴포넌트에 있는 .sort()가 매번!! 다시!! 실행될 것이다.

 

그래서 자식 컴포넌트에서 연산을 한 결과 값을 useMemo()로 저장해 놓고 변경사항이 있을 때만 sort() 를 실행하게 해주는 것이다.

 

<예시>

import React, { useMemo } from "react";

const DemoList = (props) => {
  const { items } = props;
  const sortedList = useMemo(() => {
    return items.sort((a, b) => a - b);
  }, [items]);
  return (
    <div>
      <h2>{props.title}</h2>
      <ul>
        {sortedList.map((item) => {
          <li key={item}>{item}</li>;
        })}
      </ul>
    </div>
  );
};

 

요렇게! 그리고 props로 넘겨주는 배열이 객체이기 때문에 매번 재 생성되는 것을 잊으면 안된다.

그래서 부모 컴포넌트에서 넘겨주는 배열도 useMemo()를 사용해 재생성되지 않도록 해주는 걸 잊지말자!

 

 

 

728x90