코드

import './App.css';
import {useCallback, useState} from "react";
import Child from "./Child";

function App() {
  const map = [
      'https://www.daum.net/',
      'https://www.naver.com/',
      'https://www.nate.com/',
  ]
  const [urls, setUrls] = useState([...map]);
  const [count, setCount] = useState(0);
  const [_, forceRender] = useState(0);

  const longTimeWork = async () => {
    console.log('[call longTimeWork]');
    for (let j = 0; j < urls.length; j++) {
        console.log(urls);
        await fetch(urls[j], {mode: 'no-cors'});
    }
    setCount(prev => prev + 1);
  }

  const useLongTimeWork = useCallback(longTimeWork, [urls]);

  return (
    <div className="App">
      <button onClick={() => forceRender(prev => prev + 1)}>부모 그리기</button>
      <button onClick={() => setUrls([...urls, map[count % 3]])}>URL 추가</button>
      <Child count={count} longTimeWork={longTimeWork} />
    </div>
  );
}

export default App;
import {useEffect} from "react";

function Child({count, longTimeWork}) {
  useEffect(() => {
      longTimeWork();
  }, [longTimeWork])

  return (
    <div>
      <button>
          {'child'}
          {' '}
          {count}
      </button>
    </div>
  );
}

export default Child;

 

React에서 보통 상위 컴포넌트가 하위 컴포넌트에게 함수를 전해줄때가 많다.
위의 예에서는 longTimeWork가 이에 해당 한다. longTimeWork함수는 네이버, 다음, 네이트 사이트를 차례대로 하나씩 방문하는 네트워크 레이턴시가 아주 많은(시간이 아주 많이 걸리는) 함수다.
하위 컴포넌트는 useEffect를 사용해서 함수를 받자마자 실행 시킨다고 가정하자. 하위 컴포넌트가 받는 함수가 빠르고, 가벼운 함수라면 부모가 render할 때마다 호출해도 크게 상관없다.
하지만 함수 이름에서 보다 싶이 longTimeWork!
이때는 부모가 render할 때마다 함수를 실행시키기 부담스럽다. 어떻게 해야할까? 이때 바로 useCallback을 쓰면된다.

 

문제 이해하기

문제점을 파악해보자.
상위 컴포넌트에서 <Child longTimeWork={longTimeWork} />를 선언했다.
이때 부모 그리기(forceRender) 함수를 호출해서 부모를 그려보자. 그러면 어때 longTimeWork 함수도 다시 만들어진다. 이말은 즉 longTimeWork 함수는 새로운 함수가 된다는 말이다.
하위 컴포넌트(Child) 입장에서는 longTimeWork 함수가 새로 만들어졌으니 useEffect에서 다시 호출한다.
longTimeWork는 내부적으로 setCount(prev + 1)을 실행하니 무한대로 longTimeWork 함수를 호출한다.
나는 그냥 하위 컴포넌트에서 longTimeWork가 새로 생길 때만 호출하고 싶었는데......

 

문제 해결

const useLongTimeWork = useCallback(longTimeWork, [urls]);

<Child count={count} longTimeWork={useLongTimeWork} />

함수를 선언하고, Child에 전달하자.


useCallback 두번째 파라미터는 의존성 배열이다. 그러니깐 urls가 변경 될때만 해당 함수를 다시 만들겠다는 말이다.
다시 한번 부모 그리기(forceRender) 함수를 호출해서 부모를 그려보자. urls가 변경되지 않았기 때문에 Child에서 함수를 호출하지 않는다.
그리고 URL 추가 버튼을 누르면 urls가 변경 되기 때문에 longTimeWork가 재 정의되고 Child 컴포넌트에서 해당 함수를 실행하게 된다.
내가 원하는 결론이다. 아주 뿌듯하다.

 

결론

useCallback 함수는 의존성 배열에 정의된 변수가 변경 될때만 함수를 다시 만든다. 그러므로 성능 향상에 도움이 된다.
하지만 일단 useCallback없이 코딩하고 느려지면 그때 생각하자.

'React' 카테고리의 다른 글

React(SPA) Auth 로그인 흐름  (0) 2020.01.09
React Redux 기본  (0) 2019.12.19
React Form input  (0) 2019.12.16

+ Recent posts