인프런 커뮤니티 질문&답변

해피페이스님의 프로필 이미지
해피페이스

작성한 질문수

부트캠프에서 알려주지 않는 것들 (리액트) 1편

TDD - Green, Red, Refactor

해결된 질문

작성

·

551

1

안녕하세요. TDD의 Green, Red, Refactor 파트(9:40)에서

리팩터링을 진행하시면서 onPageNumberClick 함수를 useCallback으로 감싸서 렌더링이 다시 될때 이 함수가 두번 생성되지 않도록 한다고 하셨는데

Screen Shot 2023-06-08 at 9.38.50 AM.png

이 부분이 잘 이해가 가지 않아서요. 설명해주신걸로 유추해보면 useCallback으로 감싸면 함수가 한번만 생성되는 것 같은데
useCallback을 사용했을때와 사용하지 않았을 때 차이에 대해서 조금 더 설명해주실 수 있을까요?

답변 2

1

애프터캠프님의 프로필 이미지
애프터캠프
지식공유자

https://codesandbox.io/s/usecallback-8w7blx

 

링크 가셔서 9줄과 10번째 줄을 각각 비교해보시면 되겠습니다.

9번째 줄 코드를 사용한다고 했을 때 콘솔 창에 보시면 처음엔 true인데 이것이 의미하는 것은 onClick, onClickFriend 가 같은 주소이고 버튼을 클릭했을 때 false로 출력됩니다. 그 이유는 useCallback 으로 감싸고 있기 때문에 2번째 렌더링에서는 onClickFriend는 새로운 주소를 할당받은 함수이고 onClick은 이전 렌더링에서 사용한 함수 주소를 갖고 있기 때문입니다.

10번째 줄을 주석해제하고 버튼을 클릭해보시면 렌더링 될때마다 onClick, onClickFriend는 새로운 주소값을 할당 받기 때문에 true가 리턴될 것입니다.

아아 그렇군요
리액트가 컴포넌트 실행할때마다 함수를 새로 만드는데, 만약에 함수가 useCallback으로 감싸져있고 종속성 배열에 있는 것들이 바뀌지 않았으면 새로 만들지 않는거네요.

답변 감사합니다!

0

안녕하세요, 저도 해당 강의를 보면서 useCallback에 대해 궁금증이 생겨서요. 위에서 설명해주신 부분을 보고 useCallback을 사용하면 의존성 배열(dependency array)이 변경되지 않는 한 콜백 함수에 새로운 주소값을 할당하지 않고, 불필요한 리렌더링을 예방할 수도 있다라는 장점을 이해했습니다. 그렇다면, 왜 모든 콜백 함수에 useCallback을 사용하지 않고 특정 콜백 함수에만 사용하는 이유가 있을까요? 실무에서는 어떤 경우에 주로 useCallback을 사용하시는지 설명해주실 수 있을까요?

애프터캠프님의 프로필 이미지
애프터캠프
지식공유자

대부분의 경우 useCallback 을 사용하기 보다는 사용하지 않는 편이 좋습니다. 리액트 앱이 제대로 퍼포먼스를 내지 못하는 경우에만 사용한다라고 생각하시면 좋겠네요. 우선 장점은 제대로 이해한 것 같으셔서 넘어가고 단점을 말씀드리면 useCallback 으로 함수를 감싸고 있으면 그것은 메모리에 저장됩니다. 그렇기 때문에 새롭게 렌더링 될 때마다 함수를 매번 만드는게 아니라 메모리에서 꺼내서 사용하게 되기 때문에 useCallback을 남용하면 메모리를 잡아먹습니다. 그런데 대부분의 경우에 useCallback 을 사용했을 때 얻는 퍼포먼스 관점에서의 이점이 없습니다. 그래서 제가 앞서 말한대로 정말 리액트 앱이 느려질 때만 사용하라고 말씀드린 것입니다.

 

사용해야 하는 극단적인 예를 하나 보여드리면,

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

const ExpensiveItem = React.memo(({ count, increment }) => {
  // ... costly computations / render
  return (
    <div>
      Count: {count}
      <button onClick={increment}>Increase Count</button>
    </div>
  );
});

const App = () => {
  const [counts, setCounts] = useState(new Array(10000).fill(0)); // 10000 items

  const increment = useCallback((index) => {
    setCounts((counts) => {
      const newCounts = [...counts];
      newCounts[index]++;
      return newCounts;
    });
  }, []);

  return (
    <div>
      {counts.map((count, index) => (
        <ExpensiveItem key={index} count={count} increment={() => increment(index)} />
      ))}
    </div>
  );
};

export default App;

increment 함수에 useCallback을 씌우지 않으면 App이 렌더링 될 때마다 ExpensiveItem 는 increment가 새로운 주소를 할당받은 함수이니까 이 컴포넌트도 재렌더링을 하게 됩니다. 하지만 useCallback을 씌우게 되면 항상 고정된 주소의 함수가 넘어오니까 재렌더링이 발생하지 않게 됩니다.

useCallback을 항상 써야 하냐는 해외 커뮤니티에서도 조금 논란의 소지가 있는데 이 밑에 링크 들어가셔서 댓글 보면 좋을 것 같습니다.

https://www.reddit.com/r/reactjs/comments/11gssqx/do_we_really_need_to_wrap_every_function_in/

 

 

해피페이스님의 프로필 이미지
해피페이스

작성한 질문수

질문하기