WIL(Weekly I Learned)

useMemo, useCallback 차이

borobong230 2023. 5. 8. 23:56

useMemo

메모이제이션 된 값을 반환하는 함수.

 

메모이제이션 이란?

기존에 수행한 연산의 결과값을 어딘가에 저장해두고 동일한 입력이 들어오면 재활용하는 프로그래밍 기법.

 

효과: 중복 연산을 피할 수 있음 -> 애플리케이션 성능 최적화 가능

 

// 기본 형태
useMemo(() => function, [dependency])


import { useMemo } from 'react';

function ExampleComponent({ list }) {
  const expensiveComputation = useMemo(() => {
    // Your expensive computation code goes here
  }, [list]);

  return <div>{/* Your component JSX */}</div>;
}

 

useMemo를 쓰는 이유?

겉보기엔 useState랑 큰 차이가 없다.

하지만, 부모 컴퍼넌트 혹은 그 외의 요소로 인하여 리랜더링이 일어난다면, console.log가 계속 찍히게 된다.

const ExampleUseState = () => {
	const [count, setCount] = useState(0)
    console.log(count)
    const onClickBtn = () => {
    	//count up 
    }
    return (
    	<div>
        	<button onClick={onClickBtn}/>
        <div/>
    )
}

 

하지만 useMemo를 사용한다면?

const ExampleUseState = () => {
	const [count, setCount] = useState (0)
	useMemo(() => {
    	console. log (count)
    },[count])
    
	const onClickBtn = () => {
	//count up
	}
    
	return (
        <div>
            <button onClick={onClickBtn}/>
        <div/>
    )
}

count 값이 변했을때에만 console.log 함수가 실행된다.

이렇게 되면 불필요한 연산을 하지 않게 되고, 어플리케이션 최적화를 할 수 있다.

 

 

useCallback

메모이 제이션 된 함수를 반환하는 함수

 

useCallbak(function, [dependency])

 

import { useCallback } from 'react';

function ExampleComponent({ onEvent }) {
  const memoizedCallback = useCallback(() => {
    // Your callback code goes here
  }, [dependency1, dependency2]);

  return <ChildComponent onEvent={memoizedCallback} />;
}

 

사용 예시)

1. 자식 컴퍼넌트에 함수 전달 할 때

import { useState, useCallback } from 'react';

function ChildComponent({ onRemove, id }) {
  return <button onClick={() => onRemove(id)}>Remove</button>;
}

function ParentComponent() {
  const [items, setItems] = useState(['Item 1', 'Item 2', 'Item 3']);

  const handleRemove = useCallback((itemId) => {
    setItems((prevItems) => prevItems.filter((_, index) => index !== itemId));
  }, []);

  return (
    <div>
      {items.map((item, index) => (
        <div key={index}>
          {item} <ChildComponent onRemove={handleRemove} id={index} />
        </div>
      ))}
    </div>
  );
}

일반 함수를 전달하게 될 경우, 부모 컴퍼넌트가 리렌더링 수정되면 자식 컴퍼넌트도 같이 리렌더링이 일어나게 된다.

하지만 useCallback을 사용할 경우 원치 않는 리랜더링을 방지할 수 있다.

 

 

2. 외부 api 호출 할 때

import { useState, useEffect, useCallback } from 'react';

function FetchDataComponent() {
  const [data, setData] = useState(null);

  const fetchData = useCallback(async () => {
    try {
      const response = await fetch('https://api.example.com/data');
      const result = await response.json();
    } catch (error) {
      console.error('Error fetching data:', error);
    }
  }, []);

  useEffect(() => {
    fetchData().then((result)=> {setData(result)});
  }, [fetchData]);

  return (
    <div>
      {data ? (
        <div>{/* Render the fetched data */}</div>
      ) : (
        <div>Loading...</div>
      )}
    </div>
  );
}

위의 예제에서 useCallback을 쓰지 않을 경우!
FetchDataComponent가 리렌더링 될 경우 fetchData함수에는 새로운 함수가 할당됨.

그렇게 되면 useEffect의 result 상태값이 바뀌고, state값이 바뀌었기 때문에 다시 리랜더링이 되고 다시 fetchData함수가 새로운 함수가 실행되는 무한루프에 빠지게 된다.

 

이를 useCallback으로 해결 할 수 있다.

 

 

참고: https://narup.tistory.com/273#----%--%EC%-E%--%EC%-B%-D%--%EC%BB%B-%ED%-F%AC%EB%--%-C%ED%-A%B-%EC%--%--%--props%EB%A-%-C%--%ED%--%A-%EC%--%--%EB%A-%BC%--%EC%A-%--%EB%-B%AC%ED%--%--%EB%-A%--%--%EA%B-%BD%EC%-A%B-