useCallback

leekoby·2023년 3월 22일
0

React

목록 보기
5/10
post-thumbnail

🔧변경내용🔨

제목날짜내용
발행일23.03.22

📌들어가기에 앞서

해당 포스트는 useCallback에 대해 학습한 내용을 정리하며 기록한 내용입니다.




React Hook은 렌더링 최적화를 위한 Hook도 존재하는데, useCallbackuseMemo가 바로 그 역할을 하는 Hook라고 전에 말했다.

이번에는 useCallback Hook에 대해 배워보고, useMemouseCallback의 차이에 대해 학습하도록 하자.




📖 useCallback이란?

useCallback 또한 useMemo와 마찬가지로 메모이제이션 기법을 이용한 Hook

useMemo는 값의 재사용을 위해 사용하는 Hook이라면,

useCallback은 함수의 재사용을 위해 사용하는 Hook

아래 코드를 보자.

function Calculator({x, y}){
	const add = () => x + y;
	return <>
      <div>
			{add()}
      </div>
  </>;
}

현재 이 Calculator 컴포넌트 내에는 add라는 함수가 선언이 되어 있는 상태

add 함수는 props로 넘어온 xy 값을 더해 <div> 태그에 값을 출력

이 함수는 해당 컴포넌트가 렌더링 될 때마다 새롭게 만들어질 것

useMemo와 마찬가지로, 해당 컴포넌트가 리렌더링 되더라도 그 함수가 의존하고 있는 값인 xy가 바뀌지 않는다고 생각해자.

그렇다면 함수 또한 메모리 어딘가에 저장해 뒀다가 다시 꺼내서 쓸 수 있을 것이다.

이때 useCallback Hook을 사용하면 그 함수가 의존하는 값들이 바뀌지 않는 한 기존 함수를 계속해서 반환

xy값이 동일하다면 다음 렌더링 때 이 함수를 다시 사용.

/* useCallback를 사용하기 전에는 꼭 import해서 불러와야 합니다. */
import React, { useCallback } from "react";

function Calculator({x, y}){
	const add = useCallback(() => x + y, [x, y]);
	return <>
      <div>
		{add()}
      </div>
  </>;
}

useCallback만 사용해서는 useMemo에 비해 최적화를 느낄 수는 없다.

왜냐하면 useCallback은 함수를 호출을 하지 않는 Hook이 아니라, 그저 메모리 어딘가에 함수를 꺼내서 호출하는 Hook이기 때문

따라서 단순히 컴포넌트 내에서 함수를 반복해서 생성하지 않기 위해서 useCallback을 사용하는 것은 큰 의미가 없거나 오히려 손해인 경우도 있다.

그러면 언제 사용하는 게 좋을까?

자식 컴포넌트의 props로 함수를 전달해줄 때 이 useCallback을 사용하기가 좋다.




📖 useCallback과 참조 동등성

useCallback은 참조 동등성에 의존

React는 JavaScript 언어로 만들어진 오픈소스 라이브러리이기 때문에 기본적으로 JavaScript의 문법을 따른다.

JavaScript에서 함수는 객체다.

객체는 메모리에 저장할 때 값을 저장하는 게 아니라 값의 주소를 저장하기 때문에, 반환하는 값이 같을 지라도 일치연산자로 비교했을 때 false가 출력된다.

아래 코드를 보자.

function doubleFactory(){
    return (a) => 2 * a;
}
  
const double1 = doubleFactory();
const double2 = doubleFactory();
  
double1(8); // 16
double2(8); // 16
  
double1 === double2;  // false
double1 === double1;  // true

double1double2는 같은 함수를 할당했음에도 메모리 주소 값이 다르기 때문에 같다고 보지 않는다.

JavaScript에서 함수는 객체다.

따라서 두개의 함수는 동일한 코드를 공유하더라도 메모리 주소가 다르기 때문에, 메모리 주소에 의한 참조 비교 시 다른 함수로 본다.

이는 React 또한 같다.

React는 리렌더링 시 함수를 새로이 만들어서 호출을 한다.

새로이 만들어 호출된 함수는 기존의 함수와 같은 함수가 아니다.

그러나 useCallback을 이용해 함수 자체를 저장해서 다시 사용하면 함수의 메모리 주소 값을 저장했다가 다시 사용한다는 것과 같다고 볼 수 있다.

따라서 React 컴포넌트 함수 내에서 다른 함수의 인자로 넘기거나 자식 컴포넌트의 prop으로 넘길 때 예상치 못한 성능 문제를 막을 수 있다.




📖 useCallback를 이용하여 앱 최적화하기

import { useCallback, useState } from "react";
import "./styles.css";
import List from "./List";
import { useEffect } from "react";

export default function App() {
  const [input, setInput] = useState(1);
  const [light, setLight] = useState(true);

  const theme = {
    backgroundColor: light ? "White" : "grey",
    color: light ? "grey" : "white"
  };
  
  // const getItems = () => {
  //   return [input + 10, input + 100];
  // };
  const getItems = useCallback(() => {
    return [input + 10, input + 100];
  }, [input]);

  const handleChange = (event) => {
    if (Number(event.target.value)) {
      setInput(Number(event.target.value));
    }
  };

  return (
    <>
      <div style={theme} className="wall-paper">
        <input
          type="number"
          className="input"
          value={input}
          onChange={handleChange}
        />
        <button
          className={(light ? "light" : "dark") + " button"}
          onClick={() => setLight((prevLight) => !prevLight)}
        >
          {light ? "dark mode" : "light mode"}
        </button>
        <List getItems={getItems} />
      </div>
    </>
  );
}

기본 상태에서는 input의 숫자가 변경되거나 button을 눌렀을 때에도 "아이템을 가져옵니다"가 콘솔에 출력되었다.

이렇게 동작하는 이유는 버튼을 누를 때도 앱이 리렌더링되므로, App 내부의 getItems() 함수가 다시 만들어졌기 때문이다.

새로 만들어진 함수는 이전의 함수와 참조 비교시 다른 함수이기 때문에 계속해서 리렌더링이 발생한다.

useCallback을 이용하여 함수 자체를 저장해서 버튼을 눌러도 콘솔에 출력되지 않게 수정했다.




📚 레퍼런스

코드스테이츠 수업자료

useCallback

0개의 댓글