Notice
Recent Posts
Recent Comments
Link
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Tags more
Archives
Today
Total
관리 메뉴

작은 지식주머니

React-native 대용량 리스트 렌더링 최적화 방법 과정 기록 본문

React-native

React-native 대용량 리스트 렌더링 최적화 방법 과정 기록

작지 2023. 7. 21. 21:34

CATE 앱에서 Infinite-scroll 기능을 제공하는 Feed 스크린이 있습니다.

 

앱 출시 초기에는 렌더링 아이템이 무겁지 않았고 데이터도 적어 아이템을 그대로 FlatList에 담아 노출하였습니다.

 

하지만 렌더링 아이템 기능 추가, 데이터 누적에 따라서 리스트에 데이터가 쌓이면서

UI FPS 60을 유지하던 리스트가 UI FPS 30 이하로 내려갔습니다.

아오 ㅜㅜ

그리고 초기 렌더링 속도도 점점 느려져 갔는데요.

각 아이템의 렌더링 아이템이 너무 무거워 초기 렌더링 속도가 5초 이상 대기하는 일이 많아졌습니다.

 

데이터 증가에 따른 UI FPS 문제, 렌더링 아이템 속도 개선을 하기 위해서 시도하였던 방법들을 공유하겠습니다.


개선 방법 1 React 내장 함수 사용하기

 

매번 새로운 함수를 새롭게 메모리에 쌓는 일을 피하기 위해 우선 인라인 코드들을 useCallback으로 감싸주었습니다.

 

특히 FlatList에서 반복적으로 호출하는 renderItem 을 중심적으로 적용하였습니다.

함수 또한 deps의 변화가 적은 순부터 하나하나 적용하며 에러가 발생하는지 체크하고 함수의 재호출을 억제하였습니다.

 

또한 Feed Component 중 비교적 메모리가 크고 props 변화가 적은 컴포넌트를 React.memo로 매핑하였습니다.

해당 리팩토링을 진행하고 초기 및 재 렌더링 속도가 상승되었습니다.

 

하지만 여전히 느린데요.


개선 방법 2 IMAGE 캐싱, 리사이즈

 

어디서 시간을 오랫동안 잡고 힘들어하는지 차근차근 잡아보려고했습니다.

 

우선 컴포넌트를 한개씩 나누어 렌더링에 얼마나 시간이 걸리는지 측정하였습니다.

 

타이틀 10ms 이내
이미지 400ms 이상
부가정보 10ms 이내
부가정보 30ms 이내

 

해당 컴포넌트들 중 가장 앱에서 많은 메모리와 시간을 차지하는 부분은 메인 이미지 였습니다.

 

이미지 렌더링을 개선하기 위하여 우선 이미지 크기를 줄이는 방식으로 개선하도록 하였습니다.

이미지 업로드 시 백엔드에서 원본 이미지를 _small, _medium, _large, _xlarge 네 가지의 사이즈로 나누도록 요청드렸습니다.

 

또한 유저가 올린 사진을 jpeg로 변환하여 압축된 이미지를 사용하도록 하였습니다.

유저는 Feed 목록을 불러올 때마다 사용자 기기에 맞는 이미지를 받는 로직을 생성하였습니다.

const resizeFileUrl = await this.asyncGetResizedFileName(image.fileUrl, resizeWidth);
const imageSize = await this.getImageSize(resizeFileUrl);
return Object.assign({}, {
    uri: image.fileUrl,
    width: imageSize.width,
    height: imageSize.height,
});

화면에 그리고 싶은 크기(resizeWidth)를 기반으로_small, _medium, _large, _xlarge 네 가지의 사이즈 중

하나의 주소를 받아오고 해당 이미지의 width, height를 계산하여 보내주도록 하였습니다.

 

이미지 리사이즈 로직으로 이미지 렌더링 속도가 400ms → 200ms 언더 까지 내려가는 파격적인 효과를 봤지만

아직 사용자 입장에서는 총합 2000ms 이상 기달려야하는 상태입니다.

 

react-native-fast-Image 라이브러리를 사용하여 이미지 렌더링 방식을 변경하고자 하였습니다.

https://github.com/DylanVann/react-native-fast-image

 

GitHub - DylanVann/react-native-fast-image: 🚩 FastImage, performant React Native image component.

🚩 FastImage, performant React Native image component. - GitHub - DylanVann/react-native-fast-image: 🚩 FastImage, performant React Native image component.

github.com

 

fast-Image 에서 제공하는 이미지 캐싱, 리사이즈 옵션을 사용하여 렌더링 시간이 캐싱된 이미지일 경우
200ms → 최대 50ms 정도로 개선되었습니다.


개선방법 3. 데이터 누적 이슈 개선

아이템 하나하나의 렌더링 시간은 개선하였지만 데이터가 쌓이면서 앱에 부하가 걸리는 현상은 아직 개선이 되지 않았습니다.

아직이다!

 

우선 데이터가 누적되면서 FPS 가 저하되는 이유를 추정해보았습니다.

 

저는 렌더링 아이템이 화면에서 벗어나도 계속해서 메모리에 남아있다고 판단을 하였는데요.

이유는 리스트에 데어터가 10개 있을 경우와 50개 있을 경우의 속도가 너무 많이 차이가 나서.. 라는 단순한 발상이었는데요.

가상도

화면에서 보이는 Index +- 3 렌더링 이외에는 전부 화면에서 지울 수 있다면 메모리를 줄일 수 있을 것이라 생각하였는데요.

다행히 FlatList에서 해당 옵션을 제어할 수 있는 옵션이 있었습니다.

 

windowSize 옵션 설정으로 최대 렌더링 갯수에 제한을 두었으며

maxToRenderPerBatch 설정으로 한 번 시도하는 렌더링에 렌더링 갯수를 조절하였습니다.

removeClippedSubviews 을 사용하여 렌더링 아이템을 제외한 아이템을 unmount 시켜 메모리를 절약했습니다.

 

이 세가지 옵션의 조합으로 렌더링 아이템들이 계~~~속해서 쌓여서 앱에 부하를 주는 일이 줄어들었습니다.

이후 데이터 누적에도 UI FPS 60을 계속해서 유지하게 되었습니다.

 

하지만 unmount 된 아이템을 다시 사용자의 화면내에 노출 시켰을 경우 다시 렌더링을 하게 됩니다.

재렌더링시 unmount 된 렌더링 아이템을 다시 그려주며 JS Thread가 적잖이 사용이 되고 있었습니다.

 

또한 unmount 된 아이템의 UI 를 그려주는 화면을 그려주는 시간을 사용자에게 보여주게 되었는데

그리는 시간 또한 그렇게 빠른 편은 아니었습니다.

또한 빠르게 스크롤을 하면 대기시간이 길어지게 되었고 더 나은 방법을 찾게 되었습니다.


개선방법 4.  FlashList 도입

FlashList 도입을 결정하게 되었습니다.

https://shopify.github.io/flash-list/

 

FlashList - super fast list for react native

FlashList is a faster alternative to FlatList with a similar API. Migrate in a few seconds and get major performance boost.

shopify.github.io

https://github.com/shopify/flash-list

 

GitHub - Shopify/flash-list: A better list for React Native

A better list for React Native. Contribute to Shopify/flash-list development by creating an account on GitHub.

github.com

 

우선 기존 FlatList 보다 JS Thread 10배차이, UI Thread 5배 차이,

월등한 퍼포먼스를 가진 라이브러리 였으며, FlatListProps를 기반으로 만들어져 빠르게 도입이 가능할 것이라고 판단하였습니다.

 

JS, UI Thread의 퍼포먼스 향상으로 초기 렌더링 시간이 개선되었습니다.

FlashList를 사용하여 FlatList에 비해서 월등히 좋은 퍼포먼스의 메모리 사용

estimatedItemSize 각 아이템의 높이를 계산하여 offsetY 위치를 보존합니다.

 

약 3s → 1.5s 이상으로 2배 이상 빨라져 사용자가 대기하는 시간을 대폭 줄였습니다.

고맙다!!! FlashList

하지만 FlashList에서 useState 값을 타 아이템과 공유하는 이슈가 발생하였습니다.

1번 아이템에서 저장된 state가 5번 아이템과 공유하는 이슈가 생겨서 개선이 시급했습니다.

FlatList 내에서 useState 처럼 동적인 변화가 필요할 때 extraData 에 담아 사용하도록 되어있었는데요.

 

지금까지 렌더링 아이템을 화면에서 지우지 않아 useState를 써도 아무런 문제가 없던 것 처럼 보인거였습니다.

사실은 원래부터 extraData 를 사용하여 상태변화를 시켜야 했고 렌더링 아이템 내에서는 가급적 useState를 사용해서는 안됐습니다.

몰랐지..

렌더링 아이템에서 존재하는 useState를 전 ㅡㅡㅡㅡㅡ 부 삭제하였습니다.

렌더링 아이템의 변화 state는 아이템 밖에서 이뤄지도록 변경이 필요하였습니다.

const renderItem = useCallback({item, extraData}): RenderInfo<any>) => {

    const {
        follow,
        // 기타 등등
    } = extraData as Model;
    const followExtraData = follow.find(val => val === item.userId);
    // 기타 등등
	
    return (
    	<renderItem
        	...
            follow={followExtraData?.follow ?? item.followYn}
        />
        
    )
}, [extraData]);

///////

const useFeedInteractionEvent = (deps?: ~~) => {
	// 인터랙션 이벤트가 발동할 시에 extraData를 바꿔줄 이벤트들
}

useState 대신

renderItem 에서 특정 인터렉션이 일어나면

extraData의 변수를 find 함수로 감지 및 변화하도록 하였습니다.

또한 renderItem을 useCallback 으로 감싸 extraData를 deps로 설정하여

해당 아이템만을 재렌더링하도록 로직을 변경하였습니다.


결론

과정

  1. memo, useCallback 을 사용하여 렌더링 아이템을 캐싱하여 사용
  2. 이미지 리사이즈 로직을 요청 및 적용하여 이미지 사이즈 다운 75kb
  3. react-native-fastImage 를 사용한 이미지 캐싱 및 렌더링 방식 교체로 성능 향상
  4. FlatList 렌더링 아이템 갯수 조절
  5. FlashList 도입, offsetY 저장, 렌더링 아이템 캐싱
  6. Feed 내 useState 삭제 및 extraData 로 동적 state 관리

결과

  1. 초기 렌더링 속도 6s → 1s ~ 2s
  2. 데이터 누적에 따른 UI FPS 변화 변경 전 60 → 30 → 20 ~~~~ 변경 후 60 FPS
  3. 빠른 스크롤 시 렌더링 아이템 노출 시간 0.5s → 0.1s 이하

3.8ms 면 0.0038 초???

 

아직 리팩토링 할 곳이 많을 수 있지만 우선 저희팀은 피드 목록 개선을 성공적으로 마무리했다고 생각했습니다.

개발 팀 내부에서도 눈에 띄게 빨라진 Feed를 보고 긍정적인 반응을 보여주었습니다!!! 야호!!@!@

 

 

참고 링크

https://velog.io/@miniyoung37/React-Native%EC%97%90%EC%84%9C-%EA%B0%80%EC%9E%A5-%EB%B9%A0%EB%A5%B8-List-FlashList

'React-native' 카테고리의 다른 글

Metro 란?  (0) 2023.09.03
React-native 디버깅 과정 공유  (0) 2023.07.24
Comments