Closed7

[Scrap] React で無限スクロールする方法を調べる

へぶんへぶん

スクロール可能にする

そもそも、HTML/CSS で一定領域をスクロール可能にするどうやるのってところから分かってなかった。
領域(height, width) を指定して、overflow: 'auto' の指定でスクロール可能になるっぽい。

const InfiniteScrollPage = () => {
  return (
    <Box bg={'blue.500'} py={32}>
      <Center>
        <Box bg={'white'} w={80} overflow={'auto'} h={'300px'}>
          {indexes.map(i => (
            <Center key={i}>
              <Text>index of {i}</Text>
            </Center>
          ))}
        </Box>
      </Center>
    </Box>
  )
}
へぶんへぶん

無限スクロールのパッケージどんなのがあるか

自前で実装するのは考えてないので、パッケージを使いたい。ググったり npm 検索したりして調べる。

react-infinite-scroll-component

https://www.npmjs.com/package/react-infinite-scroll-component
結構使われてるっぽい。記事もたくさん出てくる。

react-infinite-scroller

https://www.npmjs.com/package/react-infinite-scroller
結構使われてるっぽいし、記事もたくさん出てくるけど、最終アプデが三年前なのでメンテされてないのでは。使わないことにする。

react-virtualized

https://www.npmjs.com/package/react-virtualized
ピンポイントで無限スクロールが作れるというよりかは、表作ったり要素が多い画面をパフォーマンスよく実装するツールキット、っぽい印象。全然触ってないのでなんとも言えないけど、若干難しそうな気がする。

へぶんへぶん

react-infinite-scroll-component を試す。

Next.js でも問題なく動く。

import React, { useState } from 'react'
import InfiniteScroll from 'react-infinite-scroll-component'
import { Box, Heading } from '@chakra-ui/react'

const ISC = () => {
  const [items, updateItems] = useState(Array.from({ length: 40 }))

  const fetchMoreData = () => {
    setTimeout(() => {
      updateItems(items.concat(Array.from({ length: 40 })))
    }, 1500)
  }

  return (
    <Box>
      <InfiniteScroll
        dataLength={items.length}
        next={fetchMoreData}
        hasMore={true}
        loader={<Heading>Loading...</Heading>}
        pullDownToRefresh={true}
        pullDownToRefreshContent={<Heading>Pulling</Heading>}
        refreshFunction={() => {
          setTimeout(() => {
            updateItems(Array.from({ length: 40 }))
          }, 1500)
        }}
      >
        {items.map((i, index) => (
          <Box key={index}>div - #{index}</Box>
        ))}
      </InfiniteScroll>
    </Box>
  )
}
export default ISC

ただ、画面の高さが長く、初期化時のアイテムが全部見えている状態になっていると、次のアイテムを呼び出す next が走らないっぽい。
一瞬問題かもと思ったけど、総アイテム数が少ない & next を呼び出す閾値を超えてるがことでnext が無限に呼ばれ続ける、的な挙動よりはるかに扱いやすいし、実用的に思う。

へぶんへぶん

react-virtualized

試そうと思ったが、行ごとに高さを計算しないといけないっぽい。ちゃんとやらないとうっかりミスしそう。オフの時間に触りたくない感がある。

素晴らしいブログ見つけた。
https://tmegos.hatenablog.jp/entry/react-virtualized-list-libraries

このブログ読む限り仮想 Window 使って無限スクロールやること自体、沼な感じがした。
実装コスト高い & バグりやすそう & 設計変更の追従面倒そう、ってだから必然性ない限り避けたい。

へぶんへぶん

react-virtuoso

https://www.npmjs.com/package/react-virtuoso
開発が活発な react-virtuoso というのがあるみたいなので、試してみる。

  • チャットとか上向きに伸びていく List にも対応しているよ、って書いてていい感じ。
  • increaseViewportBy ってプロパティで viewport の仮想的な長さ調整できるのいい感じ

しかし、今のところ描画されないので、明日以降に持ち越し。

import { Heading } from '@chakra-ui/react'
import { Virtuoso } from 'react-virtuoso'

const items: number[] = [0, 1, 2, 3, 4, 5, 6, 8, 9, 10]
const VirtuosoList = () => {
  return (
    <Virtuoso<number>
      style={{
        background: 'blue',
      }}
      height={600}
      width={800}
      data={items}
      totalCount={items.length}
      itemContent={index => (
        <Heading bg="yellow" color="black" key={index}>
          Index of {index}
        </Heading>
      )}
    />
  )
}
export default VirtuosoList
へぶんへぶん

凡ミスだった。修正して問題なく動いたのでヨシ。
react-virtuoso 良さそう。

ただ、こういう最適化するの最後の最後だな、とも思った。

import { Center, Box, Heading } from '@chakra-ui/react'
import { Virtuoso } from 'react-virtuoso'
import * as React from 'react'
import { useState } from 'react'

function* generate(min: number, max: number) {
  let currentValue = min

  while (currentValue < max) yield currentValue++
}

const indexes = Array.from(generate(0, 10000))

const VirtuosoList = () => {
  const [totalCount, updateTotalCount] = useState(100)
  return (
    <Box bg={'blue'} h={'100vh'} w={'100vw'}>
      <Center h={'100%'} w={'100%'}>
        <Box h={'50vh'} w={'50vw'} overflow={'auto'} bg={'white'}>
          <Heading>Total count is {totalCount}</Heading>
          <Virtuoso
            totalCount={totalCount}
            endReached={_ => updateTotalCount(totalCount + 100)}
            increaseViewportBy={200}
            itemContent={index => <div>Item {index}</div>}
          />
        </Box>
      </Center>
    </Box>
  )
}
export default VirtuosoList
へぶんへぶん

結論

実際の Web サイトで実装したら色々変わりそうだけど、以下のが結論。

  • 最初は react-infinite-scroll-component サクッと作る。
  • パフォーマンスに問題ありそうだなってなったら、react-virtuoso を使う。
このスクラップは2021/09/23にクローズされました