React项目无需逐个引入SVG路径渲染50+复用SVG图标的最优方案是什么


问题背景
我在实现上图所示的设计功能时遇到瓶颈,想要渲染第二张图中的可复用SVG图标,不想逐个引入本地SVG文件的完整路径,目前已经实现了如下动态引入的Icon组件,想咨询是否有更优的实现方案?
现有代码:
import React, { useEffect, useState } from "react"; function Icon({ name, ...otherProps }) { /* Use state hook to store icon module value */ const [iconModule, setIconModule] = useState(null); useEffect(() => { /* Use dynamic import to get corresponding icon as a module */ import(`./icons/${name}.svg`) .then((module) => { /* Persist data in state */ setIconModule(module); }) .catch((error) => { /* Do not forget to handle errors */ console.error(`Icon with name: ${name} not found!`); }); }, [name /* update on name change */]); const renderIcon = () => { if (!iconModule) return null; /** * Equal to: * import { ReactComponent as Icon } from "./path/to/icon.svg"; */ const Component = iconModule.ReactComponent; return <Component {...otherProps} />; }; return <>{renderIcon()}</>; } export default Icon;
优化方案
你现有的动态引入方案可以正常运行,但存在加载延迟、同图标重复请求、无加载占位的问题,可根据项目规模选择以下更优方案:
方案1:批量预引入所有图标 适合图标总数<200的项目
一次性把所有图标导入到映射对象中,组件同步读取对应图标,无加载延迟,逻辑最简单。
如果是Vite项目,用import.meta.glob实现:
// src/components/Icon.jsx import React from 'react'; // 批量导入所有svg,eager设为true表示同步加载所有模块 const svgModules = import.meta.glob('./icons/*.svg', { eager: true, import: 'ReactComponent' }); // 生成「文件名-组件」映射表 const iconMap = Object.fromEntries( Object.entries(svgModules).map(([path, Component]) => { const name = path.match(/\/([^/]+)\.svg$/)[1]; return [name, Component]; }) ); function Icon({ name, ...otherProps }) { const Component = iconMap[name]; if (!Component) { console.error(`Icon ${name} not found`); return null; } return <Component {...otherProps} />; } export default Icon;
如果是Webpack项目,把批量导入逻辑替换为require.context即可:
const svgRequire = require.context('./icons', false, /\.svg$/); const iconMap = {}; svgRequire.keys().forEach(path => { const name = path.match(/\.\/([^/]+)\.svg$/)[1]; iconMap[name] = svgRequire(path).ReactComponent; });
方案2:SVG雪碧图方案 适合图标数量多、对性能要求高的项目
把所有SVG图标打包成一个雪碧图文件,加载一次即可全局使用所有图标,缓存效率更高,整体体积比单独打包多个SVG更小。
可使用vite-plugin-svg-icons(Vite生态)或svg-sprite-loader(Webpack生态)实现,组件写法更简洁:
function Icon({ name, className = '', ...props }) { return ( <svg className={`icon ${className}`} {...props}> <use xlinkHref={`#icon-${name}`} /> </svg> ); }
使用时直接传图标名即可,还可以通过CSS的color、font-size属性直接控制图标颜色和大小,灵活度比引入SVG组件更高。
方案3:保留异步引入逻辑的优化 适合图标量极大、不想增加首屏体积的项目
如果需要保留按需加载的特性,可以给现有逻辑加全局缓存和加载占位,优化体验:
import React, { useEffect, useState } from "react"; // 全局缓存已加载的图标模块,避免重复请求 const iconCache = new Map(); function Icon({ name, fallback = null, ...otherProps }) { const [Component, setComponent] = useState(null); useEffect(() => { // 命中缓存直接返回 if (iconCache.has(name)) { setComponent(iconCache.get(name)); return; } import(`./icons/${name}.svg`) .then((module) => { iconCache.set(name, module.ReactComponent); setComponent(module.ReactComponent); }) .catch(() => { console.error(`Icon ${name} not found`); }); }, [name]); return Component ? <Component {...otherProps} /> : fallback; } export default Icon;
内容的提问来源于stack exchange,提问作者Shehabd Dwedar
相关产品推荐
相关产品推荐

