Ant Design List 组件完全指南:从基础列表到虚拟滚动与网格布局
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/gh_mirrors/ant/ant-design
本指南围绕 antd 仓库 List 组件文档 展开,系统讲解 Ant Design 中用于展示同一主题下多元素内容的 List 组件。你将掌握 List 的完整 API 参数、List.Item / List.Item.Meta 的组合用法、分页与「加载更多」两种翻页方案、网格与响应式栅格、滚动加载与虚拟列表等高阶实战技巧,并通过源码实现理解其底层原理,能够直接落地到企业级中后台业务中。
何时使用 List
List 用于展示与同一主题(single subject)相关的内容。与 Table 的强结构化数据表格不同,List 的内容可以由不同类型、不同尺寸的多个元素组成——例如一条新闻列表可以同时包含文字、缩略图、段落摘要和操作按钮。
典型场景包括:
- 消息通知、动态 Feed 流;
- 商品 / 文章 / 数据卡片列表(结合
grid栅格模式); - 带头像、标题、描述的通讯录或成员列表;
- 需要"加载更多"或分页浏览的长数据列表。
List 采用dataSource+renderItem的数据驱动渲染模型,同时支持直接传入children自定义内容,兼顾声明式与命令式两种使用方式。
快速上手:三种基础形态
1. 简单列表(Simple List)
最简单的用法是传入字符串数组,配合header/footer/bordered等展示性属性。参考 simple.tsx:
import { Divider, List, Typography } from 'antd'; const data = [ 'Racing car sprays burning fuel into crowd.', 'Japanese princess to wed commoner.', 'Australian walks 100km after outback crash.', 'Man charged over missing wedding girl.', 'Los Angeles battles huge wildfires.', ]; const App: React.FC = () => ( <> <List header={<div>Header</div>} footer={<div>Footer</div>} bordered dataSource={data} renderItem={(item) => ( <List.Item> <Typography.Text mark>[ITEM]</Typography.Text> {item} </List.Item> )} /> </> ); export default App;要点:
header/footer接收任意ReactNode,渲染在列表顶部与底部;bordered为列表添加边框(边框样式由 Design Token 中的lineWidth、lineType、colorBorder生成,见 style/index.ts);- 默认尺寸为
default,可通过size切换为small/large,不同尺寸对应不同内边距 token(itemPaddingSM/itemPadding/itemPaddingLG)。
2. 基础列表 + 数据驱动(Basic List)
数据驱动的标准写法是dataSource+renderItem+List.Item.Meta组合,参考 basic.tsx:
import { Avatar, List } from 'antd'; const data = [ { title: 'Ant Design Title 1' }, { title: 'Ant Design Title 2' }, { title: 'Ant Design Title 3' }, { title: 'Ant Design Title 4' }, ]; const App: React.FC = () => ( <List itemLayout="horizontal" dataSource={data} renderItem={(item, index) => ( <List.Item> <List.Item.Meta avatar={<Avatar src={`https://api.dicebear.com/7.x/miniavs/svg?seed=${index}`} />} title={<a href="https://ant.design">{item.title}</a>} description="Ant Design, a design language for background applications, is refined by Ant UED Team" /> </List.Item> )} /> ); export default App;List.Item.Meta提供avatar(头像)、title(标题)、description(描述)三个槽位,是"头像 + 标题 + 摘要"布局的标准组合件。从源码 Item.tsx 可以看到,Meta 内部渲染为-item-meta-avatar、-item-meta-title、-item-meta-description三层结构,其间距、字号均由独立 Design Token 控制(详见后文"主题定制"一节)。
3. 垂直布局 + 操作项(Vertical)
当itemLayout="vertical"时,List.Item的actions(操作区)会从右侧移动到内容底部,而extra(额外内容)则显示在右侧,非常适合图文混排的资讯流,参考 vertical.tsx:
import { LikeOutlined, MessageOutlined, StarOutlined } from '@ant-design/icons'; import { Avatar, List, Space } from 'antd'; const IconText = ({ icon, text }: { icon: React.FC; text: string }) => ( <Space> {React.createElement(icon)} {text} </Space> ); const App: React.FC = () => ( <List itemLayout="vertical" size="large" pagination={{ onChange: (page) => console.log(page), pageSize: 3 }} dataSource={data} footer={<div><b>ant design</b> footer part</div>} renderItem={(item) => ( <List.Item key={item.title} actions={[ <IconText icon={StarOutlined} text="156" key="list-vertical-star-o" />, <IconText icon={LikeOutlined} text="156" key="list-vertical-like-o" />, <IconText icon={MessageOutlined} text="2" key="list-vertical-message" />, ]} extra={<img width={272} alt="logo" src="..." />} > <List.Item.Meta avatar={<Avatar src={item.avatar} />} title={<a href={item.href}>{item.title}</a>} description={item.description} /> {item.content} </List.Item> )} /> );actions 与 extra 的位置规则(关键行为差异):
- 水平布局(
horizontal):actions与extra均显示在最右侧; - 垂直布局(
vertical):actions移到内容底部,extra移到右侧; - 源码中该逻辑通过
ListContext下发itemLayout,见 Item.tsx 的三元分支:垂直布局且存在extra时,渲染-item-main(内容 + actions)与-item-extra两栏;否则按水平模式平铺渲染。
此外Item.tsx还实现了一个细节:当List.Item直接包含多个文本节点时(isItemContainsTextNodeAndNotSingular判断),会加上-item-no-flex类回退为块级布局,避免 flex 布局破坏纯文本内容。
List 完整 API 参数详解
以下参数表完整来自 index.en-US.md,并结合 index.tsx 的类型定义与默认值展开说明。通用属性(如className、style、id等)可参考 Common props。
List 主属性
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| bordered | 是否渲染列表边框 | boolean | false |
| dataSource | 列表数据源数组 | any[] | - |
| footer | 列表底部渲染器 | ReactNode | - |
| grid | 列表网格模式配置,如{gutter: 16, column: 4} | object | - |
| header | 列表顶部渲染器 | ReactNode | - |
| itemLayout | 列表布局方向 | horizontal|vertical | horizontal |
| loading | 数据加载中是否显示 loading 指示器 | boolean | SpinProps | false |
| loadMore | 显示"加载更多"内容 | ReactNode | - |
| locale | i18n 文案,包括空数据文案 | object | {emptyText: 'No Data'} |
| pagination | 分页配置,设为false可隐藏 | boolean | object | false |
| renderItem | 使用dataSource时自定义列表项渲染 | (item, index) => ReactNode | - |
| rowKey | 列表项唯一 key:可以是React.Key类型的字段名,或接收 item 返回React.Key的函数 | keyof T| (item: T) =>React.Key | "key" |
| size | 列表尺寸 | default|large|small | default |
| split | 是否渲染列表项之间的分隔线 | boolean | true |
源码级行为补充:
- rowKey 的三级兜底策略(index.tsx):优先使用函数
rowKey(item)→ 其次取item[rowKey]→ 再退化为item.key→ 最终兜底为list-item-${index}。其中"按 index 兜底"的模式在数据变化时可能引起 key 不稳定,生产环境建议始终为数据提供稳定唯一 key。 - loading 的双形态支持(index.tsx):传
boolean时内部自动包装为{ spinning: loading }后交给 Spin 组件;传对象时可直接透传 Spin 的tip、delay等属性。加载期间列表渲染一个minHeight: 53的占位层(源码中isLoading && <div style={{ minHeight: 53 }} />)。 - size 的全局联动:
size通过useSizehook 与 ConfigProvider 的全局size上下文合并(组件级配置优先),最终映射为lg/smCSS 类。 - 空数据文案优先级(index.tsx):
locale.emptyText> ConfigProvider 的renderEmpty('List')> 内置DefaultRenderEmpty。 pagination默认false(不分页),split默认true,bordered默认false——这些默认值在组件函数签名中直接可见(index.tsx)。
List.Item
| 属性 | 说明 | 类型 | 默认值 | 版本 |
|---|---|---|---|---|
| actions | 列表项操作区内容。itemLayout为vertical时显示在底部,否则显示在最右侧 | Array<ReactNode> | - | |
| classNames | 语义化结构 className | Record<'actions' \| 'extra', string> | - | 5.18.0 |
| extra | 列表项额外内容。itemLayout为vertical时显示在右侧,否则显示在最右侧 | ReactNode | - | |
| styles | 语义化 DOM 样式 | Record<'actions' \| 'extra', CSSProperties> | - | 5.18.0 |
classNames/styles(5.18.0 新增)允许对actions与extra两个语义模块做精确的类名与内联样式定制。源码 Item.tsx 显示,二者会与 ConfigProvider 中list.item.classNames / list.item.styles的全局配置深度合并,实现"全局主题 + 局部覆盖"的两层定制体系。
List.Item.Meta
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| avatar | 列表项头像 | ReactNode | - |
| description | 列表项描述 | ReactNode | - |
| title | 列表项标题 | ReactNode | - |
Meta 内部仅渲染有值的槽位(title或description都不存在时整个 content 区不渲染),避免产生空 DOM,见 Item.tsx。
分页方案一:内置 Pagination 配置
List 内置了对 Pagination 组件的集成:传入pagination对象即自动分页,dataSource会在渲染前按当前页切片。
<List pagination={{ position: 'bottom', align: 'center', pageSize: 3, onChange: (page) => console.log(page) }} dataSource={data} renderItem={(item) => <List.Item>{item}</List.Item>} />交互式示例(可切换位置与对齐方式)见 pagination.tsx。
pagination 专属参数
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| position | 指定Pagination的位置 | top|bottom|both | bottom |
| align | 指定Pagination的对齐方式 | start|center|end | end |
其余参数(pageSize、current、total、hideOnSinglePage、showSizeChanger等)全部透传给 Pagination 组件,详见 Pagination 文档。
源码级实现原理(index.tsx):
- 分页状态由组件内部
useState管理:paginationCurrent默认取pagination.defaultCurrent || 1,paginationSize默认取pagination.defaultPageSize || 10; - 通过
extendsObject合并三层配置:默认值{ current: 1, total: 0 }→ 基于dataSource.length计算出的total与当前状态 → 用户传入的pagination对象(用户配置优先级最高); - 存在越界保护:当
current > Math.ceil(total / pageSize)时自动收敛到最大有效页; - 数据切片逻辑:仅当
dataSource.length > (current - 1) * pageSize时执行splice,否则保持原数据(避免空页渲染); onChange/onShowSizeChange会被包装(triggerPaginationEvent),在更新内部状态的同时回调用户传入的事件处理函数;- 分页器渲染位置由
paginationPosition决定:top渲染在头部之前,bottom渲染在尾部之后,both则两处都渲染(index.tsx)。
对应的测试用例位于 pagination.test.tsx,覆盖了hideOnSinglePage(单页隐藏分页器)、pageSize切片、快照渲染等行为。
分页方案二:加载更多(Load More)
与内置分页不同,"加载更多"由loadMore属性接管列表尾部区域,配合loading属性与手动数据追加实现,参考 loadmore.tsx:
const [initLoading, setInitLoading] = useState(true); const [loading, setLoading] = useState(false); const [list, setList] = useState<DataType[]>([]); const onLoadMore = () => { setLoading(true); setList(data.concat([...new Array(count)].map(() => ({ loading: true, name: {}, picture: {} })))); fetch(fakeDataUrl) .then((res) => res.json()) .then((res) => { setData(data.concat(res.results)); setList(data.concat(res.results)); setLoading(false); }); }; const loadMore = !initLoading && !loading ? ( <div style={{ textAlign: 'center', marginTop: 12, height: 32, lineHeight: '32px' }}> <Button onClick={onLoadMore}>loading more</Button> </div> ) : null; return ( <List className="demo-loadmore-list" loading={initLoading} itemLayout="horizontal" loadMore={loadMore} dataSource={list} renderItem={(item) => ( <List.Item actions={[<a key="list-loadmore-edit">edit</a>, <a key="list-loadmore-more">more</a>]}> <Skeleton avatar title={false} loading={item.loading} active> <List.Item.Meta avatar={<Avatar src={item.picture.large} />} title={<a href="https://ant.design">{item.name?.last}</a>} description="Ant Design, a design language for background applications, is refined by Ant UED Team" /> <div>content</div> </Skeleton> </List.Item> )} /> );实现要点:
loadMore是一个受控的ReactNode,是否渲染"加载更多"按钮完全由业务状态(如initLoading、loading)决定;- 用
Skeleton的loading属性为新增但尚未返回数据的占位项展示骨架屏,形成平滑的加载体验; - 源码中
loadMore与pagination、footer共同参与isSomethingAfterLastItem()判断(index.tsx),用于决定最后一个列表项是否需要保留分隔线。
网格与响应式布局(Grid)
固定网格 Grid
通过grid={{ gutter: 16, column: 4 }}即可让 List 变为多列栅格,通常与 Card 搭配构成卡片墙,参考 grid.tsx:
<List grid={{ gutter: 16, column: 4 }} dataSource={data} renderItem={(item) => ( <List.Item> <Card title={item.title}>Card content</Card> </List.Item> )} />List grid props
| 属性 | 说明 | 类型 | 默认值 |
|---|---|---|---|
| column | 网格列数 | number | - |
| gutter | 网格间距 | number | 0 |
| xs | <576px时的列数 | number | - |
| sm | ≥576px时的列数 | number | - |
| md | ≥768px时的列数 | number | - |
| lg | ≥992px时的列数 | number | - |
| xl | ≥1200px时的列数 | number | - |
| xxl | ≥1600px时的列数 | number | - |
响应式网格 Responsive Grid
通过同时配置xs~xxl各断点列数实现断点自适应,参考 responsive.tsx:
<List grid={{ gutter: 16, xs: 1, sm: 2, md: 4, lg: 4, xl: 6, xxl: 3, }} dataSource={data} renderItem={(item) => ( <List.Item> <Card title={item.title}>Card content</Card> </List.Item> )} />响应式实现原理(index.tsx):
- 组件先检测
grid配置中是否含xs~xxl任一响应式字段(needResponsive),仅在需要时才调用useBreakpointhook 订阅窗口变化,避免无谓的监听开销; useBreakpoint基于 responsiveObserver 的媒体查询机制,返回各断点是否命中的映射;- 断点匹配按
xs < sm < md < lg < xl < xxl顺序取当前命中的最大断点(responsiveArray遍历),命中该断点的列数即作为columnCount; - 列宽通过
colStyle计算为${100 / columnCount}%(含maxWidth),配合 Grid 的Row+Col实现等宽分列——未配置任何响应式字段时useBreakpoint不启用,直接用grid.column; - 网格模式下
List.Item外层由Item.tsx渲染为Col(flex={1}),内部才渲染实际的 item 元素(Item.tsx)。
滚动加载与虚拟列表(高阶)
当数据量持续增长,继续使用"加载更多"按钮会带来大量 DOM 渲染开销,此时有两种进阶方案。
滚动加载(Infinite Scroll)
配合第三方库react-infinite-scroll-component实现触底自动加载,参考 infinite-load.tsx:
<div id="scrollableDiv" style={{ height: 400, overflow: 'auto', padding: '0 16px' }}> <InfiniteScroll dataLength={data.length} next={loadMoreData} hasMore={data.length < 50} loader={<Skeleton avatar paragraph={{ rows: 1 }} active />} endMessage={<Divider plain>It is all, nothing more 🤐</Divider>} scrollableTarget="scrollableDiv" > <List dataSource={data} renderItem={(item) => ( <List.Item key={item.email}> <List.Item.Meta avatar={<Avatar src={item.picture.large} />} title={<a href="https://ant.design">{item.name.last}</a>} description={item.email} /> <div>Content</div> </List.Item> )} /> </InfiniteScroll> </div>注意scrollableTarget需要指向承载滚动容器的id,数据量不大时该方案实现成本最低。
虚拟列表(Virtual List)
当列表项成百上千时,应使用rc-virtual-list只渲染可视区域内的节点,参考 virtual-list.tsx:
import VirtualList from 'rc-virtual-list'; const ContainerHeight = 400; <VirtualList data={data} height={ContainerHeight} itemHeight={47} itemKey="email" onScroll={onScroll} > {(item: UserItem) => ( <List.Item key={item.email}> <List.Item.Meta avatar={<Avatar src={item.picture.large} />} title={<a href="https://ant.design">{item.name.last}</a>} description={item.email} /> <div>Content</div> </List.Item> )} </VirtualList>height为虚拟滚动视口高度,itemHeight为预估的单项高度(用于计算滚动位置与渲染窗口),itemKey指定唯一键字段;- 滚动到底部前追加数据的判断基于
scrollHeight - scrollTop - ContainerHeight <= 1的容差比较; - 注意:虚拟列表模式下通常不再依赖 List 自身的
dataSource分页,数据累积逻辑由外部状态控制。
主题定制与 Design Token
List 的所有视觉细节均通过 cssinjs 的 Design Token 驱动,令牌定义见 style/index.ts 的ComponentToken接口,默认值由prepareComponentToken给出(style/index.ts)。
完整 Token 清单
| Token | 说明 | 默认值 |
|---|---|---|
| contentWidth | 内容宽度(用于小屏垂直布局换行) | 220 |
| itemPadding | 默认尺寸列表项内边距 | ${paddingContentVertical} 0 |
| itemPaddingSM | 小尺寸列表项内边距 | ${paddingContentVerticalSM} ${paddingContentHorizontal} |
| itemPaddingLG | 大尺寸列表项内边距 | ${paddingContentVerticalLG} ${paddingContentHorizontalLG} |
| headerBg | 头部区域背景色 | transparent |
| footerBg | 底部区域背景色 | transparent |
| emptyTextPadding | 空数据文案内边距 | padding |
| metaMarginBottom | Meta 下间距 | padding |
| avatarMarginRight | 头像右间距 | padding |
| titleMarginBottom | 标题下间距 | paddingSM |
| descriptionFontSize | 描述文字字号 | fontSize |
通过 ConfigProvider 覆盖 Token
参考 component-token.tsx,使用ConfigProvider的theme.components.List即可全局或局部覆盖:
<ConfigProvider theme={{ components: { List: { headerBg: 'pink', footerBg: 'pink', emptyTextPadding: 32, itemPadding: '26px', itemPaddingSM: '16px', itemPaddingLG: '36px', metaMarginBottom: 20, avatarMarginRight: 20, titleMarginBottom: 10, descriptionFontSize: 20, }, }, }} > {/* 所有 List 将应用上述主题 */} </ConfigProvider>从源码看样式生成的三个层次
style/index.ts 中genStyleHooks('List', ...)依次生成三类样式:
- genBaseStyle:基础样式,包括 flex 布局的
-item、-item-meta三栏结构、-item-action操作区、加载态-spin最小高度、空数据-empty-text等; - genBorderedStyle:
bordered模式的边框、圆角(borderRadiusLG)与不同尺寸下的内边距覆盖; - genResponsiveStyle:基于
screenMD/screenSM的媒体查询——中等屏以下操作区与 extra 调整外边距,小屏以下(max-width: screenSM)列表项flexWrap: 'wrap'、垂直布局wrap-reverse并让 extra 居中换行,实现移动端友好降级(style/index.ts)。
常见实践建议
- 始终提供稳定 key:
rowKey优先使用数据中的唯一 ID 字段或返回唯一值的函数,避免依赖源码中的 index 兜底,防止重排时出现渲染错位; - 按数据规模选择方案:几十条以内用内置
pagination;交互式追加用loadMore+ Skeleton;持续滚动且数据量大用 Infinite Scroll;上千条且需要流畅滚动用rc-virtual-list; - 网格与响应式结合:卡片场景优先配置
xs~xxl断点列数,让移动端自动降为单列; - 空状态定制:通过
locale.emptyText或 ConfigProvider 的renderEmpty提供更友好的空数据提示; - 主题统一治理:
headerBg、itemPadding等令牌既可在 ConfigProvider 全局配置,也可与List.Item的classNames/styles(5.18.0+)局部覆盖配合,形成"全局 + 局部"的分层样式治理。
测试保障
List 组件在仓库中拥有完整的测试覆盖(components/list/tests):index.test.tsx覆盖基础渲染与空状态、pagination.test.tsx覆盖分页切片与分页器显隐、loading.test.tsx覆盖加载态、Item.test.tsx覆盖列表项与 Meta 结构、image.test.ts与demo.test.ts验证示例代码的可运行性。这些测试用例是理解组件行为契约的最佳参考。
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/gh_mirrors/ant/ant-design
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考