Что ж оно так лагает-то?
Если при рендеринге огромной таблицы с какой-нибудь transition анимацией не делать ничего дополнительно, то приложение будет лагать, а пользователь страдать.
В большинстве случаев когда мы используем event listener для создания таблицы с бесконечной прокруткой, нам не только нужно выполнить вычисления, связанные с вьюпортом и высотой строки, но и написать много логики в обработчике прокрутки, чтобы предотвратить слишком частую перерисовку, так как каждый коллбэк скролла будет вызывать setState
.
Код будет примерно таким:
componentDidMount() {
window.addEventListener('scroll', this.handleScroll)
}
handleScroll(e) {
// use window offset and boundingRect
const { ...someAttributes } = window;
const { ...someBoundingRect } = this.component
// some logic prevent re-render
if ( ... ) return;
// do some math
const newIndex = ...
// and how many rows should be rendered
this.setState({index: newIndex })
}
Но есть и другой подход к реализации бесконечной прокрутки таблицы, без знания чего-либо о значениях window или boundingRect.
Это IntersectionObserver. Определение из w3c:
Эта спецификация описывает API, который можно использовать, чтобы узнать видимость и положение элементов DOM («targets») относительно содержащехся в них элементов
При использовании этого метода вам не нужно знать высоту строки, вершину вьюпорта или вообще любое другое значение для выполнения математики.
Концепция в том, чтобы расставить анкоры с индексами в каждой контрольной точке и каждый раз, когда якорь триггерится, получать значение индекса и перерисовывать таблицу. Благодаря этому не нужно делать какой-нибудь математической магии с высотой DOM и вьюпортом.
Триггер анкора для индекса 1
Рендерим больше строк
Код с IntersectionObserver
будет примерно таким.
handleSentinel = (c) => {
if(!this.observer) {
// создаём observer
this.observer = new IntersectionObserver(
entries => {
entries.forEach(e => {
// если анкор стригерен, рендерим следующую секцию
if (e.isIntersecting) {
this.setState(
{ cursor: +e.target.getAttribute('index') }
);
}
});
},
{
root: document.querySelector('App'),
rootMargin: '-30px',
}
}
if (!c) return;
// наблюдаем за анкором
this.observer.observe(c)
}
render() {
const blockNum = 5;
return(
...
<tbody>
{MOCK_DATA.slice(0, (cursor+1) * blockNum).map(d =>
<Block>
{
// добавляем анкор в каждую контрольную точку
// например, через каждые 5 строк
d.id % blockNum === 0 ?
<span ref={this.handleSentinel} index={d.id / blockNum} />
: null
}
</Block>)}
</tbody>
...
)
}
Автор: germn