[React] 리렌더링 관리 (React.Memo / useMemo / useCallBack)

우혁주·2024년 3월 1일

효율적으로 관리하지 않으면 리렌더링은 우후죽순 발생하고, 이로 인해 비용이 많이 발생한다. 따라서 효율적으로 리렌더링을 관리하는 방법에 대하여 알아보자.

React.Memo / useMemo / useCallBack 모두 리렌더링을 관리할 수 있고 유용한 방식들이다.

[메모이제이션]

메모이제이션이란 기존에 수행한 연산 결과값을 저장해 두고 다시 동일한 입력이 들어오면 저장한 입력을 재활용하는 프로그래밍 기법이다. 잘 적용시 중복 연산을 피하여 어플리케이션 성능을 최적화 할수 있는 방법이다.

1. React.Memo

상태가 업데이트 되면 그 상태값을 가진 컴포넌트와 종속된 하위의 모든 컴포넌트들이 다시 리렌더링 된다.

스크린샷 2024-03-01 오전 11 30 53

위 예시와 같은 경우에 count 값이 변경되면 count 상태값이 있는 Test가 업데이트 되고, propscount를 받은 CountText와 아무것도 하지않는 TestHeader도 자식 컴포넌트라는 이유로 리렌더링된다.

React.memo 는 컴포넌트를 인자로 받아 컴포넌트를 리턴해주는 고차 컴포넌트다. 함수 컴포넌트를

React.memo로 감싸고 이전 props와 다음 렌더링에 사용될 props의 변화가 없으면 캐싱된 컴포넌트의 결과값을 반환해준다.

그렇다면 어떻게 사용하는가?

스크린샷 2024-03-01 오전 11 44 12

이제 TestHeader 컴포넌트 내부에 있는 상태값이 변하는것이 아니라면 TestHeader는 따로 리렌더링 되지 않는다.

어떤 방식으로 이전 props와 다음 props를 비교할까?

React.memo는 두 props를 얕은 비교를 하여 동일한지 판단한다. 이때 자동으로 제공하는 얕은 비교 로직을 사용하지 않고 로직을 변경 또는 추가 하고 싶은 경우엔 아래아 같이 사용하면 된다.

스크린샷 2024-03-01 오전 11 59 48

그럼 리렌더링 하지 않을 모든 컴포넌트를 React.memo를 사용하여 모두 memoization 하면 되지 않을까? 라고 생각할 수 있지만, React.memo 작업도 이전 props와 새로운 props를 얕은 비교를 하기 때문에 비용이 들지 않는 작업이 아니고, 대부분 다수의 컴포넌트가 매번 다른 props를 전달받기에 렌더링 소요 시간 + 비교 소요 시간이 오히려 리렌더링 소요시간을 늘리는 것과 다름 없기에, 적재적소에 사용해야한다.

만약 위 예시 중 TestHeader가 count 상태값을 props로 받고 count가 버튼 클릭이 아닌 매초마다 ++ 되는 로직이라면 쓸대없는 비용이 오히려 더 많이 발생한다는 것이다.

즉, 상황에 맞춰 잘 사용해야 한다!

2. useMemo

사용하면 좋은곳

  1. 연산량이 많은 함수가 실행되어 뷰에 그리는데, 상관 없는 다른 상태값에 의해 리렌더링 되어 다시 연산을 해야하는 경우에 사용하면 좋다.
  2. 복잡한 데이터 변환이 일어나는데, 다른것에 의해 다시 리렌더링 하면 안좋은 경우.
  3. 어떠한 값이 변할때만 어떤 연산을 시켜야 하는 경우
  4. 참조값을 보존하고 싶을때

즉, 본인과 관련되지 않은 상태값에 의해 리렌더링 되어 쓸대없는 비용이 발생할것을 방지하기 위해 사용하거나, a + b = result 의 일을 하는 함수를 b값이 변환 됐을때만 함수가 실행되도록 하려고 할때 사용하면 좋다.

사용 방법

스크린샷 2024-03-01 오후 12 38 26

복잡하고 연산이 많이 들어가는 함수를 안에 넣는다. 뒤의 [] 는 디펜던시이다. 즉, 종속성을 설정하는 것이다. 디펜던시에 상태값을 넣으면 이 상태값이 바뀔때만! 안의 로직을 실행하게 된다.

사용 예시

스크린샷 2024-03-01 오후 1 28 38

과정

함수를 호출 → 결과 기억 → 의존성 배열에 포함된 값 변경 발생 → 메모이제이션 된 함수 재 실행 → 새로운 결과 반환

3. useCallBack

함수는 리렌더링 될때마다 새로운 주소

사용 목적

  • 콜백 함수를 메모이제이션하여, 동일한 콜백 함수를 재사용하고자 할때
  • 주로 자식 컴포넌트에 콜백 함수를 props로 전달하여 리랜더링을 최적화한다.
    • count++를 하는 countHandler라는 이름의 기본형 함수를 자식에게 props로 전달 했다고 가정했을때, 자식이 직접적으로 count라는 상태값에 연관이 없기에 자식은 리랜더링 되지 않을것 같다고 생각할 수 있으며, 또한 함수는 인간인 나의 입장에서 봤을때는 변화하지 않고 그대로인것 같지만, 리랜더링 되면서 함수 또한 새로 정의된다. 따라서 count가 0 일때의 countHandler와 count가 2일때의 countHandler는 서로 다른 함수이므로, 리랜더링 된다.
    • 따라서 useCallBack을 사용하여 함수를 처음 상태 그대로 전달하여 리렌더링 되지 않도록 하는것이다.

주의점

setState((prev) ⇒ prev++) 전의 값에다가 추가하는 형식으로 해야한다.
이것은 나중에 좀 더 상세히 알아보도록 하겠다.

사용 방법

스크린샷 2024-03-01 오후 2 06 15

이때 handleMegaBoost에 useMemo를 사용 해도 똑같은 효과를 낸다

스크린샷 2024-03-01 오후 2 13 22

이런식으로 해도 되지만 이경우에는 유즈 메모가 함수를 저장하여 똑같은 효과를 낼 수 있지만 올바른 사용법은 아니다!

useMemo와의 차이점

  • useMemo는 함수나 연산결과를 메모이제이션해 최적화 하는것이고, 디펜던시에 있는 값이 변겅 될때마다 새로운 결과를 계산하는 아이다.
  • useCallBack은 콜백 함수를 메모이제이션해서 재사용하는데 사용하며, 디펜던시에 있는 값이 변경되면 새로운 함수를 생성하는것이다.

[useMemo(UM) vs useCallBack(UC)]

  • UM은 연산 결과를 반환, UC는 함수 반환
  • UM은 값 메모이제이션, UC는 함수를 메모이제이션
  • UM은 값 계산 로직을 콜백 함수에 작성, UC는 함수를 생성하는 로직을 콜백함수에 작성
  • UM은 계산비용 큰 연산을 최적화 하는데 주로 사용되는 용도 / UC는 자주 렌더링 되는 컴포넌트에서 함수를 최적화하는데 사용 하고 불필요한 함수 재생성을 방지하는데 사용한다.
  • 둘다 의존성 배열을 잘 활용 해야한다.

무작위로 사용하면 오히려 안좋은 효과를 낼 수 있으므로, 잘 판단하여 제대로 사용한다!


고차 컴포넌트란?

고차컴포넌트란 리엑트에서 사용되는 디자인 패턴 중 하나인데, 고차 컴포넌트는 컴포넌트 로직을 재사용하기 위한 강력한 도구로, 기존 컴포넌트를 가져와 새로운 컴포넌트로 변환하는 함수다.

고차 컴포넌트는 다음과 같은 특징을 갖는다:

  1. 함수로 구현된 컴포넌트 변환기: 고차 컴포넌트는 일반적으로 함수로 구현되며, 인자로 컴포넌트를 받아서 새로운 컴포넌트를 반환한다.
  2. 컴포넌트 컴포지션을 활용한 재사용성 강화: 고차 컴포넌트는 여러 컴포넌트에 적용될 수 있으며, 컴포넌트 간의 로직을 공유하여 재사용성을 높인다.
  3. Props를 통한 컴포넌트 간의 데이터 전달: 고차 컴포넌트는 하위 컴포넌트에 추가적인 props를 전달하여 기능을 확장하거나 변경할 수 있다.

예를 들어, 인증이 필요한 페이지에서 사용자의 로그인 상태를 확인하고, 로그인되어 있지 않으면 로그인 페이지로 리디렉션하는 기능을 구현할 수 있다. 이를 고차 컴포넌트로 추상화하여 여러 페이지에서 재사용할 수 있다.

스크린샷 2024-03-01 오전 11 41 10

위 코드에서 withAuth는 인증이 필요한 페이지에 대한 고차 컴포넌트다. 이를 사용하여 Dashboard 컴포넌트를 감싸고, 로그인 상태를 확인하여 로그인되어 있지 않으면 리다이렉션을 시킨다. 이로써 인증이 필요한 페이지를 쉽게 검증하고 재사용할 수 있다.

profile
프론트엔드 개발자

0개의 댓글