habrahabr

Делаем таблицу с бесконечной прокруткой без event listener

  • суббота, 15 июня 2019 г. в 00:18:37
https://habr.com/ru/post/456046/
  • Разработка веб-сайтов
  • JavaScript
  • Программирование
  • Usability
  • ReactJS


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



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