跳转到内容

VirtualList 虚拟列表

仅渲染可见项目来高效渲染大型列表。由 @tanstack/react-virtual 提供支持。

此组件需要 @tanstack/react-virtual 作为对等依赖:

Terminal window
npm install @tanstack/react-virtual
import { VirtualList } from 'asterui/virtuallist'

基础用法

高效渲染 10,000 个项目的列表。

import { VirtualList } from 'asterui/virtuallist'

function App() {
  const basicItems = Array.from({ length: 10000 }, (_, i) => ({
    id: i,
    name: `Item ${i + 1}`
  }))
  
  return (
      <VirtualList
        items={basicItems}
        height={300}
        itemHeight={40}
        className="border border-base-300 rounded-lg"
        renderItem={(item) => (
          <div className="p-2 border-b border-base-300 flex items-center h-full">
            {item.name}
          </div>
        )}
      />
    )
}

export default App

样式列表

带有卡片样式和悬停效果。

import { VirtualList } from 'asterui/virtuallist'

function App() {
  const users = Array.from({ length: 5000 }, (_, i) => ({
    id: i,
    name: `User ${i + 1}`,
    email: `user${i + 1}@example.com`
  }))
  
  return (
      <VirtualList
        items={users}
        height={300}
        itemHeight={60}
        className="border border-base-300 rounded-lg"
        renderItem={(user) => (
          <div className="p-3 hover:bg-base-200 transition-colors border-b border-base-300 h-full flex flex-col justify-center">
            <div className="font-medium">{user.name}</div>
            <div className="text-sm text-base-content/60">{user.email}</div>
          </div>
        )}
      />
    )
}

export default App

带间隙

项目之间有间隙。

import { VirtualList } from 'asterui/virtuallist'

function App() {
  const cardItems = Array.from({ length: 1000 }, (_, i) => ({
    id: i,
    title: `Card ${i + 1}`,
    description: 'A brief description'
  }))
  
  return (
      <VirtualList
        items={cardItems}
        height={300}
        itemHeight={72}
        gap={8}
        className="p-2 border border-base-300 rounded-lg"
        renderItem={(item) => (
          <div className="card bg-base-200 p-3 h-full flex flex-col justify-center">
            <h3 className="font-bold">{item.title}</h3>
            <p className="text-sm text-base-content/70">{item.description}</p>
          </div>
        )}
      />
    )
}

export default App

滚动回调

跟踪滚动位置以进行无限加载或分析。

Scroll position: 0px
import { VirtualList } from 'asterui/virtuallist'
import { useState } from 'react'

function App() {
  const scrollItems = Array.from({ length: 10000 }, (_, i) => ({ id: i }))
  const [scrollTop, setScrollTop] = useState(0)
  
  return (
      <div>
        <div className="mb-2 text-sm text-base-content/70">Scroll position: {Math.round(scrollTop)}px</div>
        <VirtualList
          items={scrollItems}
          height={250}
          itemHeight={40}
          onScroll={setScrollTop}
          className="border border-base-300 rounded-lg"
          renderItem={(_, index) => (
            <div className="p-2 border-b border-base-300 flex items-center h-full">
              Row {index + 1}
            </div>
          )}
        />
      </div>
    )
}

export default App

可变高度(聊天)

使用 Chat 组件的不同长度的聊天消息。

import { Chat } from 'asterui'
import { VirtualList } from 'asterui/virtuallist'

function App() {
  const chatMessages = [
    { id: 1, user: 'Alex', text: 'Hey!' },
    { id: 2, user: 'You', text: 'Hi Alex, what\'s up?' },
    { id: 3, user: 'Alex', text: 'Not much, just wanted to check if you\'re coming to the party on Saturday' },
    { id: 4, user: 'You', text: 'Oh yeah, I\'ll be there! What time does it start?' },
    { id: 5, user: 'Alex', text: '8pm' },
    { id: 6, user: 'You', text: 'Cool' },
    { id: 7, user: 'Alex', text: 'Can you bring some snacks? We\'re running low on chips and stuff. Maybe grab a couple bags of tortilla chips and some salsa if you can find good ones' },
    { id: 8, user: 'You', text: 'Sure thing, I\'ll stop by the store on the way' },
    { id: 9, user: 'Alex', text: 'Thanks!' },
    { id: 10, user: 'You', text: 'No problem. Who else is coming?' },
    { id: 11, user: 'Alex', text: 'Sarah, Mike, probably Jordan. Maybe a few others' },
    { id: 12, user: 'You', text: 'Nice' },
    { id: 13, user: 'Alex', text: 'Yeah it should be fun. We\'re gonna set up the projector in the backyard for a movie later if the weather holds up' },
    { id: 14, user: 'You', text: 'What movie?' },
    { id: 15, user: 'Alex', text: 'Haven\'t decided yet. Any suggestions?' },
    { id: 16, user: 'You', text: 'How about something funny? We could do a comedy' },
    { id: 17, user: 'Alex', text: 'Good idea' },
    { id: 18, user: 'You', text: 'Alright, see you Saturday then!' },
    { id: 19, user: 'Alex', text: 'See ya!' },
    { id: 20, user: 'You', text: '👋' },
  ]
  
  return (
      <VirtualList
        items={chatMessages}
        height={300}
        itemHeight={(msg) => 70 + Math.floor(msg.text.length / 40) * 24}
        className="bg-base-100 border border-base-300 rounded-lg p-2"
        renderItem={(msg) => (
          <Chat
            position={msg.user === 'You' ? 'end' : 'start'}
            header={msg.user}
            message={msg.text}
            color={msg.user === 'You' ? 'primary' : undefined}
          />
        )}
      />
    )
}

export default App
属性描述类型默认值
items要渲染的项目数组T[]-
height可滚动容器的高度number | string-
itemHeight固定高度,或返回可变高度的每项预估高度的函数number | ((item: T, index: number) => number)-
renderItem每个项目的渲染函数(item: T, index: number) => ReactNode-
overscan在可见区域外渲染的项目数number5
gap项目之间的间隙(像素)number0
width容器的宽度number | string-
className滚动容器的额外类string-
innerClassName内部容器的额外类string-
itemClassName每个项目包装器的额外类string-
onScroll滚动位置更改时的回调(scrollTop: number) => void-
data-testid用于测试的测试 IDstring-
  • 尽可能使用固定的 itemHeight 以获得最佳性能
  • 如果 renderItem 函数开销很大,请将其记忆化
  • 如果在快速滚动时看到闪烁,请增加 overscan
  • 保持项目组件简单 - 避免在渲染中进行繁重的计算