如何动态导入 SVG 并内联渲染
How to dynamically import SVG and render it inline
我有一个接受一些参数并呈现 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”:
"Generates no extra chunk. All modules are included in the current
chunk and no additional network requests are made. A Promise is still
returned but is already resolved. In contrast to a static import, the
module isn't executed until the call to import() is made."
这是我的图标解析为:
> 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.
我不明白如何使用这个导入的模块将其渲染为一个组件,或者甚至可以这样吗?
您的渲染函数(对于 class 组件)和函数组件不应是异步的(因为它们必须 return DOMNode 或 null - 在您的情况下,它们 return 一个 Promise) .相反,您可以以常规方式渲染它们,然后导入图标并在下一次渲染中使用它。请尝试以下操作:
const Test = () => {
let [icon, setIcon] = useState('');
useEffect(async () => {
let importedIcon = await import('your_path');
setIcon(importedIcon.default);
}, []);
return <img alt='' src={ icon }/>;
};
您可以在导入 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 时为 ReactComponent
获得 undefined
的人,这是由于一个错误,其中将 ReactComponent
添加到每个 SVG 的 Webpack 插件以某种方式导入不会触发动态导入。
基于此 solution,我们可以通过在您的动态 SVG 导入上强制执行相同的加载程序来临时解决它。
唯一的区别是 ReactComponent
现在是 default
输出。
ImportedIconRef.current = (await import(`!!@svgr/webpack?-svgo,+titleProp,+ref!./${name}.svg`)).default;
另请注意,使用带有可变部分的动态导入时存在限制。 详细解释了问题。
要解决此问题,您可以使动态导入路径更加明确。
例如,而不是
// App.js
<Icon path="../../icons/icon.svg" />
// Icon.jsx
...
import(path);
...
你可以改成
// App.js
<Icon name="icon" />
// Icon.jsx
...
import(`../../icons/${name}.svg`);
...
我根据答案做了修改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;
};
动态加载 svg 的一种解决方案是使用 require 在 img 中加载它,示例:
<img src={require(`../assets/${logoNameVariable}`)?.default} />
我有一个接受一些参数并呈现 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”:
"Generates no extra chunk. All modules are included in the current chunk and no additional network requests are made. A Promise is still returned but is already resolved. In contrast to a static import, the module isn't executed until the call to import() is made."
这是我的图标解析为:
> 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.
我不明白如何使用这个导入的模块将其渲染为一个组件,或者甚至可以这样吗?
您的渲染函数(对于 class 组件)和函数组件不应是异步的(因为它们必须 return DOMNode 或 null - 在您的情况下,它们 return 一个 Promise) .相反,您可以以常规方式渲染它们,然后导入图标并在下一次渲染中使用它。请尝试以下操作:
const Test = () => {
let [icon, setIcon] = useState('');
useEffect(async () => {
let importedIcon = await import('your_path');
setIcon(importedIcon.default);
}, []);
return <img alt='' src={ icon }/>;
};
您可以在导入 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 时为 ReactComponent
获得 undefined
的人,这是由于一个错误,其中将 ReactComponent
添加到每个 SVG 的 Webpack 插件以某种方式导入不会触发动态导入。
基于此 solution,我们可以通过在您的动态 SVG 导入上强制执行相同的加载程序来临时解决它。
唯一的区别是 ReactComponent
现在是 default
输出。
ImportedIconRef.current = (await import(`!!@svgr/webpack?-svgo,+titleProp,+ref!./${name}.svg`)).default;
另请注意,使用带有可变部分的动态导入时存在限制。
要解决此问题,您可以使动态导入路径更加明确。
例如,而不是
// App.js
<Icon path="../../icons/icon.svg" />
// Icon.jsx
...
import(path);
...
你可以改成
// App.js
<Icon name="icon" />
// Icon.jsx
...
import(`../../icons/${name}.svg`);
...
我根据答案做了修改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;
};
动态加载 svg 的一种解决方案是使用 require 在 img 中加载它,示例:
<img src={require(`../assets/${logoNameVariable}`)?.default} />