diff --git a/README.md b/README.md index aea266e..b12d1b5 100644 --- a/README.md +++ b/README.md @@ -77,8 +77,29 @@ const renderItem: ListRenderItem = useCallback( #### 2-6 initialNumToRender -- 처음 렌더링할 `FlatList` 아이템의 개수를 조절할 수 있습니다. 디바이스 크기가 조금씩 다르므로, 한 화면에 들어갈 수 있는 아이템의 수도 달라질 수 있기 때문에 적절한 개수를 사용해야 합니다. +- 처음 렌더링할 `FlatList` 아이템의 개수를 조절할 수 있습니다. 디바이스 크기가 조금씩 다르므로, 한 화면에 들어갈 수 있는 아이템의 수도 달라질 수 있기 때문에 적절한 개수를 사용해야 합니다. 첫 화면으로 렌더링된 item들은 `scrollToTop` props의 인식 성능을 위해 스크롤을 벗어나도 unmount되지 않습니다. ### 최적화 방법 3. [react-native-fast-image](https://github.com/DylanVann/react-native-fast-image)의 사용 - `FlatList`에서 `Render window`를 벗어난 컴포넌트는 언마운트됩니다. 언마운트된 컴포넌트는 다시 `Render window`에 들어왔을 때 마운트됩니다. `FlatList`의 자식 컴포넌트가 만약 이미지를 포함한다면 매번 새로운 네트워크 요청을 하게됩니다. `FastImage`를 적용하면 캐시된 이미지를 사용하여 불필요한 네트워크 요청을 건너뛰고, 빠른 이미지 로딩으로 `JS Thread`의 부하를 감소시킬 수 있습니다. + +### windowSize와 maxToRenderPerBatch 그리고 removeClippedView의 상관 관계 + +#### TL;DR + +- `windowSize`는 아이템을 렌더링할 영역(`render window`)을 계산하는데 사용됩니다. `windowSize`의 값이 커서 렌더링할 아이템의 수가 많아지더라도 한번에 렌더링하는 아이템의 개수는 `maxToRenderPerBatch`를 넘을 수 없습니다. +- 매번 스크롤할 때마다 `render window`는 다시 계산됩니다. +- 빠른 스크롤로 인해 빈화면이 나오는 이유는 `maxRenderToBatch`로 인해 한번에 렌더링되는 아이템의 개수보다 화면에 렌더링되어야할 아이템의 개수가 많아서 일 수 있습니다.(물론 무거운 네트워크 작업으로 인한 확률이 더 높습니다) +- `removeClippedView`는 네이티브 단에서 사용됩니다. `VirtualizedList`에서 계산된 `render window` 영역이 변할 때마다 해당 영역을 벗어난 아이템들을 `unmount` 시켜줍니다. 즉 `removeClippedView`가 기준으로 삼는 영역은 스크린 화면 영역이 아닌 `render window` 영역입니다. + +#### Detailed + +- `FlatList`의 코드를 살펴보면, 성능과 관련된 `windowSize`, `maxToRenderPerBatch`, `removeClippedView`는 `virtualizedListProps`에 속하는 것을 알 수 있습니다. 따라서 이 `props`들은 `virtualizedList`로 넘겨집니다. +- `VirtualizedList`는 `ScrollView`의 `Wrapper` 컴포넌트입니다. 메모리 사용과 대용량의 리스트 데이터를 렌더링할 때 퍼포먼스 향상을 위해 현재 `active`한 아이템을 `render window`에 위치시키고, `render window` 밖에 있는 모든 아이템들은 적절한 사이즈를 가진 빈 공간으로 대체합니다. +- 이때 `render window`라는 영역을 계산하기 위해 내부적으로 [computeWindowedRenderLimits](https://github.com/facebook/react-native/blob/6e9d3bf7b145ce46ad4f46f03d63ea4cbf96ced1/Libraries/Lists/VirtualizeUtils.js#L101) 라는 함수를 사용합니다. 이 함수에서 계산된 `newCellsAroundViewport` 영역이 바로 아이템을 렌더링할 `render window`를 의미합니다. +- `computeWindowedRenderLimits` 함수에서는 렌더링할 영역을 계산하기 위해 `windowSize`와 `maxToRenderPerBatch` `props`를 사용합니다. 함수의 로직을 살펴보면 `windowSize`로 아이템을 렌더링할 영역에 영향을 미치며, `maxToRenderPerBatch`는 영역 내에 렌더링될 아이템의 개수를 제한하는데 [사용](https://github.com/facebook/react-native/blob/6e9d3bf7b145ce46ad4f46f03d63ea4cbf96ced1/Libraries/Lists/VirtualizeUtils.js#L199)됩니다. `windowSize`에 의해 렌더링할 아이템이 많아지더라도 한번에 `maxToRenderPerBatch` 보다 많은 아이템을 렌더링할 수는 없습니다. 스크롤 이벤트가 일어날 떄마다 `render window`의 영역은 변하며, 그 과정에서 한번에 `maxToRenderPerBatch`의 개수만큼 렌더링을 시도하게 됩니다. +- `removeClippedView`는 위 과정에서 계산된 영역과 함께 `ScrollView`로 넘겨집니다. +- `ScrollView`에서는 넘겨받은 `props`를 기반으로 `__INTERNAL_VIEW_CONFIG`라는 객체를 만들어서 네이티브에 [등록](https://github.com/facebook/react-native/blob/main/Libraries/Components/ScrollView/ScrollContentViewNativeComponent.js)합니다. + +- 안드로이드의 [ReactScrollView](https://github.com/facebook/react-native/blob/main/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java) 부분을 살펴보면, `mRemoveClippedSubviews`가 `true`이면 `updateClippingRect`라는 메서드를 [호출](https://github.com/facebook/react-native/blob/main/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ReactScrollView.java#L282)하고 있습니다. +- `updateClippingRect`는 [calculateClippingRect](https://github.com/facebook/react-native/blob/6e9d3bf7b145ce46ad4f46f03d63ea4cbf96ced1/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactClippingViewGroupHelper.java#L34)를 사용하는데, 여기에서 `clipping`할 영역을 계산하고 있음을 확인할 수 있습니다.