SVG 转 React 组件需要将 HTML 属性转换为 JSX (class 变 className, stroke-width 变 strokeWidth), 删除多余元数据, 并包装成带类型 props 的可复用组件. SVGR 等工具可自动完成此过程. 先用 SVGO 优化 SVG 可减小 20-60% 文件大小. 为确保无障碍访问, 根据图标是装饰性还是有意义的来添加 aria-label 或 aria-hidden. 使用我们的免费 SVG 转 JSX 工具即时获取可用于生产的 React 组件.
- JSX 要求驼峰命名的 SVG 属性: stroke-width 变为 strokeWidth, fill-rule 变为 fillRule, clip-path 变为 clipPath.
- SVGR 是将 SVG 文件转换为 React 组件的行业标准工具, Create React App 和 Next.js 都在使用.
- 在转换为 React 之前务必运行 SVGO. 它可以移除编辑器元数据并将 SVG 文件大小减少 20-60%.
- 与 img 标签或 CSS 背景相比, 内联 SVG 组件提供对样式, 动画和无障碍的完全控制.
- 对于 50+ 图标的图标库, 请考虑使用 SVG sprites 或可 tree-shaking 的图标包以最小化包体积.
- 装饰性图标需要 aria-hidden="true"; 有意义的独立图标需要 role="img" 和 aria-label.
1. 为什么要将 SVG 转换为 React 组件?
SVG (可缩放矢量图形) 是现代 Web 应用中图标, 标志和插图的主流格式. 与光栅图像 (PNG, JPEG, WebP) 不同, SVG 不受分辨率限制, 可通过 CSS 设置样式, 且可动画化. 但在 React 中使用 SVG 并不像复制粘贴那么简单.
在 React 中使用 SVG 有三种方式: <img src="icon.svg">, CSS 背景图, 以及内联 SVG 作为 JSX. 内联 SVG 是最强大的方式, 因为它提供:
- 完全的 CSS 控制: 使用 CSS 类或内联样式为单个路径, 组和元素设置样式
- 动态 props: 在运行时通过 React props 更改颜色, 大小和描边宽度
- 动画: 使用 CSS transitions, Framer Motion 或 GSAP 为 SVG 的特定部分添加动画
- 无障碍: 直接向 SVG 元素添加 ARIA 属性, 标题和描述
- 事件处理: 向 SVG 元素附加 onClick, onHover 和其他事件处理程序
权衡是内联 SVG 会成为 JavaScript 包的一部分. 对于小图标 (每个 2KB 以下), 影响可以忽略. 对于大型复杂插图, 考虑使用 <img> 标签加载外部 SVG 文件.
2. JSX 中的 SVG 属性差异
将 SVG 移入 React 时最大的障碍是属性名称转换. SVG 使用短横线命名 (kebab-case), 但 JSX 要求驼峰命名 (camelCase). 以下是需要转换的完整属性列表:
| SVG / HTML 属性 | JSX 等效属性 |
|---|---|
class | className |
stroke-width | strokeWidth |
stroke-linecap | strokeLinecap |
stroke-linejoin | strokeLinejoin |
stroke-dasharray | strokeDasharray |
stroke-dashoffset | strokeDashoffset |
fill-rule | fillRule |
fill-opacity | fillOpacity |
clip-path | clipPath |
clip-rule | clipRule |
font-size | fontSize |
text-anchor | textAnchor |
xlink:href | xlinkHref |
xmlns:xlink | xmlnsXlink |
color-interpolation | colorInterpolation |
dominant-baseline | dominantBaseline |
stop-color | stopColor |
stop-opacity | stopOpacity |
除了属性名称, 还有其他语法变更:
style="fill:red;stroke:blue"必须变为style={{ fill: "red", stroke: "blue" }}(对象语法)- 自闭合标签如
<path>必须使用<path /> xmlns="http://www.w3.org/2000/svg"在 JSX 中是可选的 (React 会自动添加)- 注释
<!-- ... -->必须使用{/* ... */}语法
哪怕遗漏一个属性转换, 都会触发 React 控制台警告, 并可能导致渲染问题.
3. 手动转换 vs. 自动化工具
对于单个图标, 手动转换很简单: 重命名属性, 删除不必要的元数据, 然后包装成组件. 步骤如下:
- 第 1 步: 复制 SVG 标记: 从设计工具 (Figma, Sketch, Illustrator) 或 SVG 文件中复制.
- 第 2 步: 删除不必要的属性: 如 xmlns, xml:space, 编辑器特定的 data 属性和注释.
- 第 3 步: 将短横线属性重命名为驼峰命名 (参见上表).
- 第 4 步: 转换内联样式: 从字符串转换为 JavaScript 对象.
- 第 5 步: 包装为 React 组件: 添加 TypeScript props (size, color) 和 SVG 属性透传.
这对 1-5 个图标有效, 但对于更大的集合来说既繁琐又容易出错. SVGR 等自动化工具可以在一步中处理所有转换和优化.
手动转换示例:
// Before: Raw SVG from Figma
<svg xmlns="http://www.w3.org/2000/svg" class="icon"
width="24" height="24" viewBox="0 0 24 24"
fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round"
stroke-linejoin="round">
<path d="M20 6L9 17l-5-5"/>
</svg>
// After: React Component
interface CheckIconProps extends React.SVGProps<SVGSVGElement> {
size?: number;
}
function CheckIcon({ size = 24, ...props }: CheckIconProps) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M20 6L9 17l-5-5" />
</svg>
);
}4. SVGR: 行业标准的 SVG 转 React 工具
SVGR 是将 SVG 文件转换为 React 组件的事实标准工具. Create React App, Next.js, Vite (通过插件) 和大多数主流 React 工具链都在内部使用它. SVGR 处理:
- 属性转换 (短横线到驼峰命名)
- style 字符串到对象的转换
- xmlns 移除
- title 元素注入以提高无障碍性
- ref 转发以便 DOM 访问
- TypeScript 类型生成
- 集成 SVGO 进行优化
你可以通过多种方式使用 SVGR:
CLI 用法
# Install SVGR CLI
npm install -D @svgr/cli
# Convert a single SVG file
npx @svgr/cli icon.svg
# Convert all SVGs in a directory to React components
npx @svgr/cli --out-dir src/components/icons src/assets/svg/
# With TypeScript support
npx @svgr/cli --typescript --out-dir src/icons src/svg/
# With SVGO optimization
npx @svgr/cli --svgo --out-dir src/icons src/svg/Webpack / Next.js 集成
// next.config.js
module.exports = {
webpack(config) {
config.module.rules.push({
test: /\.svg$/,
use: ['@svgr/webpack'],
});
return config;
},
};
// Now you can import SVGs as React components:
import Logo from './logo.svg';
function Header() {
return <Logo width={120} height={40} />;
}Vite 集成
// vite.config.ts
import svgr from 'vite-plugin-svgr';
export default defineConfig({
plugins: [react(), svgr()],
});
// Import as React component
import { ReactComponent as Logo } from './logo.svg';
// Or with default export
import Logo from './logo.svg?react';SVGR 还支持自定义模板, 允许你精确定义输出组件的样式. 这对在大型图标库中实施一致的模式非常有用.
5. 转换前使用 SVGO 优化 SVG
SVGO (SVG 优化器) 是一个 Node.js 工具, 通过移除冗余信息, 折叠组, 合并路径和清理元数据来优化 SVG 文件. 在转换为 React 组件之前运行 SVGO 可将 SVG 大小减少 20-60%.
SVGO 移除和优化的内容:
- 编辑器元数据: Inkscape, Illustrator 和 Sketch 的 data 属性, 注释和处理指令
- 冗余属性: 浏览器自动应用的默认值 (fill="black", stroke="none")
- 空元素: 无可见内容的组, defs 和容器
- 精度: 将坐标精度从 15 位小数减少到 2-3 位, 不会造成可见质量损失
- 路径优化: 合并相邻路径段并简化曲线
- ID 清理: 移除或缩短自动生成的 ID, 避免多个内联 SVG 时的冲突
SVGO 内置于 SVGR 中, 所以使用 SVGR 即可免费获得优化. 你也可以单独使用 SVGO:
# Install SVGO
npm install -D svgo
# Optimize a single file
npx svgo input.svg -o output.svg
# Optimize all SVGs in a folder
npx svgo -f ./src/assets/svg/ -o ./src/assets/svg-optimized/
# With custom config (svgo.config.js)
module.exports = {
plugins: [
'preset-default',
'removeDimensions', // Remove width/height, keep viewBox
'removeXMLNS', // Remove xmlns for inline use
{ name: 'removeAttrs', // Remove specific attributes
params: { attrs: '(data-.*)' }
},
{ name: 'sortAttrs' }, // Consistent attribute order
],
};
# Before SVGO: 2,847 bytes
# After SVGO: 892 bytes (68% reduction)6. 创建带 Props 的可复用 SVG 图标组件
设计良好的 SVG 图标组件应足够灵活以在应用中的任何地方使用. 关键原则:
- 接受 size prop: 一个数字同时设置宽度和高度 (图标通常是正方形的)
- 接受 color prop: 默认为 currentColor 以继承父级文字颜色
- 展开剩余 props: 使用
...rest转发 className, onClick, aria-label, data 属性等 - 使用 TypeScript: 扩展
React.SVGProps<SVGSVGElement>获得完整类型安全 - 转发 refs: 当使用者需要 DOM 访问来做动画或测量时使用
React.forwardRef
以下是生产可用的图标组件模式:
import React from 'react';
interface IconProps extends React.SVGProps<SVGSVGElement> {
size?: number;
color?: string;
title?: string;
}
const HeartIcon = React.forwardRef<SVGSVGElement, IconProps>(
({ size = 24, color = 'currentColor', title, ...rest }, ref) => (
<svg
ref={ref}
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke={color}
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
role={title ? 'img' : undefined}
aria-label={title}
aria-hidden={title ? undefined : true}
{...rest}
>
{title && <title>{title}</title>}
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
</svg>
)
);
HeartIcon.displayName = 'HeartIcon';
export default HeartIcon;使用示例:
// Basic usage - inherits parent text color
<HeartIcon />
// Custom size and color
<HeartIcon size={32} color="#ef4444" />
// With accessibility label
<HeartIcon title="Add to favorites" />
// With className for Tailwind
<HeartIcon className="text-red-500 hover:text-red-600 transition-colors" />
// With event handler
<HeartIcon onClick={() => toggleFavorite()} style={{ cursor: 'pointer' }} />
// With ref for animations
const iconRef = useRef<SVGSVGElement>(null);
<HeartIcon ref={iconRef} />7. SVG Sprites vs. 内联 SVG vs. img 标签
在 React 应用中使用 SVG 有三种主要策略, 各有权衡:
内联 SVG 组件 (大多数应用推荐)
每个图标是一个独立的 React 组件, SVG 标记内联其中.
优点: 完全的样式控制, 动态 props, 动画支持, 可 tree-shake, 良好的开发体验
缺点: 增加 JS 包体积, 无法与 JS 分开缓存
SVG Sprite (symbol + use)
所有图标使用 <symbol> 元素在一个 SVG sprite 文件中定义一次. 单个图标通过 <use href="#icon-name"> 引用.
<!-- sprites.svg -->
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
<symbol id="icon-heart" viewBox="0 0 24 24">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0..." />
</symbol>
<symbol id="icon-star" viewBox="0 0 24 24">
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87..." />
</symbol>
</svg>
<!-- Usage -->
<svg width="24" height="24">
<use href="#icon-heart" />
</svg>优点: 图标在 DOM 中只定义一次, 更小的 JS 包, 浏览器缓存 sprite
缺点: 样式受限 (无法为每个实例设置不同路径颜色), 设置更复杂, 无 tree-shaking
img 标签加载外部 SVG 文件
SVG 文件作为静态资源加载, 就像 PNG 或 JPEG 图片一样.
// External SVG via img tag
<img src="/icons/logo.svg" alt="Company logo" width={120} height={40} />
// Next.js Image component
import Image from 'next/image';
<Image src="/icons/logo.svg" alt="Logo" width={120} height={40} />优点: 可缓存, 无 JS 包影响, 适合大型复杂 SVG
缺点: 无样式控制, 无动画, 额外 HTTP 请求, 无动态颜色
经验法则: 对交互式的带样式图标使用内联 SVG 组件. 对大型图标集 (100+ 图标) 使用 sprites. 对复杂插图和装饰图像使用 img 标签.
8. 无障碍: aria-label, role="img", title 元素
SVG 无障碍常被忽视, 但对屏幕阅读器用户至关重要. 正确方法取决于图标上下文:
装饰性图标 (在文字标签旁)
当图标出现在可见文字旁边时, 图标是装饰性的. 对屏幕阅读器隐藏它:
// Decorative icon next to text - hide from screen readers
<button>
<svg aria-hidden="true" focusable="false" width="16" height="16" viewBox="0 0 24 24">
<path d="M19 7l-7 7-7-7" />
</svg>
<span>Show more</span>
</button>有意义的图标 (独立使用, 无文字)
当图标不带文字独立传达含义时, 它需要无障碍名称:
// Meaningful icon - needs accessible name
<button aria-label="Close dialog">
<svg aria-hidden="true" width="24" height="24" viewBox="0 0 24 24">
<path d="M18 6L6 18M6 6l12 12" />
</svg>
</button>
// Standalone meaningful icon (no button wrapper)
<svg role="img" aria-label="Warning: action required" width="24" height="24" viewBox="0 0 24 24">
<path d="M12 2L2 22h20L12 2zm0 4l7.5 14h-15L12 6z" />
<path d="M12 10v4m0 2v2" />
</svg>使用 title 元素
SVG <title> 元素提供某些屏幕阅读器会读出的文本描述. 使用 aria-labelledby 关联:
// Using <title> with aria-labelledby for broadest support
<svg
role="img"
aria-labelledby="icon-title-settings"
width="24" height="24"
viewBox="0 0 24 24"
>
<title id="icon-title-settings">Settings</title>
<path d="M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z" />
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l..." />
</svg>处理所有三种情况的可复用模式:
interface AccessibleIconProps extends React.SVGProps<SVGSVGElement> {
size?: number;
color?: string;
label?: string; // If provided, icon is "meaningful"
children: React.ReactNode; // SVG paths
}
function AccessibleIcon({ size = 24, color = 'currentColor', label, children, ...rest }: AccessibleIconProps) {
const isDecorative = !label;
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke={color}
strokeWidth={2}
role={isDecorative ? undefined : 'img'}
aria-label={isDecorative ? undefined : label}
aria-hidden={isDecorative ? true : undefined}
focusable={isDecorative ? false : undefined}
{...rest}
>
{children}
</svg>
);
}9. React 中的 SVG 动画 (CSS, Framer Motion, GSAP)
内联 SVG 的最大优势之一是能为单个元素添加动画. 以下是三种最常见的方法:
CSS 动画
最简单的方法. 使用 CSS 关键帧或过渡来动画化 SVG 属性 (opacity, transform, stroke-dashoffset, fill):
// CSS animation: spinning loader
function SpinnerIcon({ size = 24 }: { size?: number }) {
return (
<>
<style>{`
@keyframes spin { to { transform: rotate(360deg); } }
.svg-spinner { animation: spin 1s linear infinite; }
`}</style>
<svg className="svg-spinner" width={size} height={size} viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="10" stroke="#e2e8f0" strokeWidth="3" />
<path d="M12 2a10 10 0 0 1 10 10" stroke="#3b82f6" strokeWidth="3" strokeLinecap="round" />
</svg>
</>
);
}
// CSS transition: stroke animation on hover
function AnimatedCheck({ size = 48 }: { size?: number }) {
return (
<>
<style>{`
.check-path {
stroke-dasharray: 30;
stroke-dashoffset: 30;
transition: stroke-dashoffset 0.4s ease;
}
.check-icon:hover .check-path {
stroke-dashoffset: 0;
}
`}</style>
<svg className="check-icon" width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="#22c55e" strokeWidth="2">
<circle cx="12" cy="12" r="10" opacity="0.2" />
<path className="check-path" d="M8 12l3 3 5-5" strokeLinecap="round" />
</svg>
</>
);
}Framer Motion
Framer Motion 通过声明式 motion 组件提供最佳的 React SVG 动画体验. 支持路径变形, 交错效果和弹簧物理:
import { motion } from 'framer-motion';
// Animated path drawing with Framer Motion
function AnimatedHeart() {
return (
<svg width="48" height="48" viewBox="0 0 24 24" fill="none">
<motion.path
d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"
stroke="#ef4444"
strokeWidth={2}
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 2, ease: 'easeInOut' }}
/>
</svg>
);
}
// SVG with hover scale and color change
function InteractiveIcon() {
return (
<motion.svg
width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor"
whileHover={{ scale: 1.2, stroke: '#3b82f6' }}
whileTap={{ scale: 0.9 }}
transition={{ type: 'spring', stiffness: 300 }}
>
<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
</motion.svg>
);
}GSAP (GreenSock)
GSAP 是复杂时间线 SVG 动画的最强选择. 它在所有浏览器中一致处理 SVG 变换:
import { useRef, useEffect } from 'react';
import { gsap } from 'gsap';
function GsapSvgAnimation() {
const pathRef = useRef<SVGPathElement>(null);
const circleRef = useRef<SVGCircleElement>(null);
useEffect(() => {
const tl = gsap.timeline({ repeat: -1, yoyo: true });
tl.from(pathRef.current, {
strokeDashoffset: 100,
strokeDasharray: 100,
duration: 1.5,
ease: 'power2.inOut',
});
tl.to(circleRef.current, {
scale: 1.2,
transformOrigin: 'center',
duration: 0.5,
ease: 'elastic.out(1, 0.5)',
}, '-=0.5');
}, []);
return (
<svg width="64" height="64" viewBox="0 0 24 24" fill="none">
<circle ref={circleRef} cx="12" cy="12" r="10" stroke="#3b82f6" strokeWidth="1.5" />
<path ref={pathRef} d="M8 12l3 3 5-5" stroke="#22c55e" strokeWidth="2" strokeLinecap="round" />
</svg>
);
}选择 CSS 处理简单悬停和加载动画. 选择 Framer Motion 处理 React 风格的手势动画. 选择 GSAP 处理复杂的序列动画和 SVG 变形.
10. 性能: SVG vs. PNG vs. WebP 图标对比
选择正确的图标格式影响性能和视觉质量:
| 格式 | 典型图标大小 | 可缩放 | CSS 可样式化 | 可动画 | 最适合 |
|---|---|---|---|---|---|
| SVG | 200-800 B | Yes | Yes | Yes | Icons, logos, UI graphics |
| PNG | 1-5 KB (@2x) | No | No | No | Photos, complex images |
| WebP | 0.5-3 KB | No | No | Limited | Photos (smaller than PNG) |
| Icon Font | 50-200 KB (full set) | Yes | Color only | No | Legacy projects |
对于图标和简单图形, SVG 几乎总是最佳选择. 优化后的典型图标 SVG 大小为 200-800 字节, 而 PNG 在多种分辨率下为 1-5KB.
React 中 SVG 的性能建议:
- 用 SVGO 优化: 转换为组件前移除不必要的元数据
- 懒加载复杂 SVG: 对 5KB 以上的插图使用 React.lazy() 或动态导入
- 避免内联大型图形: 地图, 详细插图和复杂图表应使用 img 标签
- 使用 currentColor: 避免不同颜色变体的重复 SVG
- 简化路径复杂度: 更简单的路径渲染更快, 文件更小
11. Tree-Shaking SVG 图标库
使用 React 图标库 (Lucide, Heroicons, Phosphor, React Icons) 时, tree-shaking 确保只有实际导入的图标包含在生产包中. 图标库可能包含数千个图标, 总计数 MB.
要启用有效的 tree-shaking, 请从特定模块路径使用命名导入:
// GOOD: Tree-shakable named imports
import { Heart, Star, Search } from 'lucide-react';
import { ArrowRightIcon, CheckIcon } from '@heroicons/react/24/outline';
// BAD: Barrel import that may include entire library
import * as Icons from 'lucide-react'; // All 1000+ icons bundled
// GOOD: Import from specific sub-package (react-icons)
import { FaHeart } from 'react-icons/fa';
import { HiSearch } from 'react-icons/hi';
// BAD: Import from root (no tree-shaking)
import { FaHeart } from 'react-icons'; // May bundle all icon sets流行 React 图标库的 tree-shaking 对比:
| 库名称 | 图标总数 | 可 Tree-Shake | 单图标大小 |
|---|---|---|---|
| Lucide React | 1,400+ | Yes | ~500 B |
| Heroicons | 300+ | Yes | ~400 B |
| Phosphor Icons | 7,000+ | Yes | ~600 B |
| React Icons | 40,000+ | Per sub-package | ~300-800 B |
如果你的应用只使用大库中的 10-20 个图标, tree-shaking 与导入整个库的差异可达 50KB vs. 2MB+.
提示: 某些打包器 (特别是旧的 webpack 配置) 在使用桶导入时无法正确 tree-shake. 始终从特定子包导入.
使用我们的免费 SVG 转 JSX/React 工具
/zh/tools/svg-to-jsx →常见问题
将 SVG 转换为 React 组件最简单的方法是什么?
最简单的方法是使用在线 SVG 转 JSX 工具. 粘贴 SVG 代码即可立即获得 React 组件. 对于批量转换多个 SVG 文件, 使用 SVGR CLI: npx @svgr/cli --out-dir src/icons src/svg/
在 React 中应该使用内联 SVG 还是 SVG sprite 系统?
对于少于 50 个图标的大多数 React 应用, 内联 SVG 组件更简单灵活. 对于大型图标集 (100+ 图标), 考虑使用 SVG sprite 减少 JS 包体积. 两种方法都能很好地配合 React.
内联 SVG 会显著增加 JavaScript 包体积吗?
对于典型图标 (每个 200-800 字节), 影响很小. 20 个优化后的内联 SVG 图标在 gzip 前约增加 8-15KB. 对于大型复杂 SVG, 使用 img 标签或 React.lazy() 动态导入.
SVGR 和 SVGO 有什么区别?
SVGO 是优化 SVG 文件的独立工具. SVGR 是将 SVG 文件转换为 React 组件的工具, 处理属性转换和 TypeScript 类型. SVGR 内部使用 SVGO 进行优化.
如何在 React 中让 SVG 图标具有无障碍性?
装饰性图标添加 aria-hidden="true" 和 focusable="false". 有意义的独立图标添加 role="img" 和 aria-label. 按钮内的图标, 按钮设置 aria-label, SVG 设置 aria-hidden="true".
可以在 React 中为 SVG 添加动画吗? 应该使用什么库?
可以. CSS 动画适合简单效果. Framer Motion 适合 React 风格的手势动画. GSAP 适合复杂的时间线动画和 SVG 变形.
为什么粘贴 SVG 代码时 React 会显示警告?
React 显示警告是因为 SVG 属性使用短横线命名而 JSX 需要驼峰命名. 使用 SVG 转 JSX 工具可自动处理所有转换.
如何在 Next.js 或 Vite 中使用 SVGR?
Next.js 安装 @svgr/webpack 并在 next.config.js 中添加 webpack 规则. Vite 安装 vite-plugin-svgr 并添加到 vite.config.ts 的 plugins 中.