Material UI Badge 组件全解析计数、圆点、可见性控制与源码实现【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Googles Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-uiMaterial UI 的Badge组件用于在其包裹的子元素右上角生成一个小标记以承载未读消息数、通知计数、在线状态等补充性状态信息。本文以 Badge 官方文档 为主线完整覆盖其全部用法指南与演示场景并深入 组件源码 与 单元测试讲解max截断、showZero隐藏、不可见过渡动画保留等行为的底层实现读完后可直接在项目中正确、可访问地落地 Badge 的各种形态。使用指南官方给出的三条设计准则官方文档在 badges.md 中明确了三条使用规范这三条准则同时决定了组件的语义和可访问性设计徽标只用于补充性状态Badge 适合展示短小的计数或紧凑状态例如收件箱按钮上的未读消息数。如果某个状态本身非常重要应直接展示在界面中而不是仅依赖徽标传达。徽标语义要并入宿主元素的可访问名称Badge 是依附于另一元素的视觉提示它的含义应包含在宿主元素的 accessible name 中。例如应使用aria-labelInbox, 4 unread messages而不是简单的aria-labelInbox。圆点徽标只用于简单状态variantdot不显示文字或数字只应在周边 UI 已能明确表达状态含义时使用例如Online或Unread。官方的 入门演示 就是这三条准则的集中体现import IconButton from mui/material/IconButton; import Badge from mui/material/Badge; import MailIcon from mui/icons-material/Mail; const maxVisibleNotifications 99; const unreadNotificationsCount 100; function getUnreadNotificationsLabel(count: number) { if (count 0) { return show no unread notifications; } if (count maxVisibleNotifications) { return show more than ${maxVisibleNotifications} unread notifications; } return show ${count} unread notification${count 1 ? : s}; } export default function BadgeIntro() { const label getUnreadNotificationsLabel(unreadNotificationsCount); return ( IconButton aria-label{label} Badge badgeContent{unreadNotificationsCount} colorsecondary max{maxVisibleNotifications} MailIcon / /Badge /IconButton ); }注意其中的关键细节徽标数量100超过了max99因此视觉显示为99但aria-label中生成的是 show more than 99 unread notifications即可访问名称反映真实语义而不是被截断后的视觉值aria-label挂在宿主IconButton上而非 Badge 上因为从源码看Badge 内部的标记元素默认带有aria-hiddentrue见下文 源码分析屏幕阅读器根本不会朗读它。列表项场景Badge 挂在 ListItemButton 上ListItemButton 演示 将同样模式应用到列表中未读计数被并入列表项的可访问名称ListItemButton selected aria-currentpage aria-label{Inbox, ${unreadMessagesCount} unread messages} ListItemIcon Badge badgeContent{unreadMessagesCount} colorprimary InboxIcon / /Badge /ListItemIcon ListItemText primaryInbox / /ListItemButton可见的计数4通过模板字符串直接包含在aria-label里使其在周边 UI 的语境中被朗读。Badge 内容badgeContent使用badgeContent属性在包裹元素上添加短计数或标签。最简示例来自 SimpleBadgeIconButton aria-labelshow 4 unread messages Badge badgeContent{4} colorprimary MailIcon / /Badge /IconButtonbadgeContent的类型是React.ReactNode见 类型定义因此不仅可以传数字也可以传字符串或任意节点如NEW。圆点徽标Dot badge使用variantdot得到一个不带计数的紧凑状态指示器。DotBadge 演示IconButton aria-labelshow new notifications Badge colorsecondary variantdot NotificationsIcon / /Badge /IconButton从源码看variantdot会走独立的样式变体徽标高度、最小宽度收窄为 8pxRADIUS_DOT 4height/minWidth均为其两倍内边距归零见 Badge.js 中的变体定义。同时displayValue会被置为undefined即dot 变体永远不渲染badgeContent——这一点被 测试用例 should not render badgeContent when variantdot 明确验证。可见性invisible 与 showZero通过invisible属性可以控制徽标的显隐。BadgeVisibility 演示 用一个 Switch 切换invisibleconst [invisible, setInvisible] React.useState(false); IconButton aria-label{ invisible ? open inbox : open inbox, ${unreadMessagesCount} unread messages } Badge colorsecondary badgeContent{unreadMessagesCount} invisible{invisible} MailIcon / /Badge /IconButton值得注意切换可见性时aria-label也随之变化——徽标隐藏后可访问名称里不再声称有 4 条未读消息保持语音播报与视觉一致。零值自动隐藏当badgeContent为 0 时徽标默认自动隐藏若0对界面有意义可以用showZero属性覆盖此行为。ShowZeroBadge 演示 并排对比了两种写法IconButton aria-labelshow no unread messages Badge colorsecondary badgeContent{0} MailIcon / /Badge /IconButton IconButton aria-labelshow 0 unread messages Badge colorsecondary badgeContent{0} showZero MailIcon / /Badge /IconButton第一个徽标不可见第二个显示 0。该逻辑的源码实现位于 useBadge.tslet invisible invisibleProp; if (invisibleProp false badgeContentProp 0 !showZero) { invisible true; }也就是说零值隐藏与invisible是或关系只要badgeContent 0且未显式开启showZero徽标就进入不可见状态。测试套件 对这一组合做了穷举验证包括badgeContent{0}时默认带invisible类、badgeContent{0} showZero时不带invisible类两种断言。最大值max使用max属性为大数字封顶。BadgeMax 演示IconButton aria-labelshow 99 unread messages Badge colorsecondary badgeContent{99} MailIcon / /Badge /IconButton IconButton aria-labelshow more than 99 unread messages Badge colorsecondary badgeContent{100} MailIcon / /Badge /IconButton IconButton aria-labelshow more than 999 unread messages Badge colorsecondary badgeContent{1000} max{999} MailIcon / /Badge /IconButton三个徽标分别显示99、99、999。截断逻辑在 useBadge.ts 中只有一行核心判断const displayValue: React.ReactNode badgeContent Number(badgeContent) max ? ${max} : badgeContent;测试用例 精确界定了边界行为默认max为 99badgeContent{100}显示99badgeContent{1000} max{999}显示999等于 max 时不截断badgeContent{1000} max{1000}显示1000小于 max 时原样显示badgeContent{50} max{1000}显示50。自定义颜色color使用color属性把主题调色板颜色应用到徽标上。ColorBadge 演示 展示了三种典型语义配色Badge badgeContent{8} colorprimary.../Badge // 普通信息 Badge badgeContent{2} colorsuccess.../Badge // 已确认 Badge badgeContent{1} colorerror.../Badge // 严重告警color的合法内置取值为default、primary、secondary、error、info、success、warning见 PropTypes 定义默认值为default。从源码结构看徽标背景色的样式变体是动态生成的Badge.js 遍历theme.palette的所有调色板项过滤掉contrastText为每个颜色生成backgroundColor: palette[color].main与color: palette[color].contrastText。因此只要你在主题中新增了自定义颜色例如brandcolorbrand会自动获得对应样式无需手写 CSS——这一点也被OverridableStringUnion类型Badge.d.ts在类型层面支持。对齐anchorOrigin使用anchorOrigin属性把徽标移动到被包裹元素的任意一角。BadgeAlignment 演示 是一个可交互示例通过两组单选按钮切换verticaltop/bottom与horizontalright/left并实时渲染对应的 JSXIconButton aria-labelshow 12 unread messages Badge badgeContent{12} colorsecondary anchorOrigin{{ vertical: bottom, horizontal: left, }} MailIcon / /Badge /IconButtonanchorOrigin的默认值为{ vertical: top, horizontal: right }右上角。从源码看两个方向都可以只传一半getAnchorOrigin 会对缺失的维度单独回退默认值——vertical缺省为tophorizontal缺省为right。例如anchorOrigin{{ vertical: bottom }}等价于bottomright组合测试 分别断言了这两种单维写法生成的类名anchorOriginBottomRightRectangular、anchorOriginTopLeftRectangular。定位本身是通过 CSS 自定义变量注入的见 Badge.jsconst offset overlap circular ? 14% : 0; const top vertical top ? offset : auto; const bottom vertical bottom ? offset : auto; const right horizontal right ? offset : auto; const left horizontal left ? offset : auto; // ... style: { --Badge-translate: ${horizontal right ? 50% : -50%}, ${vertical top ? -50% : 50%}, --Badge-inset: ${top} ${right} ${bottom} ${left}, --Badge-origin: ${horizontal right ? 100% : 0%} ${vertical top ? 0% : 100%}, }徽标样式侧Badge.js消费这些变量inset: var(--Badge-inset)决定四边贴边距离translate(var(--Badge-translate))让徽标始终骑跨在元素边缘水平方向平移 50%、垂直方向平移 ±50%transform-origin则指向被包裹元素的对应角保证缩放动画从正确的角点发生。重叠形状overlap当被包裹元素是圆形如 Avatar时使用overlapcircular让徽标更贴近弧线。BadgeOverlap 演示 对比了矩形与圆形两种宿主Badge colorsecondary badgeContent{1} Rectangle / /Badge Badge colorsecondary overlapcircular badgeContent{1} Circle / /Badgeoverlap的默认值为rectangular。从源码看它只影响一个值定位 offset 从0矩形徽标紧贴直角边缘变为14%圆形徽标内收以匹配圆弧见 Badge.js L232。自定义样式官方文档指出可以通过主题样式覆盖theme style overrides、sx属性或styled()三种途径自定义徽标并建议参考项目的自定义化指南。CustomizedBadges 演示 展示了用styled()制作在线/离线状态点的完整实现这是列表头像状态指示的经典写法const ContactStatusBadge styled(Badge, { shouldForwardProp: (prop) prop ! status, }){ status: ContactStatus }(({ theme, status }) { const themePalette (theme.vars ?? theme).palette; const offlineBadgeColor theme.vars ? theme.vars.palette.Avatar.defaultBg : theme.palette.grey[400]; return { .MuiBadge-badge: { height: 10, minWidth: 10, border: 1px solid ${themePalette.grey[300]}, boxShadow: 0 0 0 2px ${themePalette.background.paper}, ...(status offline { backgroundColor: offlineBadgeColor, ...(theme.vars ? {} : theme.applyStyles(dark, { backgroundColor: theme.palette.grey[600], })), }), }, }; });使用时的要点ContactStatusBadge status{contact.status} color{contact.status online ? success : default} variantdot overlapcircular anchorOrigin{{ vertical: bottom, horizontal: right }} Avatar{contact.initials}/Avatar /ContactStatusBadgeshouldForwardProp: (prop) prop ! status用于阻止自定义的status属性泄漏到 DOMborder: 1px solid grey[300]boxShadow: 0 0 0 2px background.paper组合出了圆点与头像之间的描边间隔效果而不是把圆点缩小离线状态色通过theme.vars分支兼容了 CSS 变量主题palette.Avatar.defaultBg与静态主题grey[400]两套取值路径。此外Badge 还暴露了slots/slotProps两个插槽机制可分别替换root与badge两个内部节点的组件类型和属性测试 验证了自定义槽组件的渲染以及通过slotProps{{ badge: { aria-hidden: false, aria-label: 10 notifications } }}覆盖徽标节点默认可访问性属性的能力。源码深入组件结构与隐藏机制双节点结构与 aria-hidden从 Badge.js 的渲染输出看Badge 是一个外壳 标记的双节点结构RootSlot {...rootProps} {children} BadgeSlot {...badgeProps}{displayValue}/BadgeSlot /RootSlotroot 节点BadgeRootposition: relative的行内 flexspan为绝对定位的徽标提供坐标参考同时透传component、className及所有其他宿主属性badge 节点BadgeBadge绝对定位的标记本身硬编码aria-hidden: trueBadge.js L243并渲染displayValue。测试 hides the visual badge from assistive technologies by default 直接断言了该节点带有aria-hiddentrue属性。这正是官方使用指南第二条把徽标语义并入宿主元素 accessible name的结构性原因徽标本体对辅助技术不可见若宿主元素不携带计数信息屏幕阅读器用户将完全丢失该状态。invisible 过渡动画的状态保留机制invisible徽标并不是从 DOM 中移除而是通过transform: scale(0)收缩消失Badge.js L124-L133配合主题过渡曲线实现缩放动画。这里有一个容易被忽略的细节徽标进入不可见状态的那一帧color、variant、anchorOrigin、overlap、badgeContent可能同时变化例如未读数归零时从standard切回dot如果直接用新值渲染消失动画就会变形——从一个新位置的圆形闪到旧位置。源码的解决方案在 Badge.js用usePreviousProps缓存上一帧的这五个属性当徽标处于不可见状态时沿用上一帧的值渲染const invisible invisibleFromHook || (badgeContent null variantProp ! dot); // ... const { color colorProp, overlap overlapProp, anchorOrigin: anchorOriginPropProp, variant variantProp, } invisible ? prevProps : props;测试 retains anchorOrigin, content, color, max, overlap and variant when invisible is true for consistent disappearing transition 专门验证了这一点从secondary色、dot变体切换为 0 计数、primary色、standard变体、bottom-left锚点时徽标仍然保留colorSecondary、dot、anchorOriginTopRightRectangular类名即以旧形态平滑缩小。另外注意 invisible 的触发条件是invisibleFromHook || (badgeContent null variantProp ! dot)未传badgeContent的 standard 徽标默认不可见而variantdot即使无内容也保持可见对应测试 L134-L144 中badgeContent{undefined} variantdot不带invisible类的断言。类名体系badgeClasses.ts 通过generateUtilityClasses(MuiBadge, [...])生成了完整的工具类名集前缀为MuiBadge-主要包括类别类名示例触发条件结构root、badge始终存在变体standard、dotvariant状态invisible不可见颜色colorPrimary、colorSecondary、colorError、colorInfo、colorSuccess、colorWarningcolor ! default锚点anchorOriginTopRight、anchorOriginBottomLeft等四角组合anchorOrigin重叠overlapRectangular、overlapCircularoverlap锚点 × 重叠anchorOriginTopRightCircular等八个组合联合定位这些类名可用于classes属性覆盖、styled选择器如演示中的 .MuiBadge-badge或主题components.MuiBadge.styleOverrides。需要留意源码中的一处标注badgeClasses.ts L81 带有TODO: v6 remove the overlap value from these class keys即anchorOriginTopRightCircular这类锚点 重叠的组合类名属于历史遗留未来大版本中组合定位样式将改由overlapCircular与锚点类名组合承担——当前版本使用它们没有问题但升级时需留意。Props 速查表综合 Badge.d.ts 与 PropTypes属性类型默认值说明anchorOrigin{ vertical?: top \| bottom; horizontal?: left \| right }{ vertical: top, horizontal: right }徽标锚点缺省维度单独回退badgeContentReact.ReactNode—徽标内显示的内容childrenReact.ReactNode—徽标所依附的宿主元素colordefault \| primary \| secondary \| error \| info \| success \| warning \| stringdefault主题调色板颜色支持自定义主题色invisiblebooleanfalse强制隐藏徽标maxnumber99显示上限超过时渲染maxoverlaprectangular \| circularrectangular宿主形状圆形时徽标内收 14%showZerobooleanfalse控制badgeContent为 0 时是否隐藏variantstandard \| dotstandard标准计数或圆点componentReact.ElementTypespan替换根节点元素slots/slotProps见 BadgeSlots{}替换/配置root与badge插槽sxSxPropsTheme—系统属性与内联样式classesPartialBadgeClasses—类名覆盖小结与延伸阅读Badge的行为可以归纳为四条规则链badgeContent决定显示什么max决定显示上限Number(badgeContent) max时渲染${max}showZero/invisible/空内容三者决定显隐anchorOrigin×overlap通过 CSS 变量决定贴边位置。所有视觉变化都以scale过渡呈现并在隐藏瞬间保留上一帧的形态参数保证了消失动画的一致性。如需继续深入可从以下仓库文件入手组件实现packages/mui-material/src/Badge/Badge.js逻辑 Hookpackages/mui-material/src/Badge/useBadge.ts类型定义packages/mui-material/src/Badge/Badge.d.ts测试packages/mui-material/src/Badge/Badge.test.js全部官方演示源码docs/data/material/components/badges/【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Googles Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考