В этой статье я не буду рассказывать что нужно использовать как можно меньше контента для отображения или кэшировать изображения. Нет! Мы будет говорить только о реальной оптимизации списка и не важно что нам нужно отобразит, так как если клиент говорит хочу и все то с ним уже не поспоришь.


Начнем с самого простого примера. Допустим у нас есть массив.


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 будет передаватся уже не сам элемент а объект содержащий три параметра:


  1. item — сам элемент массива
  2. index — индекс элемента в массиве
  3. 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 но я считаю их довольно сомнительными и не буду о них говорить в данной статье чтобы не сбивать вас с толку