본문 바로가기
리액트

useCallback과 React.Memo을 통한 불필요한 리렌디링 막기

by 지 요니 2023. 5. 24.

회원가입페이지를 구현하다가 직업과 성별 버튼을 클릭시에 전체 페이지가 리렌더링되는 예상치못한 오류가 발생하였다.

  // 직업 버튼 클릭 핸들러
  const roleButtonClickHandler = (selectedRole) => {
    setRole(selectedRole);
  };
  
  
  
   <InputTitle>직업</InputTitle>
        <ButtonContainer>
          <SelectionButton
            onClick={() => setRole("model")}
            style={{
              backgroundColor: role === "model" ? "#000000" : "#ffffff",
              color: role === "model" ? "#ffffff" : "#000000",
            }}
          >
            모델
          </SelectionButton>
          <SelectionButton
            onClick={() => setRole("photographer")}
            style={{
              backgroundColor: role === "photographer" ? "#000000" : "#ffffff",
              color: role === "photographer" ? "#ffffff" : "#000000",
            }}
          >
            작가
          </SelectionButton>
        </ButtonContainer>

코드를 살펴보면 모델과 작가를 각각 클릭시에 state가 변경되기 때문에 화면이 리렌더링되는 것은 당연했다..!

하지만 내가 원하는 것은 클릭 이벤트가 발생시에 state는 변경것이지 화면이 리렌더링이되는 것은 원치않았다.  

 

👉 리액트가 리렌더링 될 때

1. state 변경이 있을 때
2. 부모 컴포넌트가 렌더링 될 때
3. 새로운 props이 들어올 때
4. shouldComponentUpdate에서 true가 반환될 때
5. forceUpdate가 실행될 때

 

아무튼 이런 상황을 해결에 쓰인 방법들을 정리해보려고한다. 

 

👉  useCallback을 사용하여 이벤트 핸들러를 메모이제이션

useCallback()은 함수를 메모이제이션(memoization)하기 위해서 사용되는 hook 함수이다.

첫번째 인자로 넘어온 함수를, 두번째 인자로 넘어온 배열 내의 값이 변경될 때까지 저장해놓고 재사용할 수 있게 하는 기능을 한다.
const memoizedCallback = useCallback(함수, 배열);

예를 들어, 리액트 컴포넌트 안에 함수가 선언되어있을 때 이 함수는 해당 컴포넌트가 렌더링 될 때마다 새로운 함수가 생성되는데, useCallback을 사용하면 해당 컴포넌트가 렌더링 되더라도 그 함수가 의존하는 값(deps)들이 바뀌지 않는 한 기존 함수를 재사용할 수 있습니다.

 

.useCallback을 위 코드에 적용시켜보면

// 직업 버튼 클릭 핸들러
  const roleButtonClickHandler = useCallback((selectedRole) => {
    setRole(selectedRole);
  }, []);

useCallback 첫번째 파라미터로 기존 함수를 넣어주고, 두번째 파라미터로 [] 배열을 넣어주면 된다. 이를 통해 컴포넌트의 불필요한 재 렌더링을 방지 할 수 있다.

 

하지만 이 방법만으로 화면이 리렌더링되는 것을 막을 수는 없었다...! ㅜㅜ

useCallback은 상하위 컴포넌트 관계에서 상위 컴포넌트가 넘겨주는 props를 핸들링하는 역할을 하는데 하위 컴포넌트가 그 부분을 신경쓰지 않고 상위 컴포넌트의 렌더링 여부에 따라 자동으로 렌더링이 된다면, 다시 말해 useCallback은 홀로 사용한다면 그저 무용지물이 된다.

이게 무슨 소린가 ㅜ

리액트 문서에 따르면 useCallback은 참조의 동일성에 의존적인 최적화된 자식 컴포넌트에 콜백을 전달할 때 유용 하다고 한다.. 

 

즉, useCallback 사용만으로는 하위 컴포넌트의 리렌더를 막을 수 없다! 하위 컴포넌트가 참조 동일성에, 의존적인, 최적화된 Purecomponent!이어야만 비로소 불필요한 리렌더링을 막을 모든 것이 완성된다.

👉  React.memo를 사용하여 컴포넌트 메모이제이션

React.memo는 고차 컴포넌트(Higher Order Component)이다.
얕은 비교 연산을 통해 동일한 참조 값의 prop이 들어온다면 리렌더링을 방지시키는 역할을 한다.
컴포넌트가 동일한 props로 동일한 결과를 렌더링해낸다면, React.memo를 호출하고 결과를 메모이징(Memoizing)하도록 래핑하여 경우에 따라 성능 향상을 기대할 수 있다. 즉, React는 컴포넌트를 렌더링하지 않고 마지막으로 렌더링된 결과를 재사용한다.

🔍React.memo는 props 변화에만 영향을 준다. 즉, 해당 컴포넌트가 전달받는 props에 변화가 없다면 상위 컴포넌트에 재렌더링이 일어나도 해당 컴포넌트를 재렌더링하지 않는다.
🔍props가 복잡한 객체인 경우 React.memo는 얕은 비교만 한다.

고차 컴포넌트이기 때문에 사용중인 component를 memo로 감싸주면 사용할 수 있다!

또한 props에 대해 기본으로 제공되는 얕은 비교가 아닌 커스텀을 하고 싶다면 두 번째 인자로 비교 함수를 넣어 사용할 수 있다고한다.

 

따라서 useCallback React.memo를 사용하여 렌더 최적화를 한 코드는 다음과 같다.

// 직업 버튼 클릭 핸들러
  const roleButtonClickHandler = useCallback((selectedRole) => {
    setRole(selectedRole);
  }, []);
  
  const MemoizedSelectionButton = React.memo(SelectionButton);



 <InputTitle>직업</InputTitle>
        <ButtonContainer>
          <MemoizedSelectionButton
            onClick={() => roleButtonClickHandler("model")}
            style={{
              backgroundColor: role === "model" ? "#000000" : "#ffffff",
              color: role === "model" ? "#ffffff" : "#000000",
            }}
          >
            모델
          </MemoizedSelectionButton>
          <MemoizedSelectionButton
            onClick={() => roleButtonClickHandler("photographer")}
            style={{
              backgroundColor: role === "photographer" ? "#000000" : "#ffffff",
              color: role === "photographer" ? "#ffffff" : "#000000",
            }}
          >
            작가
          </MemoizedSelectionButton>
        </ButtonContainer>

 

댓글