ScrollView и FlatList
Контент может не влезать на экран. Для прокрутки — ScrollView и FlatList.
ScrollView
Для небольшого количества контента:
import { ScrollView, Text, View } from 'react-native';
<ScrollView>
<Text>Строка 1</Text>
<Text>Строка 2</Text>
{/* ... */}
</ScrollView>
Свойства:
<ScrollView
horizontal
showsVerticalScrollIndicator={false}
contentContainerStyle={{ padding: 16 }}
onScroll={(e) => console.log(e.nativeEvent.contentOffset.y)}
scrollEventThrottle={16}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
>
{/* контент */}
</ScrollView>
Проблема: ScrollView рендерит всё сразу. Если элементов 1000 — приложение упадёт или будет тормозить.
FlatList
Для больших списков. Рендерит только видимые элементы (виртуализация).
import { FlatList, Text, View } from 'react-native';
<FlatList
data={items}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => (
<View>
<Text>{item.title}</Text>
</View>
)}
/>
Обязательные пропсы:
data— массивrenderItem— функция рендера элементаkeyExtractor— уникальный ключ
Разделители
<FlatList
data={items}
ItemSeparatorComponent={() => <View style={styles.separator} />}
ListHeaderComponent={<Text>Заголовок</Text>}
ListFooterComponent={<Text>Конец</Text>}
ListEmptyComponent={<Text>Пусто</Text>}
/>
Pull to refresh
const [refreshing, setRefreshing] = useState(false);
async function onRefresh() {
setRefreshing(true);
await loadData();
setRefreshing(false);
}
<FlatList
data={items}
refreshing={refreshing}
onRefresh={onRefresh}
renderItem={...}
/>
Пагинация
<FlatList
data={items}
onEndReached={() => loadMore()}
onEndReachedThreshold={0.5}
ListFooterComponent={isLoading ? <ActivityIndicator /> : null}
/>
onEndReachedThreshold — 0.5 означает «сработать за пол-экрана до конца».
Оптимизация
<FlatList
data={items}
renderItem={renderItem}
keyExtractor={keyExtractor}
initialNumToRender={10}
maxToRenderPerBatch={10}
windowSize={5}
removeClippedSubviews
getItemLayout={(data, index) => ({
length: ITEM_HEIGHT,
offset: ITEM_HEIGHT * index,
index,
})}
/>
- initialNumToRender — сколько рендерить сразу
- maxToRenderPerBatch — за раз
- windowSize — сколько экранов вокруг
- removeClippedSubviews — удалять невидимые
- getItemLayout — если элементы одинаковой высоты
useCallback
renderItem и keyExtractor должны быть мемоизированы:
const renderItem = useCallback(
({ item }: { item: Item }) => <ItemCard item={item} />,
[]
);
const keyExtractor = useCallback(
(item: Item) => item.id.toString(),
[]
);
SectionList
Секции с заголовками:
import { SectionList } from 'react-native';
<SectionList
sections={[
{ title: 'A', data: ['Алиса', 'Антон'] },
{ title: 'Б', data: ['Борис', 'Богдан'] },
]}
keyExtractor={(item, index) => item + index}
renderItem={({ item }) => <Text>{item}</Text>}
renderSectionHeader={({ section }) => <Text>{section.title}</Text>}
/>
Если ты уже знаешь React
В вебе — .map внутри <div>. В React Native — FlatList. Это не просто оптимизация: без виртуализации длинные списки не работают.
Итоги
ScrollView— для малого контентаFlatList— для списковkeyExtractorобязателенonEndReachedдля пагинацииrenderItemмемоизируйSectionListдля секций