如何动态导入SVG并内联渲染

如何动态导入SVG并内联渲染

本文介绍了如何动态导入SVG并内联渲染的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个函数,它接受一些参数并呈现一个 SVG.我想根据传递给函数的名称动态导入该 svg.它看起来像这样:

从'react'导入React;导出默认异步 ({name, size = 16, color = '#000'}) =>{const Icon = await import(/* webpackMode: "eager" */`./icons/${name}.svg`);return <Icon width={size} height={size} fill={color}/>;};

根据

打字稿中的动态 SVG 导入钩子示例:

接口 UseDynamicSVGImportOptions {已完成?:(名称:字符串,SvgIcon:React.FC>|不明确的) =>空白;onError?: (err: Error) =>空白;}函数 useDynamicSVGImport(名称:字符串,选项:UseDynamicSVGImportOptions = {}){const ImportedIconRef = useRef>();const [loading, setLoading] = useState(false);const [error, setError] = useState();const { onCompleted, onError } = 选项;useEffect(() => {设置加载(真);const importIcon = async(): Promise=>{尝试 {ImportedIconRef.current = (等待导入(`./${name}.svg`)).反应组件;onCompleted?.(name, ImportedIconRef.current);} 抓住(错误){onError?.(错误);设置错误(错误);} 最后 {设置加载(假);}};导入图标();}, [名称, onCompleted, onError]);返回 { 错误,加载,SvgIcon: ImportedIconRef.current };}

对于那些在动态导入 SVG 时为 ReactComponent 获取 undefined 的人来说,这是由于 Webpack 插件添加了 ReactComponent 以某种方式导入的每个 SVG 不会在动态导入时触发.

基于这个解决方案,我们可以临时通过在动态 SVG 导入中强制使用相同的加载器来解决它.

唯一的区别是 ReactComponent 现在是 default 输出.

ImportedIconRef.current = (await import(`!!@svgr/webpack?-svgo,+titleProp,+ref!./${name}.svg`)).默认;


另请注意,使用带有可变部分的动态导入时存在限制.这个 SO 答案 详细解释了这个问题.

要解决此问题,您可以使动态导入路径更加明确.

例如,代替

//App.js<图标路径=../../icons/icon.svg";/>//图标.jsx...导入(路径);...

您可以将其更改为

//App.js<图标名称=图标"/>//图标.jsx...导入(`../../icons/${name}.svg`);...

I have a function that takes some arguments and renders an SVG. I want to dynamically import that svg based on the name passed to the function. It looks like this:

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} />;
};

According to the webpack documentation for dynamic imports and the magic comment "eager":

This is what my Icon is resolved to:

> Module
default: "static/media/antenna.11b95602.svg"
__esModule: true
Symbol(Symbol.toStringTag): "Module"

Trying to render it the way my function is trying to gives me this error:

Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.

I don't understand how to use this imported Module to render it as a component, or is it even possible this way?

解决方案

You can make use of ref and ReactComponent named export when importing SVG file. Note that it has to be ref in order for it to work.

The following examples make use of React hooks which require version v16.8 and above.

Sample Dynamic SVG Import hook:

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 };
}

Sample Dynamic SVG Import hook in typescript:

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 };
}

For those who are getting undefined for ReactComponent when the SVG is dynamically imported, it is due to a bug where the Webpack plugin that adds the ReactComponent to each SVG that is imported somehow does not trigger on dynamic imports.

Based on this solution, we can temporary resolve it by enforcing the same loader on your dynamic SVG import.

The only difference is that the ReactComponent is now the default output.

ImportedIconRef.current = (await import(`!!@svgr/webpack?-svgo,+titleProp,+ref!./${name}.svg`)).default;


Also note that there’s limitation when using dynamic imports with variable parts. This SO answer explained the issue in detail.

To workaround with this, you can make the dynamic import path to be more explicit.

E.g, Instead of

// App.js
<Icon path="../../icons/icon.svg" />

// Icon.jsx
...
import(path);
...

You can change it to

// App.js
<Icon name="icon" />

// Icon.jsx
...
import(`../../icons/${name}.svg`);
...

这篇关于如何动态导入SVG并内联渲染的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-16 00:17