如何动态导入 SVG 并内联渲染
Posted
技术标签:
【中文标题】如何动态导入 SVG 并内联渲染【英文标题】:How to dynamically import SVG and render it inline 【发布时间】:2020-08-03 22:43:49 【问题描述】:我有一个接受一些参数并呈现 SVG 的函数。我想根据传递给函数的名称动态导入该 svg。它看起来像这样:
import React from 'react';
export default async (name, size = 16, color = '#000') =>
const Icon = await import(/* webpackMode: "eager" */ `./icons/$name.svg`);
return <Icon width=size height=size fill=color />;
;
根据webpack documentation for dynamic imports和神奇的评论“eager”:
"不生成额外的块。所有模块都包含在当前 块并且不发出额外的网络请求。承诺还在 返回但已经解决。与静态导入相比, 在调用 import() 之前,模块不会执行。"
这就是我的图标被解析为:
> Module
default: "static/media/antenna.11b95602.svg"
__esModule: true
Symbol(Symbol.toStringTag): "Module"
尝试以我的函数尝试的方式呈现它给我这个错误:
Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
我不明白如何使用这个导入的 Module 将其渲染为组件,或者甚至可以这样吗?
【问题讨论】:
如果您静态导入,您的 svg 是否正确显示? 是的!如果我做一个普通的import MyIcon from './icons/myicon.svg'
,我可以像<MyIcon />
一样渲染它。
您可能不得不将解析后的 SVG 存储在一个状态中。
@dev_junwen 正确,但将其存储在 state 中仍然无法让我将其呈现为内联 svg。
或者另一种相当“动态”的方式是您可以定义名称到 SVG 组件的映射,然后使用括号符号语法 iconMap[name]
检索正确的 SVG。我还没有测试它,但我认为这可以工作。在这种情况下,您需要导入所有 SVG 并将其分配给地图。
【参考方案1】:
动态加载 svg 的一种解决方案是使用 require 将其加载到 img 中,例如:
<img src=require(`../assets/$logoNameVariable`)?.default />
【讨论】:
【参考方案2】:导入 SVG 文件时可以使用ref
和ReactComponent
命名导出。请注意,它必须是 ref
才能正常工作。
以下示例使用需要 v16.8
及更高版本的 React 钩子。
动态 SVG 导入钩子示例:
function useDynamicSVGImport(name, options = )
const ImportedIconRef = useRef();
const [loading, setLoading] = useState(false);
const [error, setError] = useState();
const onCompleted, onError = options;
useEffect(() =>
setLoading(true);
const importIcon = async () =>
try
ImportedIconRef.current = (
await import(`./$name.svg`)
).ReactComponent;
if (onCompleted)
onCompleted(name, ImportedIconRef.current);
catch (err)
if (onError)
onError(err);
setError(err);
finally
setLoading(false);
;
importIcon();
, [name, onCompleted, onError]);
return error, loading, SvgIcon: ImportedIconRef.current ;
打字稿中的示例动态 SVG 导入钩子:
interface UseDynamicSVGImportOptions
onCompleted?: (
name: string,
SvgIcon: React.FC<React.SVGProps<SVGSVGElement>> | undefined
) => void;
onError?: (err: Error) => void;
function useDynamicSVGImport(
name: string,
options: UseDynamicSVGImportOptions =
)
const ImportedIconRef = useRef<React.FC<React.SVGProps<SVGSVGElement>>>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error>();
const onCompleted, onError = options;
useEffect(() =>
setLoading(true);
const importIcon = async (): Promise<void> =>
try
ImportedIconRef.current = (
await import(`./$name.svg`)
).ReactComponent;
onCompleted?.(name, ImportedIconRef.current);
catch (err)
onError?.(err);
setError(err);
finally
setLoading(false);
;
importIcon();
, [name, onCompleted, onError]);
return error, loading, SvgIcon: ImportedIconRef.current ;
对于那些在动态导入 SVG 时获得 undefined
和 ReactComponent
的用户,这是由于 Webpack 插件将 ReactComponent
添加到以某种方式导入的每个 SVG 的错误不会触发动态进口。
基于此solution,我们可以通过在您的动态 SVG 导入中强制使用相同的加载器来临时解决它。
唯一的区别是ReactComponent
现在是default
输出。
ImportedIconRef.current = (await import(`!!@svgr/webpack?-svgo,+titleProp,+ref!./$name.svg`)).default;
另请注意,使用带有可变部分的动态导入时存在限制。 This SO answer详细解释了这个问题。
要解决此问题,您可以使动态导入路径更加明确。
例如,代替
// App.js
<Icon path="../../icons/icon.svg" />
// Icon.jsx
...
import(path);
...
你可以改成
// App.js
<Icon name="icon" />
// Icon.jsx
...
import(`../../icons/$name.svg`);
...
【讨论】:
哇!谢谢,很干净!我完全错过了任何文档中的 ReactComponent。我认为它会在检查时显示为导入对象的公共方法,恕我直言,但我想这不是这里的工作方式。感谢您的帮助! 顺便说一句 @dev_junwen ReactComponent 从来没有为我工作过,我不知道这段代码在你的 CodeSandbox 中是如何工作的,但.ReactComponent
只是为我返回 undefined。我不得不更改它并使用.default
,如下面的@Enchew 示例所示。如果您转到“导入默认值”,我在这里找到了一些很棒的文档:developer.mozilla.org/en-US/docs/Web/javascript/Reference/…
@Willege 我已经更新了答案。请随意查看代码示例。
@dev_junwen 不幸的是,这个解决方案对我不起作用。我什么都试过了。即使我从 CodeSandbox 下载代码并使用 npm install
从头安装所有依赖项,也不会加载 SVG 图像。我也没有收到错误消息。我做错了什么(可能是 webpack、babel 或 typescript 配置)?
@mamiu 你在使用 Create React App 吗?如果没有,您将需要手动设置内联 svg 加载器。 This 回答可能会有所帮助。【参考方案3】:
我根据答案https://github.com/facebook/create-react-app/issues/5276#issuecomment-665628393进行了更改
export const Icon: FC<IconProps> = ( name, ...rest ): JSX.Element | null =>
const ImportedIconRef = useRef<FC<SVGProps<SVGSVGElement>> | any>();
const [loading, setLoading] = React.useState(false);
useEffect((): void =>
setLoading(true);
const importIcon = async (): Promise<void> =>
try
// Changing this line works fine to me
ImportedIconRef.current = (await import(`!!@svgr/webpack?-svgo,+titleProp,+ref!./$name.svg`)).default;
catch (err)
throw err;
finally
setLoading(false);
;
importIcon();
, [name]);
if (!loading && ImportedIconRef.current)
const current: ImportedIcon = ImportedIconRef;
return <ImportedIcon ...rest />;
return null;
;
【讨论】:
【参考方案4】:您的渲染函数(用于类组件)和函数组件不应是异步的(因为它们必须返回 DOMNode 或 null - 在您的情况下,它们返回一个 Promise)。相反,您可以以常规方式渲染它们,然后导入图标并在下一次渲染中使用它。请尝试以下操作:
const Test = () =>
let [icon, setIcon] = useState('');
useEffect(async () =>
let importedIcon = await import('your_path');
setIcon(importedIcon.default);
, []);
return <img alt='' src= icon />;
;
【讨论】:
这可能适用于 img 标签,其中源将被导入Icon.default,正如你所写,但它不适用于内联 svg,我想将其呈现为 。我用异步useEffect尝试了你的方法,但是我需要setIcon
整个模块,而不是importedIcon.default(文件路径),然后像Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
为什么需要将其渲染为 <Icon>
?这种方法给你什么,有什么不同,使用<img>
是无法克服的
如果是 img 标签,我将如何更改 svg 的属性,例如图标的填充颜色?是否可以使用 img 标签?
Effects 不能承载 async
函数。它们返回一个作为清理函数调用的承诺。 robinwieruch.de/react-hooks-fetch-data
不管最初的问题是什么,这可能是“动态导入本地文件”的最佳答案。当我有一个文件 URL 列表并动态加载时,这很有效。以上是关于如何动态导入 SVG 并内联渲染的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Python 中将 SVG 图像渲染为 PNG 文件?