В этой статье я не буду рассказывать что нужно использовать как можно меньше контента для отображения или кэшировать изображения. Нет! Мы будет говорить только о реальной оптимизации списка и не важно что нам нужно отобразит, так как если клиент говорит хочу и все то с ним уже не поспоришь.
Начнем с самого простого примера. Допустим у нас есть массив.
const list = [{ _id: 567456, label: “CodingChipmunks” }, ...]
Какой самый простой способ отрисовать список небольшого размера? Правильно использовать метод .map
который возвращает новый массив и мы можем отобразить весь список. Что ж, давайте рассмотрим один пример:
render() {
return (
<View>
{list.map((item) => (
<View>
<Text>{item.label}</Text>
<View>))}
</View>
)
}
Правильно? Нет! Никогда не делай так!
- Во первых всегда нужно указывать key для каждого элемента. И пожалуйста не используйте для этого index элемента в массиве. Ниже я расскажу почему.
- Во вторых код смотрится неаккуратно
- Во третьих не используйте анонимные функции
Почему бы не сделать так:
renderItem = (item) => (
<View key={item._id}>
<Text>{item.label}</Text>
<View>
)
render() {
return (
<View>
{list.map((item) => this.renderItem(item)}
</View>)
}
или так:
const Item = (item) => (
<View >
<Text>{item.label}</Text>
<View>
)
class List extends React.Component {
…
render() {
return (
<View>
{list.map((item) => <Item key={item._id} />}
</View>)
}
}
Теперь стало гораздо лучше? Не совсем.
Осталось внести последний штрих. А именно не использовать анонимную функцию для рендеринга внутри .map
. Ведь при вызове метода render у нас каждый раз создается новая функция для рендеринга каждого item и не важно обновился компонент или нет. Никогда. Повторяю никогда так не делайте. Я часто натыкаюсь на код написанный в таком формате или когда опытный разработчик берётся за какой-то проект и там уже написано так. И знаете что? Они не то что не исправляют, они и сами так продолжают писать со словами
Зачем стараться если и до этого было не очень.
И сразу вопрос “Что? Как? Почему? Тебе разве не хочется сделать проект над которым ты работаешь лучше?” Все что нам нужно это немного изменить рендеринг.
А именно:
render() {
return (
<View>
{list.map(this.renderItem)}
</View>
)
}
И последний шаг который мы можем сделать это использовать так называемый PureComponent для рендеринга каждого item.
Примеры в студию!
class Item extends React.PureComponent {
...
}
Вот и все что нам нужно было!
А что если у меня большой и сложный список?
Думаю с простейшим рендерингом списка мы разобрались. Дальше посмотрим что делать с десятками элементов для рендеринга в списке. И нам нужно чтобы оно отображался бысто и плавно? Ответ на этот вопрос FlatList. Flatlist и все? Конечно же нет. Давайте разбираться что мы можем с ним сделать и какие методы для оптимизации использовать.
Базовый пример:
const list = [loyaltyCard1, loyaltyCard2, … , loyaltyCard100];
…
renderItem = ({ item: loyaltyCard, index }) => (...)
keyExtractor = loyaltyCard => loyaltyCard._id
render() {
<FlatList
keyExtractor={this.keyExtractor}
data={list}
renderItem={this.renderItem} />
}
Теперь мы можем не указывать в каждом элементе key
, а указать параметр keyExtractor
, это должна быть функция которая возвращает уникальный идентификатор каждого элемента в массиве.
В renderItem будет передаватся уже не сам элемент а объект содержащий три параметра:
- item — сам элемент массива
- index — индекс элемента в массиве
- separator — то что нам пока что не нужно
Это и есть все оптимизация?
Идем дальше
Если мы хотим сказать нашему списку что нужно перерисовать элементы но не хотим изменять изначальный массив с данными, то для этого существует отдельный параметр extraData
. При ее изменении список будет обновлен.
Если мы знаем точную высота каждой плашки то можем использовать getItemLayout. Если вы заранее знаете высоту вашего елемента то можно указать ее и тогда вы сможете пропустить измерение динамического содержимого
Из официального сайта по ReactNative
getItemLayout={(data, index) => (
{length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
)}
Но что если я не знаю высоту элемента но она никогда не меняется? Тогда можно воспользоваться методом getLayout и узнать эту самую высоту
onLayout = (event) => {
const {x, y, width, height} = event.nativeEvent.layout;
do something with layout
}
…
renderItem = ({ item: loyaltyCard, index}) => (
<View onLayout={this.onLayout} />
)
Массив слишком большой и рендеринг всего списка происходит оооочень долго что вызывает подтормаживания? Вас спасут initialNumToRender и maxToRenderPerBatch. С ними мы можем загружать контент частями.
initialNumToRender указывает сколько элементов должно быть отрендеренно при первой загрузке. Всегда указывайте такое количество чтобы заполнить видимую часть экрана от низу к верху и даже с запасом
maxToRenderPerBatch указывает сколько элементом будет загружено в следующих партиях
- Если вы используете lazyLoad через onEndReached и onEndReachedThreshold то указывайте количество что приходит в апи с одного запроса иначе наберетесь проблем с дублирующимися элементами в списке.
Теперь перейдём к довольно сомнительным методам оптимизации
Хочу предупредить что данные методы могут вызвать сбои в работе и что вы используете их на свой страх и риск.
А что если убирать элементы что не находятся в видимой области экрана? Согласитесь звучит неплохо
removeClippedSubviews — если этот параметр указать true
то елементы которые находятся вне зоны видимости будут скрываться.
- Будьте очень осторожны так как возможен такой кейс что при быстрой прокрутке вы наткнетесь на пустое пространство в которое уже позже будет загружен контент.
Рекомендую использовать вместе с getItemLayout.
Хочу еще сказать что можно настроить через какое количество вне зоны видимости элементы будут скрываться
На этом у меня пожалуй все. Есть еще несколько методов которые используются для оптимизации списков в ReactNative но я считаю их довольно сомнительными и не буду о них говорить в данной статье чтобы не сбивать вас с толку