[React] useCallback hook / useMemo hook

SuamKang·2023년 7월 21일
0

React

목록 보기
23/34
post-thumbnail

컴포넌트의 랜더링의 최적화를 적용했던 React.memo에서 한계점이 들어났었다.

useMemo


useCallback 훅을 알아보기 전에 useMemo에 대해서 집고 넘어가는게 좋을것 같다.
이때 React.memo와 useMemo가 헷갈릴 수 있는데 이 차이를 알아보고 넘어가자


React.memo vs useMemo


React.memo 와 useMemo는 서로 다른 역할을 하는 메소드이다.

1. React.memo

: React.memo는 리액트의 고차 컴포넌트(HOC, Higher Order Component)이고, 이는 컴포넌트 로직을 재사용하기 위한 React의 고급기술과 더불어 함수형 컴포넌트의 리랜더링 성능을 최적화하는데 사용된다.

고차 컴포넌트(HOC)는 React API의 일부가 아니며, React의 구성적 특성에서 나오는 패턴이다.

이 메소드를 사용하면 해당 컴포넌트의 props가 변경되지 않는 이상 이전 랜더링한 결과를 그대로 재사용하고, 변경되었을 경우만 리랜더링 해준다.

ex)

import React from 'react';

const MyComponent = React.memo(({ prop1, prop2 }) => {
  // 컴포넌트 로직
  return <div>{prop1} - {prop2}</div>;
});

2. useMemo

: useMemo는 리액트의 hook으로 특정 계산 결과값을 캐시하고, 의존성 배열에 속한 값의 변경 여부에 따라서 변경이 없다면 기존의 값(특정 계산 결과값)을 그대로 재사용할 수 있게 해준다.

이 메소드는 계산 비용이 높은 함수 호출이나, 연산 결과를 저장하고 해당 함수 또는 연산에 의존하는 컴포넌트가 리랜더링 될때마다 새로 계산하지 않도록 도와준다.
이는 컴포넌트 내에서 비용이 많이 드는 계산을 최적화 하는데 사용된다.

ex)

import React, { useMemo } from 'react';

const MyComponent = ({ list }) => {

  const expensiveCalculation = useMemo(() => {
    // 계산 로직
    return list.map(item => item * 2);
  }, [list]);

  return <div>{expensiveCalculation}</div>;
};

useMemo 사용은

const result = useMemo(() => calculate(value), [value]);

특정 함수의 실행은 특정계산 값(value)이 바뀔때만 실행될 수 있도록 useMemo로 감싸고, 변경될 특정값은 두번째 인자 의존성 배열로 추가한다.
또한 이 기능은 memoization 알고리즘을 통해 수행된다.

Memoization
: 기존 수행한 연산의 결과값을 메모리에 저장해두고 동일한 입력이 들어올때면 그 이전 값을 재활용하는 프로그래밍 기법이다. => 중복 연산과정 생략 가능 => 앱 성능 최적화!


요약

React.memo는 컴포넌트 자체의 리렌더링을 최적화는 데 사용되고,
useMemo는 특정 계산 결과를 캐시하여 연산 비용을 최적화하는 데 사용된다.
둘은 서로 다른 상황에서 다른 목적으로 사용되며, 상황에 맞게 적절하게 활용하면 좋다.



useCallback


: useCallback 훅은 기본적으로 컴포넌트 실행 전반에 걸쳐 함수를 저장하여 재사용 할 수 있도록 돕는 훅이다.

이는 useMemo와 마찬가지로 해당 컴포넌트가 리랜더링 될때, 그 함수가 의존하고 있는 값(props or state)이 바뀌지 않는다면, 기존 함수를 계속해서 반환한다.

만약 바뀌게 된다면 바뀐 값을 통해 함수가 참조되는것이다. 두번째 인자로 해당 함수를 감싸고있는 컴포넌트로부터 전달받는 모든것을 사용할 수 있고, 이를 의존성 배열에 추가한다.

const addHandler = useCallback(() => x + y, [x,y]);

이를 통해 함수를 메모리에 저장하고 실행때마다 재생성할 필요가 없어진다.
이렇게 되면 동일한 함수객체가 메모리의 동일한 위치(주소)에 저장되므로 비교작업이 가능해진다.

이 비교를 쉬운 예시로 들어보면,

let obj1 = {};
// undefined

let obj2 = {};
// undefined

obj1 === obj2
// false

obj2 = obj1 
//  {}, -> 같은 메모리안에 위치하게 함

obj1 === obj2
// true

이런 식으로 두 obj를 같은 객체로 간주시키는 것과 비슷하게
useCallback이 동작하게 되는것이다.


다시 전에 들었던 예시 코드와 컴포넌트를 살펴보자.

App.js

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

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

function App() {
  const [showParagraph, setShowParagraph] = useState(false);

  console.log("App Running");

  const toggleHandler = useCallback(() => {
    setShowParagraph((prev) => !prev);
  }, [])
  
  return (
    <div className="app">
      <h1>Hi there!</h1>
      <DemoOutput show={false} />
      <Button type="button" onClick={toggleHandler}>
        Toggle Paragraph
      </Button>
    </div>
  );
}

export default App;

DemoOutput.js

import React from "react";

const DemoOutput = (props) => {
  console.log("DemoOutput Running");
  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);

컴포넌트로 돌아가서
App 컴포넌트가 다시 실행되면 useCallback 훅이 리액트 내부 메모리에 저장된 함수를 찾아서 함수가 실행될 때마다 이를 재사용할 수 있게 하는것이다.

위 App컴포넌트에서 해당 함수(toggleParagraphHandler)는 의존성으로 setShowParagraph 업데이트 함수를 추가할 수도 있지만, 리액트가 스스로 이 업데이트 함수를 바꾸지 않으며 useCallback을 통해 이전과 동일한 함수 객체임을 보장하므로 의존성 배열에 굳이 넣지 않아도 된다.


따라서,
위처럼 설정을 다시 한 후에 브라우저 콘솔을 다시 살펴보면

첫 랜더링을 제외하고 버튼을 다시 클릭하게 되면
App컴포넌트만 랜더링이 되고 나머지 자식 컴포넌트들은 더이상 콘솔에 로그되어지지 않는걸 볼 수 있다.

이렇게 해서 리랜더링 시, 자바스크립트 언어 특성상 전달하는 객체(함수)값이 비교연산(diffing)할때 같은 내용이더라도 다르게 인식하는 부분때문에 useCallback이 그점을 파악하여 이전 함수랑 같다고(동일한 메모리위치에저장된 객체) 보증해주기 때문에 의존성 배열값이 바뀔때만 랜더링 되도록 할 수 있어 최적화 작업을 진행할 수 있는것이다.

profile
마라토너같은 개발자가 되어보자

0개의 댓글