본문 바로가기
📌 Front End/└ React

[React] 리액트 훅 - useMemo, useCallback(메모이제이션 값, 함수 반환)

by 쫄리_ 2024. 8. 30.
728x90
반응형

📌 useMemo, useCallback 훅

useMemo, useCallback은 모두 불필요한 렌더링 또는 연산을 제어하는 용도로

성능 최적화에 그 목적이 있다.

  • useMemo
    계산된 값을 기억해 두고, 
    의존성 배열에 있는 값이 바뀔 때만 새로 계산합니다.

  • useCallback
    함수를 기억해 두고, 
    의존성 배열에 있는 값이 바뀔 때만 새로 함수를 생성합니다.
useMemo(() => 함수, 의존성 배열);
useCallback (함수, 의존성 배열);

🤔 리렌더링(re-rendering)

React는 Virtual DOM을 사용하고 있다.
따라서 리렌더링이 되는 조건에 충족되는 상황이라면,
이전의 Virtual DOM과 현재의 Virtual DOM을 비교하여

변경된 값에 대해 DOM 트리를 업데이트 해준다.

 

🔎 React가 리렌더링 하는 조건

  • 자신의 컴포넌트의 상태(state)가 바뀔 때
  • 부모 컴포넌트로부터 새로운 props가 들어올 때 / 변경될 때
  • 부모 Component가 리렌더링될 때 (자식 Component도 리렌더링됨)

 

 


🤔 메모이제이션 (memoization)

과도한 리렌더링은 성능 저하의 원인이 된다.
따라서 필요할 때만 리렌더링될 수 있도록 하는 방법으로 useMemo나 useCallback을 사용할 수 있다.

해당 Hook을 이해하기 위해서는 '메모이제이션'이라는 개념을 먼저 이해해야 한다.

메모이제이션이란 기존에 수행한 연산의 결괏값을 어딘가에 저장해두고

동일한 입력이 들어오면 재활용하는 프로그래밍 기법을 말합니다.

 

memoization을 잘 적용하면 중복 연산을 피할 수 있기 때문에

메모리를 조금 더 쓰더라도 애플리케이션의 성능을 최적화할 수 있습니다.


🤔 useMemo, useEffect 차이

| useMemo

처리 시간이 오래 걸리는 함수일 경우 렌더링마다 계산하기에 비효율적이기 때문에

값을 기억해놓고 dependency가 변경되었을 경우에만 다시 계산해주는 기능이다.

 

| useEffect

api 호출, 타이머 등 렌더링 과정에서 한 번만 호출해도 될 기능들이 렌더링되어 실행되거나

호출과정에서 렌더링에 영향을 끼칠 수 있는 것들을 모아서 따로 처리하기 위한 기능이다.

 

| 결론 |

useMemo 렌더링 중에 실행

useEffect 렌더링 후에 실행


📌 useMemo

useMemo는 memoization된 값을 반환한다. 

 

📝 useMemo 형식

useMemo는 함수(callbackFn)를 호출하고 이 함수에서 리턴된 값을 리턴한다. 

useMemo를 호출할 때마다 dependencies를 먼저 확인한다. 

이때 dependencies에서 바뀐 값이 없다면 캐싱된 값(이미 계산된 값)을 리턴하고, 

바뀐 값이 존재한다면 다시 함수를 호출하고 새롭게 연산된 값을 리턴한다.

useMemo(() => callbackFn, [dependencies])

 

 

사용예시

계산이 복잡한 함수를 useMemo로 감싸주고, 결과 값을 return 해준다.

useMemo 함수가 그 값을 저장해두고 있다가

ex가 변경될 때만 복잡한 계산함수를 실행시켜 메모이제이션된 값을 업데이트 해준다.

useMemo를 사용하면 매 렌더링마다 함수를 호출하지 않고, 

연산에 필요한 값이 변경되는 필수적인 경우에만 연산을 수행하여 이러한 문제를 해결할 수 있습니다.

const [ex, setEx] = useState(0)

const 복잡한계산함수 = useMemo(() => {
  return 계산결과
},[ex])

 


📌 useCallback

useCallback은 memoization된 함수를 반환한다.

 

📝 useCallback 형식

useCallback은 함수(callbackFn)를 memoization하고 이 함수를 리턴한다. 

useCallback을 호출할 때마다 dependencies를 먼저 확인한다. 

의존성이 변경된 경우에만 memoization된 함수를 변경한다. 
useMemo와 useCallback은 값을 저장하느냐 함수를 저장하느냐의 차이이고 동작하는 원리는 같다.

useCallback(() => callbackFn, [dependencies])

 

 

🔎 useCallback은 새로운 함수를 반환

useCallback(() => {}, [ex])  ➡️  함수 () => {}를 기억합니다.

ex가 변할 때마다 useCallback은 새로운 함수를 반환해요.

즉, ex가 0일 때의 () => {}와   ex가 1일 때의 () => {}는 코드 모양은 같지만, 

메모리상에서는 서로 다른 함수입니다. 그래서 두 함수는 다른 함수로 취급됩니다.

const useCallbackReturn = useCallback(() => {}, [ex])

 

 

사용예시

useCallback을 사용하면 컴포넌트가 렌더링 되더라도 

함수가 의존하는 배열 내의 값이 변경되지 않으면 기존 함수를 재사용합니다.

const add = () => x + y; // useCallback 적용 전, 렌더링될 때마다 새로운 함수가 생성됨
const add = useCallback(() => x + y, [x, y]); // useCallback 적용, x 또는 y 값이 변경될 때만 새로운 함수가 생성됨

 

 

useCallback을 사용하면 fnOnclick 함수가 계속 새로 만들어지는 것을 방지할 수 있다.
ex가 변경될 때만 새로운 함수를 만들고, 그렇지 않을 경우엔 (메모이제이션된) 동일한 함수를 불러온다.
자식 컴포넌트에 함수를 props로 넘겨줄 때, (자식 컴포넌트의) 불필요한 렌더링을 방지할 수 있다.

const ParentComp() => {
  const [ex, setEx] = useState(0)

  // ▶ useCallback 사용 : ex가 변경될 때만 <ChildComp/>가 리렌더링된다
  const fnOnclick = useCallback(() => {
    setEx(ex + 1)
  },[ex])
  
  return (
    <>
      <h1>Parent Component</h1>
      <p>Ex: {ex}</p>
      <ChildComp fnOnclick={fnOnclick} />
    </>
  )
}

 


✏️ useEffect, useCallback 함께 사용하기

🔎 전체 흐름 요약

  • 상태 초기화
    • user 상태가 null로 시작됩니다.
  • fetchUser 함수 정의
    • fetchUser 함수는 서버에서 사용자 데이터를 가져오고, userId가 바뀔 때만 새로운 함수가 생성
  • useEffect 실행
    • 컴포넌트가 렌더링될 때 useEffect가 실행됩니다.
    • fetchUser를 호출하여 데이터를 가져오고, setUser를 통해 user 상태를 업데이트합니다.
  • 함수 메모리 주소
    • useCallback 덕분에 fetchUser 함수가 계속 같은 메모리 주소를 가집니다.
    • 그래서 useEffect가 불필요하게 반복되지 않으며, userId가 바뀔 때만 fetchUser를 다시 호출

 

🔎 코드 설명

1. 컴포넌트와 상태

  • Profile 컴포넌트
    • 이 컴포넌트는 userId라는 값을 받습니다. 이 값은 서버에서 사용자 데이터를 가져오는 데 사용
  • 상태 변수
    • const [user, setUser] = useState(null);
      • user라는 상태 변수를 생성합니다. 처음에는 null로 설정됩니다.
      • setUser는 user 상태를 업데이트하는 함수입니다.

2. fetchUser 함수

  • fetchUser 함수
    • 서버에서 사용자 데이터를 가져오는 함수입니다.
    • useCallback을 사용하여 이 함수를 기억합니다. userId가 바뀔 때만 새로운 함수를 생성
  • useCallback의 역할
    • useCallback은 함수의 메모리 주소를 유지하면서, userId가 변경될 때만 새로운 함수를 만든다.
    • [userId]는 의존성 배열입니다. userId가 바뀔 때만 fetchUser 함수가 새로 만들어집니다.
  • fetchUser의 동작
    • fetch를 사용하여 서버에서 userId에 해당하는 데이터를 요청합니다.
    • 응답을 JSON 형식으로 변환한 후, user라는 데이터를 추출하여 반환합니다.

3. useEffect 훅

  • useEffect 훅
    • 컴포넌트가 렌더링된 후에 특정 작업을 실행하도록 하는 훅입니다.
  • useEffect의 동작
    • useEffect[fetchUser]라는 의존성 배열을 가지고 있습니다.
    • 이 배열에는 fetchUser 함수가 들어 있습니다. 
      따라서 fetchUser가 변경될 때만 useEffect가 실행됩니다.
    • fetchUser를 호출하고, 반환된 사용자 데이터를 setUser로 상태를 업데이트합니다.
import React, { useState, useEffect, useCallback } from "react";

function Profile({ userId }) {
  const [user, setUser] = useState(null);

  // useCallback으로 같은 fetchUser 함수를 계속 사용하도록 함
  const fetchUser = useCallback(() =>
    fetch(`https://your-api.com/users/${userId}`)
      .then((response) => response.json())
      .then(({ user }) => user),
      [userId] // userId가 바뀔 때만 함수를 새로 만듭니다.
  );

  // 이제 fetchUser 함수가 같은 주소를 가지기 때문에 useEffect가 계속 반복되지 않아요.
  useEffect(() => {
    fetchUser().then((user) => setUser(user));
  }, [fetchUser]);

  // ...
}

 


✏️ useMemo, useEffect, useCallback 함께 사용하기

  • useCallback
    • fetchUser 함수userId가 변경될 때만 새로 생성됩니다.
    • 데이터 가져오는 비동기 함수를 메모이제이션하여 성능을 최적화합니다.
  • useEffect
    • fetchUser를 호출하여 사용자 데이터를 가져오고 user 상태를 업데이트합니다.
    • fetchUser 함수가 변경될 때만 호출됩니다.
  • useMemo
    • user 데이터가 변경될 때만 복잡한 계산을 수행합니다.
    • 여기서는 예시로 사용자의 이름 길이를 계산합니다.
    • userDetails를 메모이제이션하여 user 데이터가 변경될 때만 다시 계산되도록 합니다.
import React, { useState, useEffect, useCallback, useMemo } from "react";

function Profile({ userId }) {
  const [user, setUser] = useState(null);

  // ▶ useCallback으로 fetchUser 함수 메모이제이션
  const fetchUser = useCallback(() =>
    fetch(`https://your-api.com/users/${userId}`)
      .then((response) => response.json())
      .then(({ user }) => user),
    [userId] // userId가 바뀔 때만 함수를 새로 만듭니다.
  );

  // ▶ useEffect에서 fetchUser 호출
  useEffect(() => {
    fetchUser().then((user) => setUser(user));
  }, [fetchUser]);

  // ▶ user 데이터를 기반으로 복잡한 계산 수행 (예시)
  // → 사용자 이름의 길이를 계산
  const userDetails = useMemo(() => {
    if (!user) return null;
    return {
      nameLength: user.name.length,
      // 다른 복잡한 계산이 들어갈 수 있음
    };
  }, [user]); // user 데이터가 변경될 때만 새로 계산

  return (
    <div>
      {user ? (
        <div>
          <p>User: {user.name}</p>
          <p>Name Length: {userDetails.nameLength}</p> {/* 메모이제이션된 값 사용 */}
        </div>
      ) : (
        <p>Loading...</p>
      )}
    </div>
  );
}

 


728x90
반응형