Material UI 图标系统详解:@mui/icons-material、SvgIcon 与 Icon 三种方案的用法与源码实现
2026/9/5 18:59:08 网站建设 项目流程

Material UI 图标系统详解:@mui/icons-material、SvgIcon 与 Icon 三种方案的用法与源码实现

【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Google's Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui

Material UI 的图标能力由三条并行的技术路径组成:以 React 组件形式导出的 Material SVG 图标(@mui/icons-material包)、面向自定义 SVG 的SvgIcon包装组件、以及面向任意支持连字(ligature)图标字体的Icon组件。本文基于官方文档 icons.md 展开,并结合 SvgIcon.js、Icon.js 与 createSvgIcon.js 的源码实现,说明每种方案的安装、导入、定制与无障碍处理方式,以及它们在组件内部是如何渲染的。读完后你将能独立完成 Material 图标导入、自定义 SVG 图标封装、图标字体接入,并理解fontSizecolorviewBox等属性在底层样式中的实际行为。

三种图标支持方式总览

Material UI 官方提供图标的三种方式:

  1. 以 React 组件(SVG 图标)形式导出的 Material Icons;
  2. SvgIcon组件——自定义 SVG 图标的 React 包装器;
  3. Icon组件——自定义字体图标的 React 包装器。

三者对应的源码位置分别为 packages/mui-icons-material 包、SvgIcon.js 与 Icon.js。其中@mui/icons-material中每个图标组件本身就是用createSvgIcon工具函数包装出来的SvgIcon,也就是说三条路径最终收敛到同一套底层渲染逻辑上。

Material SVG 图标(@mui/icons-material)

Google 提供了超过 2100 个官方 Material 图标,每个图标有 5 种"主题"变体。@mui/icons-material包为每个 SVG 图标导出对应的 React 组件,可从源码目录 material-icons 看到对应的 SVG 源文件。

安装

使用包管理器安装并保存到package.json依赖:

npm install @mui/icons-material
pnpm add @mui/icons-material
yarn add @mui/icons-material

这些组件内部使用 Material UI 的SvgIcon来渲染每个图标的 SVG path,因此对@mui/material存在 peer 依赖。这一点可以从 mui-icons-material 的 package.json 中直接确认:peerDependencies声明了@mui/materialreact^17 || ^18 || ^19)与可选的@types/react。如果项目中尚未安装 Material UI,需先按官方安装指南完成@mui/material的接入。

导入方式

有两种导入方式:

// Option 1:按文件路径单独导入(对包体积最安全) import AccessAlarmIcon from '@mui/icons-material/AccessAlarm'; import ThreeDRotation from '@mui/icons-material/ThreeDRotation';
// Option 2:从包入口具名导入 import { AccessAlarm, ThreeDRotation } from '@mui/icons-material';

对 bundle 体积而言 Option 1 最安全(打包器只打进实际引用的文件),Option 2 则更适合使用动态import()做代码分割的场景。

从 package.json 的exports字段可以看到其模块解析规则:

"exports": { ".": "./src/index.js", "./*": "./src/*.js" }

@mui/icons-material/DeleteOutlined会直接映射到src/DeleteOutlined.js单文件入口,这正是 Option 1 能被打包器精确树摇(tree-shake)的基础;同时声明"sideEffects": false进一步支持无用代码剔除。

五种图标主题

每个 Material 图标都有主题:Filled(默认)、Outlined、Rounded、Two-tone、Sharp。非默认主题通过图标名加后缀导入,以Delete为例:

  • Filled(默认):@mui/icons-material/Delete
  • Outlined:@mui/icons-material/DeleteOutlined
  • Rounded:@mui/icons-material/DeleteRounded
  • Twotone:@mui/icons-material/DeleteTwoTone
  • Sharp:@mui/icons-material/DeleteSharp

官方文档配套示例 SvgMaterialIcons.tsx 完整演示了DeleteDeleteForever在五种主题下的对照效果:

import DeleteIcon from '@mui/icons-material/Delete'; import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; import DeleteRoundedIcon from '@mui/icons-material/DeleteRounded'; import DeleteTwoToneIcon from '@mui/icons-material/DeleteTwoTone'; import DeleteSharpIcon from '@mui/icons-material/DeleteSharp';

命名规则注意:Material Design 指南使用 "snake_case" 命名图标(如delete_foreveradd_a_photo),而@mui/icons-material以 "PascalCase" 导出(如DeleteForeverAddAPhoto)。该规则有三个例外:3d_rotation导出为ThreeDRotation4k导出为FourK360导出为ThreeSixty。这三个例外组件在示例 SvgMaterialIcons.tsx 中作为 "Edge-cases" 单独展示。

从源码结构看,图标组件由构建脚本 builder.mjs 扫描material-icons目录下的 SVG 文件、套用模板 templateSvgIcon.js 批量生成,并通过 renameFilters/material-design-icons.mjs 处理上述命名映射。

SvgIcon:自定义 SVG 的包装组件

当需要 Material Icons 中不存在的自定义 SVG 图标时,可以使用SvgIcon包装器。该组件扩展原生<svg>元素,具备以下特性:

  • 内置无障碍支持;
  • SVG 元素应按 24x24px 视口绘制,这样图标可以直接使用,也可以作为子元素嵌入其他使用图标的 Material UI 组件。可通过viewBox属性定制,inheritViewBox则用于从原始图像继承viewBox值;
  • 默认继承当前颜色,可选地通过color属性应用主题色;
  • 支持直接以<svg>元素作为 children,可将 SVG 原样粘贴进SvgIcon

例如直接粘贴一段<svg>内容(官方示例见 SvgIconChildren.tsx):

<SvgIcon> <svg fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12a7.5 7.5 0 0015 0m-15 0a7.5 7.5 0 1115 0m-15 0H3m16.5 0H21m-1.5 0H12..." /> </svg> </SvgIcon>

源码实现:属性默认值与样式变体

查看 SvgIcon.js 的实现,可以确认文档描述的行为:

const { children, className, color = 'inherit', component = 'svg', fontSize = 'medium', htmlColor, inheritViewBox = false, titleAccess, viewBox = '0 0 24 24', ...other } = props;
  • viewBox默认'0 0 24 24',与"24x24px 视口"的文档描述一致;当inheritViewBoxtrue时不向下传viewBox(见 L127-L129);
  • color默认'inherit'fontSize默认'medium'
  • 当检测到 children 是<svg>元素时(hasSvgAsChild),组件会透传子 svg 自身的属性到根节点,并且不再强制fill: 'currentColor',因此stroke="currentColor"这类描边型图标(如 Heroicons)也能正确着色,相关逻辑见 L49-L57。

fontSize的四个取值为样式变体,实际字号由主题换算(见 L58-L73):

fontSize 取值实际字号
'inherit'继承父元素字号
'small'theme.typography.pxToRem(20)(约 1.25rem)
'medium'(默认)theme.typography.pxToRem(24)(1.5rem)
'large'theme.typography.pxToRem(35)(2.1875rem)

color的取值包括inheritactiondisabled以及所有含main的主色板键(primarysecondaryerrorinfosuccesswarning等)。变体映射见 L74-L93:action映射到palette.action.activedisabled映射到palette.action.disabled。此外htmlColor属性会把颜色直接写到<svg>color属性上,shapeRendering属性用于排查图标模糊问题。

颜色与尺寸

官方演示 SvgIconsColor.tsx 与 SvgIconsSize.tsx 分别展示了:

  • 颜色:color="primary"/"secondary"/"error"等主题色,或不传color时继承上下文文字颜色;
  • 尺寸:fontSize'inherit''small''medium''large'四档,由于根节点宽高均为1em,图标会随字号等比缩放。

component 属性:搭配 .svg 文件使用

即使图标以.svg文件形式存放,也可以用SvgIcon包装。通过 svgr 的 loader 把 SVG 文件导入为 React 组件,例如配合 webpack:

// webpack.config.js { test: /\.svg$/, use: ['@svgr/webpack'], } // --- import StarIcon from './star.svg'; <SvgIcon component={StarIcon} inheritViewBox />

也可以配合 "url-loader" 或 "file-loader" 使用,这也是 Create React App 的默认做法:

// webpack.config.js { test: /\.svg$/, use: ['@svgr/webpack', 'url-loader'], } // --- import { ReactComponent as StarIcon } from './star.svg'; <SvgIcon component={StarIcon} inheritViewBox />

两种方式的共同点是inheritViewBox——让根节点继承StarIcon自带的viewBox,而不是套上默认的0 0 24 24

createSvgIcon 工具函数

createSvgIcon是生成 Material Icons 组件所用的工具函数,也可用于包装<svg>元素或作为SvgIconchildren 传入的 SVG path。它可以从@mui/material/utils导入(见官方示例 CreateSvgIcon.tsx):

import { createSvgIcon } from '@mui/material/utils'; const HomeIcon = createSvgIcon( <path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z" />, 'Home', ); // 或者用完整自定义 SVG const PlusIcon = createSvgIcon( <svg fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor" > <path strokeLinecap="round" strokeLinejoin="round" d="M12 4.5v15m7.5-7.5h-15" /> </svg>, 'Plus', );

生成的组件继承SvgIcon的全部能力,可继续传colorfontSize等属性:

<HomeIcon color="primary" /> <PlusIcon color="secondary" />

从 createSvgIcon.js 的源码看,其实现非常简洁:内部组件渲染<SvgIcon>{path}</SvgIcon>,非生产环境会附加data-testid="${displayName}Icon"并设置Component.displayName,最终返回React.memo(React.forwardRef(Component))——因此通过createSvgIcon创建的图标组件天然支持 ref 转发和 memo 化。

其他图标库:MDI

Material Design Icons(mdi)提供 2000+ 图标。对于目标图标,可复制其提供的 SVGpath,作为SvgIcon的 children 或经createSvgIcon()包装使用。也可使用社区项目mdi-material-ui,它已用SvgIcon包装好这些图标,无需自行处理。

Icon:字体图标组件

Icon组件可渲染任何支持连字(ligatures)的图标字体。前提是先引入一种图标字体,例如 Material Icons 字体。使用时只需把图标名(字体连字)包在Icon组件内:

import Icon from '@mui/material/Icon'; <Icon>star</Icon>;

默认情况下Icon继承当前文字颜色,也可以通过color属性设置主题色:primarysecondaryactionerrordisabled

Font Material Icons

Icon默认会设置 Material Icons 字体(filled 变体)的正确基础类名,因此只需要加载字体即可,例如通过 Google Web Fonts:

<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />

自定义字体与 baseClassName

对其他字体,可以用baseClassName属性定制基础类名。例如要显示 two-tone 图标,需要引入对应字体的 two-tone 变体类名:

import Icon from '@mui/material/Icon'; <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Material+Icons+Two+Tone" // 引入 two-tone 的 MD 字体变体 ^^^^^^^^ />

官方演示见 TwoToneIcons.tsx。

全局默认 baseClassName

每次使用都修改baseClassName很繁琐,可以通过主题的defaultProps全局修改默认值:

const theme = createTheme({ components: { MuiIcon: { defaultProps: { // 替换 `material-icons` 默认值 baseClassName: 'material-icons-two-tone', }, }, }, });

之后即可直接使用 two-tone 字体:

<Icon>add_circle</Icon>

这一机制依赖 Icon.js 中的useDefaultProps({ props: inProps, name: 'MuiIcon' })——组件在解构 props 前先合并MuiIcon的默认值,其中baseClassName的内置默认值为'material-icons'(见 L122)。

Font Awesome 支持

Font Awesome 图标可以与Icon组件配合使用(官方演示 FontAwesomeIcon.tsx)。需要注意的是 Font Awesome 图标与 Material Icons 的设计方式不同:fa 图标被裁剪以占满全部可用空间。可以用全局覆盖来调整:

const theme = createTheme({ components: { MuiIcon: { styleOverrides: { root: { // Match 24px = 3 * 2 + 1.125 * 16 boxSizing: 'content-box', padding: 3, fontSize: '1.125rem', }, }, }, }, });

调整效果演示见 FontAwesomeIconSize.tsx。

从源码看,Icon的根节点是一个<span>,基础样式为width/height: 1emdisplay: inline-block,并始终附加aria-hiddennotranslate类(防止浏览器翻译图标连字文本,见 Icon.js L140-L155)。fontSize的档位与SvgIcon类似,但large档为 36px 而非 35px(见 L75-L82)。

字体图标 vs SVG 图标:如何选择

两种方案都能正常工作,但在性能与渲染质量上存在细微差别。只要条件允许,优先选择 SVG:它支持代码分割、可承载更多图标、渲染更快且质量更好。文档中也引用了 GitHub 从字体图标迁移到 SVG 图标的工程实践作为佐证。

结合本文的源码分析可以补充一点实现层面的差异:SVG 方案下每个图标是独立组件,可按需导入、精确剔除;而字体方案依赖整包字体文件的加载,图标数量越多字体文件越大,但换来的是极小的 JS 体积——适合图标数量巨大且以文本样式为主的场景。

无障碍(Accessibility)

图标可以传达大量有意义的信息,因此需要确保在合适场景下可访问。有两类典型场景:

  • 装饰性图标:仅用于视觉或品牌强化,即使从页面移除,用户仍能理解和使用页面。
  • 语义性图标:用于传达含义而非纯装饰,例如不带文字、作为交互控件(按钮、表单元素、开关)的图标。

装饰性图标

纯装饰性图标开箱即用。源码中可以看到 SvgIcon.js L137-L140 的默认行为:未提供titleAccess时自动附加aria-hidden="true",使屏幕阅读器跳过该图标;Icon.js 则无条件对字体图标添加aria-hidden

语义性 SVG 图标

应传入有意义的titleAccess属性。此时role="img"属性与<title>元素会被一并添加(实现见 SvgIcon.js L139-L148):

<SvgIcon titleAccess="delete"> <path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z" /> </SvgIcon>

对于可聚焦的交互元素(例如配合图标按钮使用时),可以用aria-label属性:

import IconButton from '@mui/material/IconButton'; import SvgIcon from '@mui/material/SvgIcon'; <IconButton aria-label="delete"> <SvgIcon> <path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z" /> </SvgIcon> </IconButton>

语义性字体图标

字体图标需要提供只对辅助技术可见的文本替代:

import Box from '@mui/material/Box'; import Icon from '@mui/material/Icon'; import { visuallyHidden } from '@mui/utils'; <Icon>add_circle</Icon> <Box component="span" sx={visuallyHidden}>Create a user</Box>

小结

Material UI 的图标体系可以概括为"一个底层、两条入口":SvgIcon是所有 SVG 图标的渲染底座,createSvgIcon在其上批量生成@mui/icons-material组件,Icon则通过baseClassName机制兼容任意连字字体(Material Icons、two-tone 变体、Font Awesome 等)。选型上优先 SVG 方案以获得代码分割与渲染质量优势;无障碍方面,装饰性图标依赖默认的aria-hidden,语义性 SVG 图标用titleAccess,语义性字体图标用visuallyHidden文本替代。相关实现可继续深入 SvgIcon.js、Icon.js 及其测试文件 SvgIcon.test.js、Icon.test.js 查看。

【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Google's Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询