react-virtualized — это отзывчивый компонент для эффективного рендеринга больших списков и табличных данных.
Типичные проблемы разработки
Если показано, в каждом классе есть классы 1/2/3 с более чем 1000 учеников.Студенческие компоненты:
function Student({student}) {
return <div>{student.name}</div>
}
Если мы отобразим весь список напрямую, только список студентов сгенерирует более 1000 тегов div.
Часто нашими студенческими компонентами будут:
function Student({student, ...rest}) {
return (
<div>
...
<div>{student.name} ....</div>
...
</div>
)
}
Количество DOM в это время становится невообразимым.
Все мы знаем, что если структура DOM слишком велика, у пользователя возникнут проблемы с взаимодействием с веб-страницей, такие как прокрутка, щелчок и другие распространенные операции. отражение виртуального DOM в реальном DOM также будет отличным.. Когда пользователь нажимает, чтобы переключить классы, происходит зависание второго уровня.
Оптимизирован с реактивной виртуализацией
В экосистеме react react-virtualized уже давно существует как оптимизация длинного списка, сообщество его обновляет и поддерживает, а обсуждения идут постоянно, что также означает, что это давняя острая проблема!
Основная идея решения вышеуказанных проблем: загружать только компоненты в видимой области
react-virtualized различает нашу сцену прокрутки на локальную прокрутку в области просмотра и прокрутку на основе области просмотра Первая эквивалентна открытию независимой области прокрутки на странице, которая принадлежит внутренней прокрутке, которая очень похожа на прокрутку с помощью iscroll, а затем Другие используют прокрутку как часть прокрутки окна (для мобильных это более распространено), исходя из этого рассчитывают компоненты, которые нужно отображать в данный момент.
Выполнение
Компонент студента изменен на:
function Student({student, style, ...rest}) {
return (
<div style={style}>
...
<div>{student.name} ....</div>
...
</div>
)
}
Компонент списка учащихся:
import React from 'react'
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'
import { List as VList } from 'react-virtualized/dist/commonjs/List'
class StudentList extends React.Component {
constructor(props) {
super(props)
this.state = {
list: []
}
}
getList = () => {
api.getList.then(res => {
this.setState({
list: res
})
})
}
componentDidMount() {
this.getList()
}
render() {
const { list } = this.state
const renderItem = ({ index, key, style }) => {
return <Student key={key} student={list[index]} style{style} />
}
return (
<div style={{height: 1000}}>
<AutoSizer>
{({ width, height }) => (
<VList
width={width}
height={height}
overscanRowCount={10}
rowCount={list.length}
rowHeight={100}
rowRenderer={renderItem}
/>
)}
</AutoSizer>
</div>
)
}
}
(Высота во внешнем стиле div необязательна. Например, если ваша веб-страница представляет собой гибкий макет, вы можете использовать flex: 1, чтобы позволить реактивной виртуализации вычислить эту высоту)
В это время, если рост каждого ученика одинаков, проблема в основном решена!
Тем не менее, проблема возникает снова.Иногда у нашего студента будет неопределенный рост.Есть два способа решения проблемы.Рекомендуется решение с реактивно-виртуализированным компонентом CellMeasurer.
метод первый
Компонент списка учащихся изменен на:
import React from 'react'
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'
import { List as VList } from 'react-virtualized/dist/commonjs/List'
import { CellMeasurerCache, CellMeasurer } from 'react-virtualized/dist/commonjs/CellMeasurer'
class StudentList extends React.Component {
constructor(props) {
super(props)
this.state = {
list: []
}
}
measureCache = new CellMeasurerCache({
fixedWidth: true,
minHeight: 58
})
getList = () => {
api.getList.then(res => {
this.setState({
list: res
})
})
}
componentDidMount() {
this.getList()
}
render() {
const { list } = this.state
const renderItem = ({ index, key, parent, style }) => {
return (
<CellMeasurer cache={this.measureCache} columnIndex={0} key={key} parent={parent} rowIndex={index}>
<Student key={key} student={list[index]} />
</CellMeasurer>
)
}
return (
<div style={{height: 1000}}>
<AutoSizer>
{({ width, height }) => (
<VList
ref={ref => this.VList = ref}
width={width}
height={height}
overscanRowCount={10}
rowCount={list.length}
rowHeight={this.getRowHeight}
rowRenderer={renderItem}
deferredMeasurementCache={this.measureCache}
rowHeight={this.measureCache.rowHeight}
/>
)}
</AutoSizer>
</div>
)
}
}
Способ второй
пройти черезreact-heightилиissueМетод, упомянутый в обратном вызове расчета, решается на примере react-height:
Компонент списка учащихся изменен на:
import React from 'react'
import { AutoSizer } from 'react-virtualized/dist/commonjs/AutoSizer'
import { List as VList } from 'react-virtualized/dist/commonjs/List'
import ReactHeight from 'react-height'
class StudentList extends React.Component {
constructor(props) {
super(props)
this.state = {
list: []
heights = []
}
}
getList = () => {
api.getList.then(res => {
this.setState({
list: res
})
})
}
componentDidMount() {
this.getList()
}
handleHeightReady = (height, index) => {
const heights = [...this.state.heights]
heights.push({
index,
height
})
this.setState({
heights
}, this.vList.recomputeRowHeights(index))
}
getRowHeight = ({ index }) => {
const row = this.heights.find(item => item.index === index)
return row ? row.height : 100
}
render() {
const { list } = this.state
const renderItem = ({ index, key, style }) => {
if (this.heights.find(item => item.index === index)) {
return <Student key={key} student={list[index]} style{style} />
}
return (
<div key={key} style={style}>
<ReactHeight
onHeightReady={height => {
this.handleHeightReady(height, index)
}}
>
<Student key={key} student={list[index]} />
</ReactHeight>
</div>
)
}
return (
<div style={{height: 1000}}>
<AutoSizer>
{({ width, height }) => (
<VList
ref={ref => this.VList = ref}
width={width}
height={height}
overscanRowCount={10}
rowCount={list.length}
rowHeight={this.getRowHeight}
rowRenderer={renderItem}
/>
)}
</AutoSizer>
</div>
)
}
}
Теперь, если данные вашего списка получены сразу, проблема в основном решена!
А прокрутка загрузки?
реактивная виртуализация официально доступнаInfiniteLoader, надпись такая же, как и официальная!
Что, если отложить этот классический случай и разработать окно чата?
Окно чата отображается в обратном порядке. Когда данные загружаются в первый раз, положение полосы прокрутки должно быть внизу. Компонент List в react-virtualized предоставляет нам метод scrollToRow(index) для реализации. Если рост ученика непостоянен, используйте его напрямую.Кусок пирога, то есть вы не можете прокрутить вниз за один раз.Временное решение:
scrollToRow = (): void => {
const rowIndex = this.props.list.length - 1
this.vList.scrollToRow(rowIndex)
clearTimeout(this.scrollToRowTimer)
this.scrollToRowTimer = setTimeout(() => {
if (this.vList) {
this.vList.scrollToRow(rowIndex)
}
}, 10)
}
Вызывается при первой загрузке данных
Поскольку InfiniteLoader не поддерживает требование загрузки в обратном порядке, он может только получать данные прокрутки и выполнять связанные операции через метод onScroll.Следует отметить, что при возврате данных предыдущей страницы, если вы используете метод 1, вы необходимо выполнить this.measureCache.clear/clearAll, уведомить react-virtualized для пересчета.Метод 2 должен добавить все индексы в массиве state.heights к количеству данных на этот раз
getList = () => {
api.getList.then(res => {
const heights = [...this.state.heights]
heights.map(item => {
return {
index: item.index + res.length,
height: item.height
}
})
this.setState({
list: [...res, ...this.state.list],
heights
})
})
}
react-virtualized также имеет много интересных функций, а его собственная реализация также имеет большое справочное значение!react-virtualized githubпрогуляться