簡體   English   中英

如何動態導入 SVG 並內聯渲染

[英]How to dynamically import SVG and render it inline

我有一個 function 需要一些 arguments 並呈現 SVG。 我想根據傳遞給 function 的名稱動態導入 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 文檔和魔術評論“eager”:

“不生成額外的塊。所有模塊都包含在當前塊中,並且不會發出額外的網絡請求。Promise 仍然返回但已解決。與 static 導入相比,模塊在調用導入之前不會執行() 已制成。”

這就是我的圖標解決的問題:

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

試圖以我的 function 試圖給我這個錯誤的方式呈現它:

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

我不明白如何使用這個導入的模塊將其渲染為組件,或者甚至可以這樣嗎?

導入 SVG 文件時,可以使用ref和名為 export 的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 };
}

編輯 react-dynamic-svg-import

typescript 中的動態 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 };
}

編輯 react-dynamic-svg-import-ts


對於那些在動態導入ReactComponentundefined ReactComponent 的人,這是由於 Webpack 插件將ReactComponent添加到每個以某種方式導入的 ZCD15A75C26008693647B31A3F0DE 的錯誤不會觸發動態導入。

基於此解決方案,我們可以通過在您的動態 SVG 導入上強制執行相同的加載程序來臨時解決它。

唯一的區別是ReactComponent現在是default的 output。

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

另請注意,使用帶有可變部分的動態導入時存在限制。 這個 SO answer詳細解釋了這個問題。

要解決此問題,您可以使動態導入路徑更加明確。

例如,而不是

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

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

您可以將其更改為

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

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

您的渲染函數(對於 class 組件)和 function 組件不應是異步的(因為它們必須返回 DOMNode 或 Z37A6259CC0C1DAE299A7866489DFFise0BDZ - 在您的情況下,它們返回 Prom。 相反,您可以以常規方式渲染它們,然后導入圖標並在下一次渲染中使用它。 嘗試以下操作:

const Test = () => {
  let [icon, setIcon] = useState('');

  useEffect(async () => {
    let importedIcon = await import('your_path');
    setIcon(importedIcon.default);
  }, []);

  return <img alt='' src={ icon }/>;
};

我根據答案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} />

接受的解決方案不再適用於最新版本的 CRA(反應腳本 5):此處的問題示例: https://github.com/mattpodolak/webpack-svg

我以文本形式動態獲取 SVG 文件,然后將 SVG 放入 div dangerouslySetInnerHTML中。

  const Icon = ({ className, name, size = 16 }: IconProps) => {
  const [Icon, setIcon] = React.useState("");

  React.useEffect(() => {
    fetch(`/icons/${name}.svg`)
      .then((res) => res.text())
      .then((res) => {
        if (res.startsWith("<svg")) return setIcon(res);
        console.error(
          `Icon: "${name}.svg" not found in ${process.env.PUBLIC_URL}/icons`
        );
        return setIcon("");
      });
  }, [name]);

  if (!Icon) return null;

  return (
    <div
      className={classNames("icon", className)}
      style={{ width: !size ? "100%" : size + "px", height: "100%" }}
      dangerouslySetInnerHTML={{ __html: Icon }}
    />
  );
};

在 Codesandbox 上預覽

您可以通過將填充值設置為“currentColor”來自動更改 svg 的顏色。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM