useCallback 제대로 사용하고 계신가요🤔

나는 최근까지 일어나지 않았다.
우리 팀이 작업 중인 프로젝트에서 자식 구성 요소에 전달되는 모든 함수 소품에 대해 useCallback를 사용했습니다.
이 접근 방식은 예상대로 이점을 제공하지 않습니다.

우리의 코드는 다음과 같았습니다(문자 그대로 😀)

const ParentComponent = () => {
  ...
  const onClick = useCallback(() => console.log('click'), [])  

  return <ChildComponent onClick={onClick} />
}

const ChildComponent = ({ onClick }) => {
  return <button onClick={onClick}>Click me</button>
}


접근 없이useCallback
const ParentComponent = () => {
  ...
  return <ChildComponent onClick={() => console.log('click')} />
}


두 번째 접근 방식에 비해 첫 번째 접근 방식의 이점은 미미하며 경우에 따라 비용useCallback을 고려하면 두 번째 접근 방식이 더 빠릅니다.

문제는 각 리렌더링에서 함수를 생성하고 파괴하는 것이 비용이 많이 드는 작업이 아니라는 것입니다. 이를 useCallback로 교체해도 큰 이점이 없습니다.

우리가 항상 useCallback 후크를 사용하는 또 다른 이유는 하위 구성 요소가 변경되지 않은 경우 하위 구성 요소가 다시 렌더링되는 것을 방지하기 위한 것입니다. 그러나 이것은 잘못된 것입니다. 왜냐하면 상위 구성 요소가 다시 렌더링될 때마다 하위 구성 요소도 다시 렌더링되기 때문입니다. .

반응.메모



소품이나 상태가 변경된 경우에만 하위 구성 요소를 다시 렌더링하려면 React.memo 을 사용하십시오.
기능 대신 클래스 구성 요소로 작업하는 경우 PureComponent 또는 shouldComponentUpdate로 동일한 결과를 얻을 수 있습니다.

첫 번째 예에서 ChildComponent를 React.memo로 래핑하면

const ChildComponent = React.memo(({ onClick }) => {
  return <button onClick={onClick}>Click me</button>
})


ParentComponent가 다시 렌더링되고 ChildComponent의 소품이 변경되지 않으면 ChildComponent는 다시 렌더링되지 않습니다.

이것은 useCallback 후크를 사용해야 할 때 좋은 통찰력을 제공합니다.useCallbackReact.memo와 함께 사용해야 합니다.

항상 그런 식이어야 한다고 말하지는 않겠습니다. 유용하다고 생각되면 React.memo 없이 useCallback을 사용할 수 있지만 대부분의 경우 이 두 가지가 쌍이 되어야 합니다. ❤

React.memo를 사용하는 경우



언제 수행해야 하는지에 대한 명확한 지침이 없습니다. 누군가는 항상 사용해야 한다고 생각하며 저는 "구성 요소의 성능을 측정하고 필요한 경우 React.memo로 최적화"하는 접근 방식을 선호합니다.

기본적으로 래핑할 수 있는 구성 요소React.memo는 테이블이나 목록과 같이 자식이 많은 구성 요소입니다.

이제 예제를 살펴보겠습니다.
여기에서 복제하고 직접 시도할 수 있습니다https://gitlab.com/markoarsenal/use-callback.

이렇게 생겼어요 (매우 창의적 😀)



긴 댓글 목록(React.memo의 좋은 후보)이 있고 맨 위에는 재렌더링을 트리거하는 것이 주요 목적인 카운터 버튼이 있습니다.

코드는 다음과 같습니다

const Home = () => {
  const [counter, setCounter] = useState(0);
  const onClick = useCallback(() => console.log("click"), []);

  return (
    <Profiler
      id="Home page"
      onRender={(compName, mode, actualTime, baseTime) =>
        console.log(compName, mode, actualTime, baseTime)
      }
    >
      <main className="max-w-5xl p-8 m-auto">
        <div className="flex justify-center mb-8">
          <button
            onClick={() => setCounter(counter + 1)}
            className="px-3 py-1 border border-gray-500"
          >
            Update {counter}
          </button>
        </div>
        <Comments comments={comments} onClick={onClick} />
      </main>
    </Profiler>
  );
};

Profiler 구성 요소가 루트 구성 요소라는 것을 알 수 있습니다. 이 구성 요소는 https://reactjs.org/docs/profiler.html 입니다.
렌더링 시간을 측정하는 데 사용하고 있습니다.onRender 콜백을 볼 수 있습니다. 우리는 내부에 몇 가지를 기록하고 있지만 가장 중요한 것은 actualTime과 baseTime입니다. actualTime은 구성 요소를 다시 렌더링하는 데 필요한 시간이고 baseTime은 최적화 없이 구성 요소를 다시 렌더링하는 데 걸리는 시간입니다. 따라서 구성 요소 내에 최적화가 없는 경우 actualTime과 baseTime은 같아야 합니다.
Comments 구성 요소는 다음과 같습니다(React.memo로 래핑된 알림).

const Comments = ({ comments, onClick }: CommentsProps) => {
  return (
    <section>
      {comments.map((comment) => {
        return (
          <Comment
            {...comment}
            className="mb-4"
            onClick={onClick}
            key={comment.id}
          />
        );
      })}
    </section>
  );
};

export default memo(Comments);


이제 Chrome에서 500개의 댓글이 있는 예제를 실행하고 "업데이트"버튼을 몇 번 눌러 다시 렌더링하고 여기에 결과를 게시합니다.



따라서 모든 재렌더링에서 우리는 상당한 양인 약 30ms를 절약하고 있습니다.

메모한 댓글 하나를 렌더링하기 위해 댓글 목록 대신 한 가지만 더 해보고 측정값이 무엇인지 확인해 봅시다.

{/* <Comments comments={comments} onClick={onClick} /> */}
<Comment {...comments[0]} onClick={onClick} />




그래도 우리는 시간을 절약할 수 있지만 그들은 무시하고 있습니다. 즉, React는 작고 간단한 구성 요소를 다시 렌더링하는 데 문제가 없으며 이러한 구성 요소를 메모하는 것은 별 의미가 없습니다.
반면에 많은 자식을 포함하는 구성 요소를 메모하는 것은 이점을 얻을 수 있습니다.

이 기사를 즐겁게 읽으셨기를 바라며 이제 useCallbackReact.memo에 대한 더 나은 개요와 사용 시기에 대해 알게 되셨기를 바랍니다.

좋은 웹페이지 즐겨찾기