Для длинного списка, такого как структура данных из 1000 массивов, если вы хотите отобразить эти 1000 данных одновременно и сгенерировать соответствующие 1000 собственных DOM, мы знаем, что собственный элемент DOM очень сложен, если длинный список создается таким количеством элементов DOM, что это, вероятно, сделает страницу невосприимчивой.
Ядром React является «виртуальный дом», мы также можем элегантно оптимизировать длинные списки, используя виртуальные списки.
- Дефект рендеринга нативного dom длинного списка
- Как виртуальные списки оптимизируют длинные списки
- Оптимизация длинных списков с помощью реактивной виртуализации
- Оптимизация длинных списков с помощью react-tiny-virtual-list
Оригинальный адрес этой статьи опубликован в моем блоге:
Добро пожаловать в звезду и форк~
Кодовый адрес варианта использования в этой статье:
1. Дефекты рендеринга длинных списков нативного dom
Во-первых, мы попытались отрендерить 1000 DOM за раз без какой-либо оптимизации в проекте React.Каждый DOM содержит тег img.Нативный DOM сам по себе является очень сложным объектом после добавления тега img. Эффект рендеринга показан на следующем рисунке:
Из приведенного выше рисунка видно, что реальных DOM действительно сгенерировано 1000. После входа на страницу эти 1000 DOM нужно отрендерить, поэтому белый экран занимает много времени.
Кроме того, при прямой визуализации страницы с 1000 узлов DOM и запуске события прокрутки использование памяти также увеличивается, как показано на следующем рисунке:
Кроме того, одновременный рендеринг многих узлов DOM также вызовет следующие проблемы:
-
Легко потерять кадры, потому что рендеринг очень медленный, поэтому частоту кадров браузера поддерживать нельзя, и субъективно будет казаться, что страница зависла.
-
Веб-страница перестает отвечать на запросы, и события и т. д. не могут быть инициированы вовремя
Все вышеперечисленные эффекты отображаются на стороне ПК.Для некоторых мобильных устройств проблемы, вызванные рендерингом длинных списков без оптимизации, будут еще более усугубляться. Рендеринг длинных списков встречается во многих сценариях на мобильной стороне, таких как Weibo, фиды и т. д. Разумная оптимизация длинных списков может улучшить взаимодействие с пользователем.
Во-вторых, принцип оптимизации виртуального списка длинного списка
Принцип оптимизации длинных списков очень прост, его можно сформулировать в одном предложении:
Используйте массив для хранения позиций всех элементов списка и визуализируйте только элементы списка в видимой области.Когда видимая область прокручивается, вычислите, какие элементы должны отображаться в видимой области в соответствии с размером смещения прокрутки и позициями все элементы списка.
Конкретные этапы реализации следующие:
- Сначала определите размер родительского элемента, в котором расположен длинный список, размер родительского элемента определяет ширину и высоту видимой области.
- Определите ширину и высоту каждого элемента списка длинного списка и вычислите положение каждого элемента длинного списка относительно родительского элемента при начальных условиях и используйте массив для хранения информации о положении всех элементов списка.
- При рендеринге в первый раз отображаются только элементы подсписка в видимой области относительно родительского элемента, при прокрутке элементы подсписка, которые должны быть в видимой области, пересчитываются в соответствии со смещением прокрутки родительского элемента. Это гарантирует, что независимо от того, как прокручивается, фактически визуализированный узел DOM имеет только элементы списка в видимой области.
- Если предположить, что 5 элементов подсписка могут быть отображены в видимой области, всего в длинном списке 1000 элементов, но в каждый момент времени фактически отображается только 5 узлов DOM. 5. Дополнительное примечание: в этом случае родительский элемент обычно использует position: relative, а позиционирование дочернего элемента обычно использует: position: absolute или sticky.
После оптимизации виртуального списка, того же отображения 1000 DOM, содержащих изображения, время белого экрана будет значительно уменьшено. Конкретный эффект показан на следующем рисунке:
Для случая без оптимизации скорость рендеринга оптимизированного виртуального списка значительно повышается.
3. Оптимизируйте длинные списки с помощью реактивной виртуализации
В сообществе есть много компонентов React, которые реализуют виртуальные списки. Наиболее часто используемые из них — это react-virtualized и react-tiny-virtual-list.Первый более всеобъемлющий, а второй — более легкий. Далее мы представим эти две библиотеки компонентов React по отдельности.
1. Введение в реактивную виртуализацию
react-virtualized — отличная библиотека компонентов для реализации виртуальных списков. react-virtualized предоставляет некоторые базовые компоненты для реализации виртуальных списков, виртуальных сеток, виртуальных таблиц и т. д., которые могут сократить ненужный рендеринг DOM. Кроме того, предоставляется несколько компонентов более высокого порядка, которые могут достигать динамической высоты дочерних элементов, автоматически заполнять область просмотра и многое другое.
Основные компоненты react-virtualized включают в себя:
- Сетка: используется для оптимизации построения произвольной сетчатой структуры, передачи двумерного массива и визуализации структуры, похожей на шахматную доску.
- Список: список реализован на основе сетки, но это одномерный список, а не сетка.
- Таблица: Таблица также реализована на базе Grid.Таблица имеет фиксированный заголовок и может прокручиваться в вертикальном направлении.
- Masonry: его также можно прокручивать по горизонтали и вертикали.В отличие от Grid, размер каждого элемента можно настроить, или размер дочерних элементов также можно динамически изменять.
- Коллекция: Подобно форме потока водопада, она также может прокручиваться по горизонтали и вертикали.
Стоит отметить, что эти базовые компоненты унаследованы от PureComponent в React, поэтому при изменении состояния выполняется только поверхностное сравнение, чтобы определить, нужно ли перерисовывать или нет. .
В дополнение к этим основным компонентам react-virtualized также предоставляет несколько компонентов более высокого порядка, таких как ArrowKeyStepper. , AutoSizer, CellMeasurer, InfiniteLoader и т. д. В этой статье представлены часто используемые AutoSizer, CellMeasurer и InfiniteLoader.
- AutoSizer: в случае дочернего элемента дочерний элемент, содержащийся в AutoSizer, будет автоматически регулировать ширину и высоту визуальной области дочернего элемента в соответствии с изменением размера родительского элемента, а также регулировать визуальную область дочерний элемент Количество отображаемых элементов dom.
- CellMeasurer: этот компонент более высокого порядка может динамически изменять высоту дочерних элементов, что подходит для ситуаций, когда высота каждого дочернего элемента в длинном списке заранее не известна.
- InfiniteLoader: этот компонент высокого порядка используется для бесконечной прокрутки таблицы или списка и подходит для асинхронных запросов во время прокрутки и т. д.
2. Использование реактивно-виртуализированных базовых компонентов
Давайте познакомимся с часто используемыми базовыми компонентами Grid и List.
(1)Grid
Все основные компоненты в основном основаны на Grid.Пример простого Grid выглядит следующим образом:
import { Grid } from 'react-virtualized';
const list = [
['Jony yu', 'Software Engineer', 'Shenzhen', 'CHINA', 'GUANGZHOU'],
['Jony yu', 'Software Engineer', 'Shenzhen', 'CHINA', 'GUANGZHOU'],
['Jony yu', 'Software Engineer', 'Shenzhen', 'CHINA', 'GUANGZHOU'],
['Jony yu', 'Software Engineer', 'Shenzhen', 'CHINA', 'GUANGZHOU'],
['Jony yu', 'Software Engineer', 'Shenzhen', 'CHINA', 'GUANGZHOU'],
['Jony yu', 'Software Engineer', 'Shenzhen', 'CHINA', 'GUANGZHOU']
];
function cellRenderer ({ columnIndex, key, rowIndex, style }) {
return (
<div
key={key}
style={style}
>
{list[rowIndex][columnIndex]}
</div>
)
}
render(
<Grid
cellRenderer={cellRenderer}
columnCount={list[0].length}
columnWidth={100}
height={300}
rowCount={list.length}
rowHeight={80}
width={300}
/>,
rootEl
);
Отображаемый эффект показан на следующем рисунке:
Рендеринг сетки тоже рендерит только DOM узел видимой области.Интересным явлением является размер полосы прокрутки.Здесь Grid провел детальную оптимизацию.Полоса прокрутки будет отображаться только при прокрутке,а полоса прокрутки будет скрыто после остановки прокрутки.
(2)List
Далее давайте проиллюстрируем использование List на примере:
import { List } from 'react-virtualized';
import loremIpsum from "lorem-ipsum"
const rowCount = 1000;
const list = Array(rowCount).fill().map(()=>{
return loremIpsum({
count: 1,
units: 'sentences',
sentenceLowerBound: 3,
sentenceUpperBound: 3
}
})
function rowRenderer ({
key,
index,
isScrolling,
isVisible,
style
}) {
return (
<div
key={key}
style={style}
>
{list[index]}
</div>
)
}
export default class TestList extends Component{
render(){
return <div style={{height:"300px",width:"200px"}}>
<List
width={300}
height={300}
rowCount={list.length}
rowHeight={20}
rowRenderer={rowRenderer}
/>
</div>
}
}
Использование List также очень просто.Вы можете построить список рендеринга, указав общее количество строк в списке, rowCount, высоту каждой строки, rowHeight и функцию rowRenderer каждого рендеринга. Конкретный эффект показан на следующем рисунке:
2. Использование реактивно-виртуализированных компонентов высокого порядка
В сочетании со списком, чтобы увидеть использование реактивных виртуализированных компонентов более высокого порядка.
(1)AutoSizer
Прежде всего, давайте рассмотрим недостатки неиспользования AutoSizer. Как показано на рисунке ниже, List может указывать только фиксированный размер. Если размер родительского элемента, в котором он расположен, изменяется, List не будет активно заполнять визуальная область родительского элемента:
Как видно из рисунка выше, List не может автоматически заполнять родительский элемент. Поэтому здесь нам нужно использовать AutoSizer. Использовать AutoSizer тоже очень просто, нам нужно только основываться на List:
class TestList extends Component{
render(){
return <div>
<AutoSizer>
{({ height, width }) => (
<List
height={height}
rowCount={list.length}
rowHeight={20}
rowRenderer={rowRenderer}
width={width}
/>
)}
</AutoSizer>
</div>
}
}
Результаты, как показано ниже:
Из вышеизложенного видно, что AutoSizer может динамически адаптироваться к изменениям ширины и высоты родительского элемента.
Но есть и проблема:
Дочерний элемент слишком длинный, и высота дочернего элемента не может быть адаптирована после изменения строки, то есть динамическое изменение высоты дочернего элемента не поддерживается только через базовый компонент Список..
(2)CellMeasurer
Чтобы решить вышеупомянутую проблему, связанную с динамическим изменением дочерних элементов, мы можем использовать компонент более высокого порядка CellMeasurer:
import { List,AutoSizer,CellMeasurer, CellMeasurerCache} from 'react-virtualized';
const cache = new CellMeasurerCache({ defaultHeight: 30,fixedWidth: true});
function cellRenderer ({ index, key, parent, style }) {
console.log(index)
return (
<CellMeasurer
cache={cache}
columnIndex={0}
key={key}
parent={parent}
rowIndex={index}
>
<div
style={style}
>
{list[index]}
</div>
</CellMeasurer>
);
}
Для списка, который необходимо отобразить, это выглядит так:
class TestList extends Component{
render(){
return <div>
<AutoSizer>
{({ height, width }) => (
<List
height={height}
rowCount={list.length}
rowHeight={cache.rowHeight}
deferredMeasurementCache={cache}
rowRenderer={cellRenderer}
width={width}
/>
)}
</AutoSizer>
</div>
}
}
Окончательный результат выглядит так:
Как мы видим из рисунка выше, высота элементов подсписка может динамически изменяться, а динамическая высота подэлементов может быть достигнута через CellMeasurer.
(3)InfiniteLoader
Наконец, давайте рассмотрим этот сценарий бесконечной прокрутки.Во многих случаях нам может понадобиться постраничная загрузка, что является распространенным сценарием бесконечной прокрутки в пределах видимой области. react-virtualized предоставляет компонент высокого порядка InfiniteLoader для бесконечной прокрутки.
Использование InfiniteLoader очень простое, просто следуйте за документом, то есть переходите на следующую страницу дома путем листания страниц, а функция, вызываемая листанием страниц, такова:
function loadMoreRows ({ startIndex, stopIndex }) {
return new Promise(function(resolve,reject){
resolve()
}).then(function(){
//模拟ajax请求
let temList = Array(10).fill(1).map(()=>{
return loremIpsum({
count: 1,
units: 'sentences',
sentenceLowerBound:3,
sentenceUpperBound:3
})
})
list = list.concat(temList)
})
}
Окончательный эффект выглядит следующим образом:
Выглядит он так же, как и базовый компонент List, разница только в том, что автоматически выполняется функция loadMoreRows для обновления списка при прокрутке.
(4) Резюме
С помощью базовых компонентов Grid, List и высокоуровневых компонентов AutoSizer, CellMeasurer и InfiniteLoader можно создавать более сложные сцены, но есть недостаток, заключающийся в том, что хотя CellMeasurer в определенной степени поддерживает изменение высоты динамических подэлементов, на самом деле это оценка, существует множество граничных условий, которые нельзя адаптировать к сцене динамических элементов, особенно изменение высоты, вызванное многими текстовыми узлами. Но нет большой проблемы с поддержкой динамической высоты для узлов изображения.
Например, пограничный случай, когда CellMeasurer не может поддерживать динамическую высоту текста:
Как видно из приведенного выше рисунка, в процессе медленного сжатия, если сжатие слишком мало, высота подэлементов не увеличивается динамически, и появляется наложение текста.
4. Оптимизируйте длинные списки с помощью react-tiny-virtual-list
react-tiny-virtual-list — это относительно легкий компонент, который реализует виртуальные списки, в отличие от react-virtualized, который поддерживает оптимизации рендеринга, такие как сетки и таблицы. react-tiny-virtual-list поддерживает только списки, которые просты в использовании, а его исходный код составляет всего более 700 строк.
Его очень легко использовать:
import VirtualList from 'react-tiny-virtual-list';
const data = ['A', 'B', 'C', 'D', 'E', 'F','A', 'B', 'C',
'D', 'E', 'F','A', 'B', 'C', 'D', 'E', 'F',
'A', 'B', 'C', 'D', 'E', 'F'];
export default class TinyVirtual extends Component {
render(){
return <VirtualList
width='100%'
height={200}
itemCount={data.length}
itemSize={50} // Also supports variable heights (array or function getter)
renderItem={({index, style}) =>
<div key={index} style={style}>
// The style property contains the item's absolute position Letter: {data[index]}, Row: #{index}
</div>
}
/>
}
}
Окончательные результаты рендеринга аналогичны и могут также поддерживать бесконечную прокрутку и так далее.
Но у react-tiny-virtual-list есть фатальный недостаток:
Динамическая высота или ширина дочерних элементов вообще не поддерживается.
V. Резюме
В этой статье представлен принцип оптимизации виртуального списка и широко используемая библиотека компонентов React, которая может оптимизировать виртуальные списки. В следующей статье будет подробно представлен исходный код react-tiny-virtual-list и react-virtualized, так что следите за обновлениями.