[React] 랜더링의 모든 것

GONI·2022년 10월 13일
0

React Native

목록 보기
3/4

(너무나 오랜만에 돌아온 듯한 느낌이지만...)

요즘 너무나 바쁘고, velog라는 것에 대해 잊어버리며 살다가 바쁘게 살던 만큼 깨달은 것들이 너무 많고, 느낀점이 너무나도 많아서 점점 정리가 필요하다고 느낄때 쯤?

비로소 돌아오게 되었다(,,,ㅎ)


그리고 비로소 돌아온 뒤 작성해 볼 주제는 바로바로

'리액트 랜더링의 모든 것'

그럼 시작해보자~!





PART1. 발단

랜더링에 대해 관심을 가지게 된 계기는 바로 React Native이다. 현재 React Native으로 어플을 제작중이고, 출시를 얼마 안남은 시점에서 내가 짜 놓은 코드에 대한 치명적인 문제점들을 발견하였다.



그것은 바로 (지옥의)랜더링 문제였다!

사실 React를 이용해서 웹페이지를 제작할 때만 해도 랜더링에 관해서 심각하게 고려해 본 적이 없었다.

현재 운영중인 실제 웹페이지를 제작했을 때에도 랜더링의 '랜' 자도 신경 써 본 적이 없었던 것 같지만 잘 돌아갔기 때문이다.

현재 웹페이지를 제작중인 직원분들도 랜더링에는 많이는 신경을 안쓰는 듯이 얘기해 주셔서 그렇게 중요한 것인지 몰랐지만........?



하지만 React Native를 이용하여 어플을 제작하다 보니 랜더링은 생각보다 심각한 문제가 되었다.

예를 들어 핸드폰에 발열을 일으킨다던지, 최적화가 제대로 이루어지지 않은 경우 앱이 도중에 꺼져버린다던지 하는 문제점이 있었기 때문이다.
(뒷구르기 후 생각해도 앱 개발은 역시 웹보다 어려운 것 같다)

앱이 도중에 튕기는 건 심각한 사용성의 문제를 일으키기 때문...



그 이후부터 화면을 제작할 때 기능보다 랜더링을 먼저 신경써서 시작한다.

사실 기능을 적용하는 것은 정~~~말 어려운 기능이 아니라면 밤을 새서라도 할 수 있겠지만, 만들어진 기능에 최적화를 우겨넣는 것은 자칫하면 기능 자체를 수정해야 할 수도 있기 때문이다!





PART2. 전개

역시나 긴 서론(주절주절)
그 이후로 최적화 문제는 다른 분의 도움을 받기 시작했고, 이제는 나 스스로 대처해봐야 하지 않겠는가 하는 생각에
여러 테스트를 진행하며 셀프 랜더링 최적화를 하기 시작했다.

먼저 각각의 item이 component로 정의되어 있는 Flatlist를 제작해 보았다.
(TestData는 자체 정의한 interface이다.)



const [data, setDate] = useState(TEST_DATA);

const handleDeleteItem = (id: number) => {
  console.log('');
  console.log('아이템 삭제 이후 랜더링');
  console.log('');
  setDate(prev => prev.filter(item => !isEqual(item.id, id)));
};

const renderTestItem: ListRenderItem<TestData> = ({ item }) => {
  return <TestItem item={item} onDelete={handleDeleteItem} />;
};

return (
    <FlatList
      	data={data}
      	renderItem={renderTestItem}
    />
);

결과 화면은 위와 같고, 이처럼 아주~ 간단한 코드를 작성한 뒤 TestItem 컴포넌트에 로그를 찍으며 아이템 삭제 기능을 실행해보았다.


결과는 너무나 당연하게도 삭제 이후 모든 기존 컴포넌트들에도 랜더링이 다시 일어났다.

그런데 생활코딩선생님의 말씀을 빌려 이 랜더링 리스트의 개수가 1억개라면 겨우 하나의 아이템을 삭제하는 데에도 1억번의 리랜더링이 일어나게 될 것이다.
(그럼 그때 과부하로 앱이 꺼지겠지?)





PART4. 위기 / 절정

그렇다면 가장 간단한 방법인 React.memo를 이용하여 최적화를 시켜보도록 하자.

export default memo(TestItem, (prev, next) => isEqual(prev.item, next.item));

React.memo에 대해 헷갈리는 분들을 위한 간단 설명

(prev, next) => isEqual(prev.item, next.item)
// 이 부분이 true 일 경우, 즉 이전 item props와 다음 item props가 같을 경우 
// 메모이제이션 된 결과를 반환
// 리랜더링이 이루어지지 않음

결과는?

당연히 실패다. 왜일까??

우리는 item 말고도 onDelete 함수도 props로 넘겨주고 있기 때문이다. 그렇다면 이 onDelete 함수 역시 최적화를 시켜줘야 할 것 같다.

함수 최적화는 당연히 useCallback을 사용하야겠지?

const handleDeleteItem = useCallback((id: number) => {
  console.log('');
  console.log('아이템 삭제 이후 랜더링');
  console.log('');
  setDate(prev => prev.filter(item => !isEqual(item.id, id)));
}, []);

그 이후 로그를 찍어보면 props로 전달되는 함수 역시 최적화가 되었기 때문에 이젠 기존 item에 대해서는 랜더링이 이루어지지 않는다!





PART5. 결말

사실 너무나 간단한 경우를 다뤘기 때문에 누구나 다 알 수도 있겠지만..? 실무에서는 아마 이 최적화 문제를 3~4번 엮어서 풀어야 할 것이다...

또한 여러가지 문제점들도 존재하는데,


# React.memo에서 prev, next props에 대한 잘못된 비교

이는 기존에 잘 구현되던 기능마저 조건을 잘못 설정하여 랜더링이 이루어지지 않아서

"어? 외않돼?"

라고 외칠 수도 있다!

따라서 prev, next 상태를 비교한 뒤 랜더링에 영향을 주는 props가 아니라면 사용하는 props들을 하나하나 추가해보면서

  • 꼭 필요한 props인지 판단
  • 랜더링이 최적화가 되는지 판단
  • 기능 함수들은 잘 작동되는지 테스트

위 사항들을 꼭 확인하길 바란다...!


# 최적화가 필요없는 component 에서 React.memo사용

이 경우에는 오히려 메모이제이션을 위해 들이는 비용만 낭비될 수가 있다. 이러한 하나하나의 현상이 결국 앱/웹 성능과 직결되기 때문에 주의해서 사용할 필요가 있다.


# 무분별한 React.memo / useCallback / useMemo 사용

최적화가 잘 되어서 너무나 기쁜 나머지 이제는 위의 것들의 사용이 습관이 되어버렸다...
하지만 최적화란 우리의 앱을 빠르게 해 주는 것이고, 우리에게 이점을 주는 것에는 당연히 cost가 존재한다.

(공짜였을 것 같아?)

보통 최적화에 사용되는 함수들의 양이 어마어마하게 많아질수록

  • 처음 랜더링이 시 기존보다 현저하게 저하,
  • 두 번째 이후 랜더링부터는 기존보다 랜더링이 현저하게 향상

위와 같은 현상이 이루어진다고 한다.

따라서...! 최적화가 꼭 필요한 부분을 판단하는 것도 능력이고, 이는 효율적인 코드를 작성하는 방법이라고 할 수 있다...!





PART6. 마치며(?)

이는 단순히 React Native에서만 적용해야 하는 것은 아닐 것이다.

물론 React Native가 랜더링에 훨씬 민감하고, 비록 React는 랜더링 최적화에 신경을 안써도 잘 돌아간다고 하더라도!

우리는
1. 조금의 속도 향상을 불러올 수 있다면
2. 그리고 더 깔끔한 코드를 원한다면
3. 자랑하고 싶다면

React를 이용하여 웹 개발을 할 때도 최적화가 필요한 부분에 신경쓰고, 깔끔한 코드와 퍼포먼스를 잡아보도록 하자!





나의 랜더링 일지 끝!

profile
오로지 나의 기억력을 위한 일지

0개의 댓글