import { useEffect, useMemo, useRef, useState } from 'react'
import { faker } from '@faker-js/faker'
type Person = {
id: number
// userId: string
firstName: string
lastName: string
age: number
}
const range = (len: number) => {
const arr: number[] = []
for (let i = 0; i < len; i++) {
arr.push(i)
}
return arr
}
const newPerson = (num: number): Person => {
return {
id: num,
// userId: faker.string.uuid(),
firstName: faker.person.firstName(),
lastName: faker.person.lastName(),
age: faker.number.int(40),
}
}
const ROW_HEIGHT = 35
const rows = range(2000).map((index): Person => newPerson(index))
const rowCount = rows.length
export const VirtualScroll = () => {
const gridRef = useRef<HTMLDivElement>(null)
const [scrollTop, setScrollTop] = useState(0)
const [clientHeight, setClientHeight] = useState(0)
const handleScroll = ({ currentTarget: { scrollTop } }: React.UIEvent<HTMLDivElement>) => setScrollTop(scrollTop)
useEffect(() => {
if (!gridRef.current) return
const handleResize = () => {
if (!gridRef.current) return
setClientHeight(gridRef.current.clientHeight)
}
const { scrollTop, clientHeight } = gridRef.current
setScrollTop(scrollTop)
setClientHeight(clientHeight)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
const { startIndex, endIndex } = useMemo(() => {
const viewportHeight = clientHeight
const startIndex = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT))
const visibleRowCount = Math.min(rowCount - startIndex, Math.ceil(viewportHeight / ROW_HEIGHT))
const endIndex = startIndex + visibleRowCount
return { startIndex, endIndex }
}, [clientHeight, scrollTop])
const visibleRows = rows.slice(startIndex, endIndex)
return (
<div style={{ flexGrow: 1, display: 'flex', flexDirection: 'column' }}>
{/* <div style={{ display: 'flex', gap: '1em' }}>
<span>scrollTop: {scrollTop.toLocaleString()}px</span>
<span>clientHeight: {clientHeight.toLocaleString()}px</span>
<span>startNode: {startIndex.toLocaleString()}</span>
<span>visibleNodesCount: {endIndex - startIndex}</span>
</div> */}
<div
ref={gridRef}
style={{
display: 'grid',
blockSize: 350,
flexGrow: 1,
gridTemplateRows: `repeat(${rows.length}, 35px)`,
overflow: 'auto',
border: '1px solid gray',
}}
onScroll={handleScroll}
>
{visibleRows.map((item, i: number) => (
<div
key={item.id}
style={{
gridRowStart: startIndex + i + 1,
display: 'flex',
gap: '1em',
height: ROW_HEIGHT,
borderBottom: '1px solid gray',
}}
>
<span>{i + 1}</span>
<span>{item.id}</span>
{/* <span>{item.userId}</span> */}
<span>{item.firstName}</span>
<span>{item.lastName}</span>
<span>{item.age}</span>
</div>
))}
</div>
</div>
)
}
브라우저 resizing 까지는 대응했지만, debounce 미적용이라...
'Javascript > React.js - Next.js' 카테고리의 다른 글
| heif, heic 파일 webp로 전환하는 방법 (1) | 2026.05.29 |
|---|---|
| next.js 에서 TinyMCE Editor 적용 - community, cloud ver. (0) | 2023.08.21 |
| Next.js 다국어 라이브러리 next-i18next vs next-translate (2) | 2023.06.02 |