ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Material UI List 组件完全解析:从基础列表到虚拟化长列表的实现与定制

2026/9/7 10:00:41 拓冰建站 浏览量
Material UI List 组件完全解析:从基础列表到虚拟化长列表的实现与定制 Material UI List 组件完全解析从基础列表到虚拟化长列表的实现与定制【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Googles Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui本篇基于 Material UI 仓库中 List 组件的官方文档系统讲解 React List 组件家族的构成与用法覆盖基础列表、嵌套列表、交互控件Checkbox/Switch、粘性子标题、alignItems对齐、inset/disableGutters布局控制、虚拟化长列表等全部核心场景并结合mui/material源码剖析dense上下文传播、secondaryAction定位、选中态主题色计算等底层实现帮助你既会用组件也看得懂组件背后的样式机制。List 组件家族一整套组合式组件按照 Material Design 规范列表是连续的、垂直的文本或图像索引由包含主要操作与次要操作以图标和文本呈现的条目组成。Material UI 并没有提供一个大而全的 List 组件而是实现为一组可自由组合的关联组件组件职责默认渲染元素List列表项的容器ulListItem通用列表项liListItemButton列表项内的可点击/可操作元素基于ButtonBasedivListItemIcon列表项内的图标包装spanListItemAvatar列表项内的头像包装spanListItemText列表项内文本内容容器支持primary/secondary两行spanDivider列表项之间的分隔线hrListSubheader嵌套列表/分组的标签li/div基础用法只需引入核心组件import List from mui/material/List; import ListItem from mui/material/ListItem;基础列表BasicList仓库中的示例 BasicList.tsx 演示了最典型的邮箱文件夹结构——注意外层用nav包裹并添加aria-labelListItem使用disablePadding把内边距交给ListItemButton控制import Box from mui/material/Box; import List from mui/material/List; import ListItem from mui/material/ListItem; import ListItemButton from mui/material/ListItemButton; import ListItemIcon from mui/material/ListItemIcon; import ListItemText from mui/material/ListItemText; import Divider from mui/material/Divider; import InboxIcon from mui/icons-material/Inbox; import DraftsIcon from mui/icons-material/Drafts; export default function BasicList() { return ( Box sx{{ width: 100%, maxWidth: 360, bgcolor: background.paper }} nav aria-labelmain mailbox folders List ListItem disablePadding ListItemButton ListItemIcon InboxIcon / /ListItemIcon ListItemText primaryInbox / /ListItemButton /ListItem {/* ... 其余列表项 */} /List /nav Divider / nav aria-labelsecondary mailbox folders List ListItem disablePadding ListItemButton componenta href#simple-list ListItemText primarySpam / /ListItemButton /ListItem /List /nav /Box ); }其中渲染链接的方式值得单独强调给ListItemButton传入componenta与href即可将其变为锚点。由于ListItemButton底层是ButtonBasecomponent支持传入任意 HTML 标签或组件。若要与路由库集成React Router 的Link、Next.js 的Link等仓库的路由集成文档提供了ListRouter.js示例思路同样是component{RouterLink}。List 容器源码解析dense是如何传递给子项的阅读 List.js 可以发现几个关键实现细节1. 默认渲染ul并清理默认样式。根节点由styled(ul)生成基础样式为listStyle: none, margin: 0, padding: 0, position: relative,componentprop 默认为ul可覆盖为nav、div等未禁用 padding 时容器上下各留 8px。2.dense通过 React Context 向后代传播。源码中const context React.useMemo(() ({ dense }), [dense]); // ... ListContext.Provider value{context}ListContext 使得List dense无需在每个ListItem上重复声明。在 ListItem.js 中可以看到消费逻辑const context React.useContext(ListContext); const childContext React.useMemo( () ({ dense: dense || context.dense || false, alignItems, disableGutters, }), [alignItems, context.dense, dense, disableGutters], );也就是说子项dense的取值是自身声明 || 父级上下文并且会继续向下层 Provider 传递。ListItem的默认垂直 padding 为 8pxdense时缩减为 4px见ListItem.js中ListItemRoot的 variants 定义。3. 提供语义化的 CSS 类名。listClasses.ts 定义了MuiList-root、MuiList-padding、MuiList-dense、MuiList-subheader四个类名配合useUtilityClasses按disablePadding、dense、subheader状态动态组合便于外部通过classesprop 或 CSS 选择器做精确覆盖。4.subheaderprop 的特殊处理。当传入subheader时容器会移除顶部 padding 并设置isolation: isolate源码注释说明这是为了防止与 iOS 覆盖式滚动条重叠子标题由此获得贴顶的视觉位置。嵌套列表ListSubheaderCollapse嵌套列表如收件箱 → 星标两级结构的完整示例见 NestedList.tsx其技术要点List的componentnav并通过aria-labelledby关联子标题 id保证可访问性子标题通过List的subheaderprop 注入而非作为普通 children对应上文源码中subheader的专门处理逻辑展开/收起由Collapse组件的inprop 驱动timeoutauto配合unmountOnExit让动画时长与高度自适应子级List设置componentdiv disablePadding子项用sx{{ pl: 4 }}4 × spacing 32px产生缩进。List sx{{ width: 100%, maxWidth: 360, bgcolor: background.paper }} componentnav aria-labelledbynested-list-subheader subheader{ ListSubheader componentdiv idnested-list-subheader Nested List Items /ListSubheader } {/* ... 一级列表项 */} Collapse in{open} timeoutauto unmountOnExit List componentdiv disablePadding ListItemButton sx{{ pl: 4 }} ListItemIcon StarBorder / /ListItemIcon ListItemText primaryStarred / /ListItemButton /List /Collapse /List列表控件Checkbox 与 Switch 的主/次操作分工Material Design 对列表内控件的位置有明确约定Material UI 提供了对应示例Checkbox 作为主要操作CheckboxList复选框既是该列表项的主要操作也是状态指示器通常放在ListItemIcon的位置Checkbox 作为次要操作CheckboxListSecondary复选框是独立的、与主操作如整行点击分离的目标Switch 作为次要操作SwitchListSecondary开关放在列表项右端。次要操作的实现依托ListItem的secondaryActionprop。源码中这一点值得注意ListItemRoot的 variants 里只要检测到secondaryAction存在就为该行以及内部的ListItemButton预留paddingRight: 48因为ListItemSecondaryAction是绝对定位在行右端的必须留出碰撞空间{ props: ({ ownerState }) !ownerState.disablePadding !!ownerState.secondaryAction, style: { // Add some space to avoid collision as ListItemSecondaryAction // is absolutely positioned. paddingRight: 48, }, },多行文本对齐alignItemsflex-startListItem默认alignItems为center垂直居中。当列表项显示三行及以上内容时头像/图标会跟着下沉不符合 Material Design 规范。此时应设置alignItemsflex-start让头像对齐顶部示例见 AlignItemsList.tsxListItem alignItemsflex-start ListItemAvatar Avatar src... / /ListItemAvatar ListItemText primaryFirst secondarySecond / /ListItem从源码结构看这个 prop 同时作用于两层ListItem自身通过 variants 设置align-items: flex-start并通过childContext把alignItems传给ListItemButton其useUtilityClasses中会生成MuiListItemButton-alignItemsFlexStart类名保证图标容器与文本容器一并对齐而不是只对齐了外层 flex 容器。粘性子标题纯 CSS sticky 实现滚动时子标题保持固定在视口顶部、直到被下一个子标题顶出屏幕——这一效果完全依赖 CSS sticky 定位示例见 PinnedSubheaderList.tsxList sx{{ width: 100%, maxWidth: 360, bgcolor: background.paper, position: relative, overflow: auto, maxHeight: 300, ul: { padding: 0 }, }} subheader{li /} {[0, 1, 2, 3, 4].map((sectionId) ( li key{section-${sectionId}} ul ListSubheader{Im sticky ${sectionId}}/ListSubheader {[0, 1, 2].map((item) ( ListItem key{item-${sectionId}-${item}} ListItemText primary{Item ${item}} / /ListItem ))} /ul /li ))} /List几个易被忽略的细节overflow必须在容器上而不是body上——sticky 元素相对最近的滚动祖先定位因此示例手动给List设置了position: relative、overflow: auto、maxHeight: 300来制造滚动区域subheader{li /}List的subheaderprop 会渲染在 children 之前这里放一个空li是为了让内部按每节一个liul分组的 HTML 结构保持合法嵌套ListSubheader自身使用position: sticky滚动时逐节接力吸附。inset与disableGutters两种布局微调Inset 列表项insetprop 让没有前置图标或头像的列表项与有图标的列表项在文本起始位置上正确对齐示例 InsetList.tsx。它通常用在同一列表内有图标项 无图标项 展开的无图标子项混排的场景如音乐列表模式。无槽列表Gutterless当列表被渲染在自身定义了左右内边距的容器如Drawer、Paper内部时ListItem默认的 16px 左右 paddinggutters会造成双层缩进。此时对ListItem或ListItemButton设置disableGutters即可移除左右 padding。从源码看disableGutters同样经由ListContext参与ListItem的样式变体判断与disablePadding移除上下 padding是正交的两个开关prop影响源码默认 paddingdisablePaddingList/ListItem移除上下垂直 paddingList/ListItem 各 8pxdense 时 4pxdisableGuttersListItem/ListItemButton移除左右水平 padding16px选中态selected背后的主题色计算选中列表项的示例见 SelectedListItem.tsx核心就是ListItemButton的selectedprop。ListItemButton.js 中可以看到选中态并非写死的颜色而是由主题动态合成[.${listItemButtonClasses.selected}]: { backgroundColor: theme.alpha( (theme.vars || theme).palette.primary.main, (theme.vars || theme).palette.action.selectedOpacity, ), }即主题主色 ×action.selectedOpacity透明度。悬停选中项时颜色为selectedOpacity hoverOpacity的叠加在触摸设备media (hover: none)上则回退为纯选中色。此外源码还处理了可访问性细节当启用theme.focusVisible时聚焦环采用 inset 形式源码注释解释滚动容器会裁掉外扩的 focus ring禁用态则应用action.disabledOpacity透明度。虚拟化列表List遇上 react-window长列表成百上千条数据直接渲染会产生明显的性能问题。文档推荐配合 react-window 使用示例见 VirtualizedList.tsx——渲染 200 行实际只会挂载可视区域内的 DOMimport ListItem from mui/material/ListItem; import ListItemButton from mui/material/ListItemButton; import ListItemText from mui/material/ListItemText; import { List, RowComponentProps } from react-window; function renderRow(props: RowComponentProps) { const { index, style } props; return ( ListItem style{style} key{index} componentdiv disablePadding ListItemButton ListItemText primary{Item ${index 1}} / /ListItemButton /ListItem ); } export default function VirtualizedList() { return ( Box sx{{ width: 100%, height: 400, maxWidth: 360, bgcolor: background.paper }} List rowHeight{46} rowCount{200} style{{ height: 400, width: 360 }} rowProps{{}} overscanCount{5} rowComponent{renderRow} / /Box ); }关键点这里的List来自react-window而非mui/materialrowComponent中返回的是 Material UI 的ListItemcomponentdiv因为虚拟化行不是li。react-window 的List要求每行高度固定所以传入了rowHeight{46}overscanCount{5}表示在可视区域上下各多渲染 5 行以消除滚动白边。文档同时建议react-window 无法满足需求时可考虑 react-virtuoso 等替代方案。样式定制类名、overrides 与 sxList 家族支持三套递进的定制手段sxprop组件级的快速样式注入如示例中的bgcolor: background.paperclassesprop 语义类名如MuiList-root、MuiList-dense、MuiListItem-gutters、MuiListItemButton-selected各组件的类名清单分别在 listClasses.ts、packages/mui-material/src/ListItem/listItemClasses.ts、packages/mui-material/src/ListItemButton/listItemButtonClasses.ts中集中定义主题级 overrides通过theme.components.MuiList.styleOverrides等做全局覆盖每个组件源码中的overridesResolver决定了主题样式按状态dense、divider、selected…合并到哪个 slot。仓库提供了完整的定制化示例 CustomizedList.tsx主题覆盖机制的详细说明见定制文档。Props 速查表List源码prop默认值说明componentul根节点渲染的 HTML 元素或组件densefalse紧凑垂直 padding通过 Context 传递给所有后代列表项disablePaddingfalse移除容器上下 8px paddingsubheader—子标题内容通常是ListSubheader渲染在 children 之前ListItem源码prop默认值说明componentli根节点渲染元素alignItemscenter可选flex-start多行时让头像顶部对齐densefalse优先继承父级List的 dense 上下文disableGuttersfalse移除左右 16px paddingdisablePaddingfalse移除上下 8px paddingdividerfalse底部添加 1px 分隔线取theme.palette.divider颜色secondaryAction—行尾绝对定位的次要操作图标按钮、Switch 等自动预留 48px 右侧空间slots/slotProps{}可替换root、secondaryAction两个 slot 的组件及其 propsListItemButton源码基于ButtonBase除上述布局 props 外还支持selected选中底色 主色 ×selectedOpacity、disabled应用action.disabledOpacity、component传a变锚点、传路由 Link 组件做导航等 ButtonBase 能力。小结Material UI 的 List 是一个典型的组合式组件设计List只负责容器、dense上下文传播与类名管理ListItem负责行布局与 gutter/padding/divider/对齐等状态变体ListItemButton复用ButtonBase提供完整的可点击、选中、禁用与焦点行为。理解 List.js 的 Context 机制、ListItem.js 中基于ownerState的 variants 样式表以及 ListItemButton.js 的主题色合成逻辑就掌握了从照抄示例到按主题规范深度定制所需的全部依据。【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Googles Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考