文章摘要 FakeGPT
加载中...|
1. 方案概述
本项目使用 @svgr/webpack 将 SVG 文件自动转换为 React 组件,实现图标的组件化管理。
核心优势
- 自动化注册:无需手动 import 每个 SVG,放入指定目录即可使用
- 组件化封装:提供统一的
<SvgIcon />组件,支持类型安全和代码补全 - 灵活导入:支持两种导入方式:
- 作为 React 组件使用(默认)
- 作为 URL 使用(添加
?url后缀)
- 高性能:构建时优化 SVG,运行时零网络请求
2. 安装依赖
在项目根目录安装 @svgr/webpack:
bash
pnpm add -D @svgr/webpack3. 配置文件
3.1 配置 next.config.ts
修改 next.config.ts 以配置 webpack 的 SVG 处理规则:
typescript
import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";
import AutoImport from "unplugin-auto-import/webpack";
import { codeInspectorPlugin } from "code-inspector-plugin";
const withNextIntl = createNextIntlPlugin();
const nextConfig: NextConfig = {
reactStrictMode: false,
output: "standalone",
compress: true,
// ... 其他配置
webpack: (config) => {
// ... 其他 webpack 插件配置
// SVGR 配置 - 将 SVG 文件转换为 React 组件
const fileLoaderRule = config.module.rules.find((rule: any) => rule.test?.test?.(".svg"));
if (fileLoaderRule) {
// 排除 SVG 文件,不使用默认的 file-loader
fileLoaderRule.exclude = /\\.svg$/i;
}
config.module.rules.push(
// 为 *.svg?url 保留原有的文件加载规则
{
...fileLoaderRule,
test: /\\.svg$/i,
resourceQuery: /url/, // 匹配 *.svg?url
},
// 将其他所有 *.svg 转换为 React 组件
{
test: /\\.svg$/i,
issuer: fileLoaderRule.issuer,
resourceQuery: { not: [...fileLoaderRule.resourceQuery.not, /url/] },
use: [
{
loader: "@svgr/webpack",
options: {
svgo: true, // 开启 SVG 优化
dimensions: false, // 移除默认宽高,方便通过 props 或 CSS 控制
svgoConfig: {
plugins: [
{
name: "preset-default",
params: {
overrides: {
// 保留 viewBox 属性,确保 SVG 缩放正确
removeViewBox: false,
},
},
},
],
},
},
},
],
},
);
return config;
},
};
export default withNextIntl(nextConfig);3.2 添加 TypeScript 类型定义
创建 src/types/svg.d.ts 文件,为 SVG 模块添加类型支持:
typescript
declare module "*.svg" {
import React from "react";
const SVGComponent: React.FC<React.SVGProps<SVGSVGElement>>;
export default SVGComponent;
}4. 核心实现
4.1 创建 SVG 注册表 (svgRegistry.ts)
创建 src/components/SvgIcon/svgRegistry.ts,自动扫描并注册所有 SVG 图标:
typescript
/**
* SVG 图标注册表
* 自动导入 src/assets/images/svgs/ 目录下的所有 SVG 文件(包括子目录)
*/
import { ComponentType, SVGProps } from 'react';
// SVG 组件类型
export type SvgComponent = ComponentType<SVGProps<SVGSVGElement>>;
// SVG 注册表接口
export interface SvgRegistry {
[key: string]: SvgComponent;
}
// 使用 require.context 自动导入所有 SVG 文件(包括子目录)
// @ts-ignore - require.context 是 webpack 提供的功能
const svgContext = require.context('@/assets/images/svgs', true, /\\.svg$/);
// 自动生成 SVG 注册表
export const svgRegistry: SvgRegistry = svgContext
.keys()
.reduce((registry: SvgRegistry, path: string) => {
// 从路径中提取文件名并转换为图标名称
// 例如:'./ai-camera.svg' -> 'ai-camera'
// './home/arrow.svg' -> 'home-arrow'
const name = path
.replace(/^\\.\\//, '') // 移除开头的 ./
.replace(/\\.svg$/, '') // 移除 .svg 扩展名
.replace(/\\//g, '-'); // 将 / 替换为 -
// 导入并注册 SVG 组件
const component = svgContext(path).default;
registry[name] = component;
return registry;
}, {});
/**
* 获取 SVG 组件
* @param name - SVG 名称(不含 icon- 前缀)
*/
export const getSvgComponent = (name: string): SvgComponent | undefined => {
return svgRegistry[name];
};
/**
* 获取所有可用的 SVG 图标名称
*/
export const getAvailableSvgNames = (): string[] => {
return Object.keys(svgRegistry);
};
// 开发环境下打印所有可用图标
if (process.env.NODE_ENV === 'development') {
console.log('[SVG Registry] 已加载的图标:', getAvailableSvgNames().sort());
}4.2 创建 SvgIcon 组件
创建 src/components/SvgIcon/index.tsx,提供统一的图标使用接口:
typescript
import React from 'react';
import { getSvgComponent } from './svgRegistry';
export interface SvgIconProps {
/**
* 图标名称
* 格式:icon-{文件名}
* 例如:icon-play, icon-pause
*/
name: string;
/**
* 图标大小(宽高相同)
* @default 24
*/
size?: number;
/**
* 自定义类名
*/
className?: string;
/**
* 自定义样式
*/
style?: React.CSSProperties;
}
/**
* 通用 SVG 图标组件
*
* 使用方式:
* ```tsx
* <SvgIcon name="icon-play" size={24} className="text-blue-500" />
* ```
*/
const SvgIcon: React.FC<SvgIconProps> = ({
name,
size = 24,
className = '',
style
}) => {
// 移除 icon- 前缀获取实际的 SVG 名称
const iconName = name.replace(/^icon-/, '');
// 从注册表中获取 SVG 组件
const SvgComponent = getSvgComponent(iconName);
// 如果找不到对应的 SVG 组件,返回 null 并在开发环境下输出警告
if (!SvgComponent) {
if (process.env.NODE_ENV === 'development') {
console.warn(
`[SvgIcon] Icon not found: ${iconName}. ` +
`Make sure it's registered in svgRegistry.ts`
);
}
return null;
}
// 渲染 SVG 组件
return (
<SvgComponent
width={size}
height={size}
className={className}
style={style}
/>
);
};
export default SvgIcon;5. 使用指南
5.1 添加图标
将 SVG 文件放入 src/assets/images/svgs/ 目录(支持子目录):
src/assets/images/svgs/
├── play.svg -> icon-play
├── pause.svg -> icon-pause
└── home/
└── arrow.svg -> icon-home-arrow5.2 使用 SvgIcon 组件
tsx
import SvgIcon from "@/components/SvgIcon";
export default function MyComponent() {
return (
<div className="flex gap-4">
{/* 基础用法 */}
<SvgIcon name="icon-play" />
{/* 自定义大小 */}
<SvgIcon name="icon-pause" size={32} />
{/* 使用 Tailwind CSS 样式 */}
<SvgIcon name="icon-home-arrow" className="w-8 h-8 text-blue-500" />
</div>
);
}5.3 直接导入为 React 组件(可选)
如果需要直接导入 SVG 作为组件:
tsx
import PlayIcon from "@/assets/images/svgs/play.svg";
export default function MyComponent() {
return <PlayIcon width={24} height={24} className="text-red-500" />;
}5.4 导入为 URL(可选)
如果需要导入 SVG 文件的 URL:
tsx
import playIconUrl from "@/assets/images/svgs/play.svg?url";
export default function MyComponent() {
return <img src={playIconUrl} alt="Play" />;
}6. 文件组织规范
6.1 命名规范
- SVG 文件名使用 kebab-case 命名(小写字母 + 连字符)
- 例如:
play-icon.svg、user-profile.svg
6.2 目录结构
建议按功能或类别组织 SVG 文件:
src/assets/images/svgs/
├── actions/ # 操作类图标
│ ├── play.svg
│ └── pause.svg
├── navigation/ # 导航类图标
│ ├── home.svg
│ └── back.svg
└── social/ # 社交媒体图标
├── facebook.svg
└── twitter.svg7. 性能分析
7.1 构建优化
- SVGO 优化:配置中已启用
svgo: true,会自动:- 移除无用的元数据和注释
- 优化路径数据
- 合并重复的元素
- 减小文件体积
7.2 运行时性能
- 零网络请求:所有 SVG 都被编译为 React 组件,无需额外的 HTTP 请求
- Tree Shaking:未使用的 SVG 图标可以被 webpack 移除
- 查找效率:图标通过哈希表存储,查找时间复杂度 O(1)
7.3 打包体积
- 机制:
require.context会将所有 SVG 打包到主 bundle 中 - 建议:
- 定期清理不再使用的图标
- 对于超大型图标库(>1000 个),考虑按需加载
- 使用 bundle analyzer 监控图标包大小
8. 故障排查
8.1 图标不显示
原因:图标名称错误或文件未正确导入
解决方案:
- 检查控制台警告信息
- 确认图标文件路径正确
- 查看开发环境下打印的已加载图标列表
8.2 TypeScript 报错
原因:类型定义缺失
解决方案:
- 确保
src/types/svg.d.ts文件存在 - 重启 TypeScript 服务器
8.3 样式不生效
原因:SVG 内部可能包含固定的 fill/stroke 属性
解决方案:
- 编辑 SVG 文件,移除固定的颜色属性
- 使用
currentColor值以支持 CSS 控制
9. 最佳实践
- 统一使用 SvgIcon 组件:保持代码一致性
- 合理组织目录结构:按功能分类便于维护
- 定期清理:移除不再使用的图标
- 优化 SVG 源文件:在添加前使用 SVGO 工具预先优化
- 使用语义化命名:图标名称应清晰表达用途
10. 参考资源
赞赏博主
评论 隐私政策