Что ж оно так лагает-то?

Если при рендеринге огромной таблицы с какой-нибудь 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> ... ) }
