FrontEnd/React

React Hooks - React.memo로 컴포넌트 최적화하기 (useMemo, useCallback)

NandaNanda 2024. 3. 28. 16:40

정리: 리엑트에서는 기본적으로 부모컴포넌트가 랜더링되면 반드시 자식컴포넌트 역시 랜더링된다. 따라서 자식컴포넌트에서는 부모로부터 받는 props에 변화가 생길때만 랜더링이 발생하도록 해주면 최적화에 도움을 준다는 생각을 할 수 있다.

 

 

리액트로 개발을 하다보면 굳이 반복해서 렌더링될 필요가 없는 컴포넌트가 계속해서 반복적으로 많이 렌더링이 되는 문제를 한번쯤은 겪어봤을 것이다. 대부분의 경우 컴포너트의 렌더링 문제를 크게 고민할 필요는 없다. 하지만 렌더링이 너무 지나치게 자주 발생하거나 매번 렌더링마다 무거운 로직을 처리해서 성능에 부담을 준다면 이는 컴포넌트 성능에 매우 좋지 않으므로 해결하는 것이 좋다.

이러한 문제를 해결하기 위해 리액트에서 제공하는 React.memo를 알아보자.
더불어 useMemo와 useCallback의 조합으로 이런 불필요한 렌더링 문제를 더 현명하게 해결해보자.


전달받는 props 의 종류에 따라 다른 결과를 보여주는 자식 컴포넌트가 있고 이 자식 컴포넌트의 부모인 부모 컴포넌트가 있다고 가정해보자.
리액트에서는 기본적으로 부모 컴포넌트가 렌더링이 되면 모든 자식 컴포넌트들이 렌더링이 된다. 그런데 이때 자식 컴포넌트가 항상 같은 props를 전달 받도록 되어있더라도 부모 컴포넌트가 어떤 다른 요소에 의해 렌더링 된다면 자식 컴포넌트도 리렌더링 될 것이다. 만약 자식 컴포넌트가 렌더링이 될 때마다 성능에 좀 무리가 오는 복잡한 로직을 반복해서 실행시켜야 한다면 이 앱의 성능은 💩이 될 것이다... ㅠ 그렇기 때문에 우리는 자식 컴포넌트가 꼭 필요할 때만 렌더링 되도록 횟수를 제한해 줄 필요가 있다.
이때 우리는 자식 컴포넌트가 전달받는 props가 변경될 때만 컴포넌트가 렌더링 되도록 한다면 훨씬 좋을 것이다. 만약 항상 같은 props를 전달 받는다면 어차피 같은 값을 보여주기 때문에 굳이 자식 컴포넌트를 렌더링해 줄 필요가 없다.

React.memo와 HOC

여기서 우리는 React에서 제공하는 고차 컴포넌트(HOC Higher Order Component)인 React.memo(HOC의 하나)를 사용하면 된다. 고차 컴포넌트란 어떤 컴포넌트를 인자로 받아서 새로운 컴포넌트를 반환해주는 함수이다. React.memo 라는 고차 컴포넌트에 어떤 컴포넌트를 넣어주면 UI적으로나 기능적으로는 똑같지만 좀 더 최적화된 컴포넌트를 반환해준다.
이렇게 최적화된 컴포넌트는 렌더링이 되어야할 상황에 놓일때마다 Prop Check를 통해서 자신이 받는 props에 변화가 있는지 없는지를 확인하게 된다. 확인 후에 props에 변화가 있다면 렌더링을 하고 변화가 없다면 새로 렌더링을 하지 않고 기존에 렌더링이 된 내용을 재사용한다.

=========================================================================================

(내 깃헙 참고. https://github.com/dhflekddy/ReactHookPractice/tree/main/reactmemo)

Q. 처음 버전으로 실행했을때 부모나이 증가 버튼만 눌렀음에도 불구 하고 자식 컴포넌트까지 랜더링되는 것을 확인할 수 있다. 즉 자식에게 전달한 props의 변화가 없음에도 리엑트의 특성(특성이라고 뭉뜽그려 말했지만 부모가 랜더링되면 자식도 따라서 랜더링되는 것은 당연하다. 왜냐하면 부모코드에는 반드시 자식컴포넌트가 있을 것이고 랜더링한다는 것은 컴포넌트안의 모든 코드를 다시 검색한다는 것을 의미하기 때문이다)때문에 자식 컴포넌트도 함께 랜더링되는 것이다. 이는 자식으로 전달한 props가 변경되었을 때만 랜더링 되었으면 하는 기대를 무너뜨리는 것이다. 이러한 현상을 자식으로 건낸 props가 변경되었을때만 랜더링될 수 있게, React.memo를 사용하여 개선할 수 있다.

React.memo와 memoization

React.memo의 memo는 memoization을 뜻한다. memoization 이란 이미 계산해놓은 값을 메모리 상에 저장해 놓고 필요할 때마다 꺼내서 재사용하는 기법이다. 우리가 이미 이전에 렌더링 해놓은 자식 컴포넌트를 메모리 어딘가에 저장해놓고 꺼내서 재사용하는 것이 React.memo의 memoization 기법이다.
React.memo를 잘 사용한다면 렌더링 횟수 감소로 인해서 이득을 많이 볼 수 있지만 무분별하게 사용한다면 오히려 성능에 독이될 수 있다. 왜냐하면 컴포넌트를 memoizing 할 때 렌더링된 결과를 어딘가에 저장해서 메모리를 추가적으로 소비하기 때문이다.

React.memo 사용하기

React.memo를 사용하기 적합한 상황은 다음과 같다.

1) 컴포넌트가 같은 props로 자주 렌더링될 때
2) 컴포넌트가 렌더링될 때마다 복잡한 로직을 처리해야 될 때

이외의 경우는 React.memo를 사용하기 전에 고민을 많이 해야 한다.

여기서 한가지 알고 넘어가야 하는 점은 React.memo는 Prop Check를 통해서만 렌더링이 될 지 안될 지 판단을 한다는 것이다. 이는 만약에 컴포넌트가 useState, useReducer, useContext와 같은 상태와 관련된 Hook을 사용한다면 props의 변화가 없더라도 여전히 state나 context가 변할 때마다 다시 렌더링이 된다는 말이다.

 

React.memo와 useMemo

객체를 props로 전달했을 경우 객체의 주소값이 달라지기 때문에 props 값이 변경되었다고 생각해서 페이지가 다시 렌더링되게 된다. 이때 useMemo를 사용해서 변수/상수를 감싸주면 객체값을 렌더링 없이 props로 전달해 줄 수 있다. 이렇게 되면 변수/상수를 전달받은 자식 컴포넌트의 불필요한 렌더링을 막아줄 수 있게 된다.

const name = useMemo(()=>{
  return {
	lastName = '홍',
    firstName = '길동'
  }
},[])

React.memo와 useCallback

자식 컴포넌트에 prop으로 함수를 전달해주게 되면 자식 컴포넌트의 불필요한 렌더링이 일어나게 되는데 이때 useCallback을 사용하게 되면 자식 컴포넌트의 렌더링을 제한할 수 있다.

const tellMe = useCallback(()=>{
	console.log("say something")
},[])