React Vite中如何无需保存文件展示API返回的动态SVG
在React + Vite中展示API获取的SVG字符串
问题描述
我从API获取了一段SVG字符串,格式如下:
<svg width="848" height="848" viewBox="0 0 848 848" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">xxxxxxxxx</svg>
希望在React组件中展示该SVG,但不想为每个SVG创建单独的.svg文件(比如import { ReactComponent as Ticket} from "./ticket.svg";这种方式)。同时因为SVG包含直接链接/API调用等复杂内容,<img src={svg} />方法也不适用。当前已启用vite-plugin-svgr,Vite配置如下:
export default defineConfig({ plugins: [svgr(), react()], });
现有组件代码:
const TicketDetail = () => { const [ticketSvg, setTicketSvg] = useState(); const getTicketSVG = () => { // ...fetching SVG here setTicketSvg(svg); // looks something like <svg> xxxxxxxxxxx </svg> }; return ( <header className={styles.header}> // display svg here <svg>{ticketSvg}</svg> </header> ); };
请问该如何实现需求?(可使用或不使用SVGR)
解决方案
方法1:使用dangerouslySetInnerHTML(无需SVGR)
这是最直接的实现方式,直接将SVG字符串作为HTML内容插入DOM。注意:必须确保API返回的SVG内容完全可信,避免XSS攻击风险。
修改后的组件代码:
import { useState, useEffect } from 'react'; import styles from './TicketDetail.module.css'; // 假设使用CSS Modules const TicketDetail = () => { const [ticketSvg, setTicketSvg] = useState(""); const getTicketSVG = async () => { // 替换为实际的API请求逻辑 const response = await fetch('/your-api-endpoint'); const svgContent = await response.text(); setTicketSvg(svgContent); }; // 组件挂载时触发SVG获取 useEffect(() => { getTicketSVG(); }, []); return ( <header className={styles.header}> {/* 只有当SVG内容存在时才渲染 */} {ticketSvg && ( <div dangerouslySetInnerHTML={{ __html: ticketSvg }} /> )} </header> ); }; export default TicketDetail;
方法2:利用SVGR将SVG字符串转为React组件
如果你已经安装了vite-plugin-svgr,可以结合@svgr/core将SVG字符串直接转换为React组件,适合需要对SVG做组件化控制的场景。
- 先安装依赖:
npm install @svgr/core --save-dev
- 修改组件代码:
import { useState, useEffect } from 'react'; import { transform } from '@svgr/core'; import styles from './TicketDetail.module.css'; const TicketDetail = () => { const [TicketComponent, setTicketComponent] = useState(null); const getTicketSVG = async () => { const response = await fetch('/your-api-endpoint'); const svgString = await response.text(); // 将SVG字符串转换为React组件代码 const componentCode = await transform( svgString, {}, { componentName: 'Ticket' } ); // 动态创建组件(仅在SVG内容可信时使用) const Component = eval(`(function(){return ${componentCode}})()`); setTicketComponent(<Component />); }; useEffect(() => { getTicketSVG(); }, []); return ( <header className={styles.header}> {TicketComponent} </header> ); }; export default TicketDetail;
⚠️ 注意:eval存在安全风险,仅当你完全信任API返回的SVG内容时使用。
方法3:更安全的SVGR转换方式(避免eval)
如果不想用eval,可以结合Babel和SVGR的JSX插件来转换,安全性更高:
- 安装所需依赖:
npm install @svgr/core @svgr/plugin-jsx @babel/core @babel/preset-react --save-dev
- 组件代码:
import { useState, useEffect } from 'react'; import { transform } from '@svgr/core'; import jsxPlugin from '@svgr/plugin-jsx'; import { transformSync } from '@babel/core'; import styles from './TicketDetail.module.css'; const TicketDetail = () => { const [TicketComponent, setTicketComponent] = useState(null); const getTicketSVG = async () => { const response = await fetch('/your-api-endpoint'); const svgString = await response.text(); // 将SVG转为JSX代码 const jsxCode = await transform( svgString, { plugins: [jsxPlugin] }, { componentName: 'Ticket' } ); // 用Babel将JSX转为可执行的React组件代码 const { code } = transformSync(jsxCode, { presets: ['@babel/preset-react'], }); // 通过动态导入创建组件 const module = await import(`data:text/javascript,${code}`); setTicketComponent(<module.default />); }; useEffect(() => { getTicketSVG(); }, []); return ( <header className={styles.header}> {TicketComponent} </header> ); }; export default TicketDetail;
内容的提问来源于stack exchange,提问作者Bram
相关产品推荐
相关产品推荐

