В этой статье я не буду рассказывать что нужно использовать как можно меньше контента для отображения или кэшировать изображения. Нет! Мы будет говорить только о реальной оптимизации списка и не важно что нам нужно отобразит, так как если клиент говорит хочу и все то с ним уже не поспоришь.
Начнем с самого простого примера. Допустим у нас есть массив.
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 но я считаю их довольно сомнительными и не буду о них говорить в данной статье чтобы не сбивать вас с толку
ссылка на оригинал статьи https://habr.com/ru/post/493768/
Добавить комментарий