[譯] 如何將 SVG 圖標用作 React 組件?

如何將 SVG 圖標用作 React 組件?

react svg icons, react svgr

我一直在 React 應用中使用 SVG 感到困惑。我在網上看到了不少在 React 中使用 SVG 的方案,但真正實踐起來時,幾乎都沒成功過。如今我要介紹一個很是簡單易行的方式來解決這個問題。前端

提示:這篇文章中的圖標均來自 Flaticon。若是你用了這個網站的圖標,別忘了向做者 / 平臺表示感謝!node

你能夠在你的 React 項目中建立一個專門用來存放 .svg 圖標文件的文件夾。以此,你能夠手動/自動生成 React 組件。接下來的兩個章節,我將展現兩種方式,一種是使用命令行與 npm 命令手動建立圖標組件,另外一種是使用 webpack 自動建立 React 組件。這兩種方式都使用了一個名爲 SVGR 的工具(這個工具也被 create-react-app 等工具普遍使用)。react

經過命令行建立 React SVG 圖標組件

在這個章節,咱們從手動建立 SVG 圖標開始。若是你須要本文的 starter 項目,能夠戳 webpack + Babel + React 項目並參考其安裝說明。android

接下來,在你的 src/ 目錄下創建一個 /assets 目錄,把你的全部 .svg 圖標全都放在裏面。咱們並不但願把資源文件與源碼弄混,由於咱們接下來會基於這些資源文件生成 JavaScript 文件。這些手動生成的 JavaScript 文件 —— 即 React 圖標組件,會跟你的其餘源碼混在一塊兒。webpack

assets/
-- twitter.svg
-- facebook.svg
-- github.svg
src/
-- index.js
-- App.js
複製代碼

如今,爲你的 React 圖標組件建立一個 src/Icons/文件夾:ios

assets/
-- twitter.svg
-- facebook.svg
-- github.svg
src/
-- index.js
-- App.js
-- Icons/
複製代碼

咱們但願最終生成的 React 圖標組件在 src/App.js 中使用:git

import React from 'react';
import TwitterIcon from './Icons/Twitter.js';
import FacebookIcon from './Icons/Facebook.js';
import GithubIcon from './Icons/Github.js';
const App = () => (
  <div>
    <ul>
      <li>
        <TwitterIcon width="40px" height="40px" />
        <a href="https://twitter.com/rwieruch">Twitter</a>
      </li>
      <li>
        <FacebookIcon width="40px" height="40px" />
        <a href="https://www.facebook.com/rwieruch/">Facebook</a>
      </li>
      <li>
        <GithubIcon width="40px" height="40px" />
        <a href="https://github.com/rwieruch">Github</a>
      </li>
    </ul>
  </div>
);
export default App;
複製代碼

不過,目前並無產生任何效果,由於如今 src/Icons 目錄是空的,裏面沒有任何圖標組件。下一步,咱們將以 assets 文件夾爲源文件夾,src/Icons爲目標文件夾,經過在 package.json 中添加一個新的 npm 腳本,來生成 React 圖標組件。github

{
  ...
  "main": "index.js",
  "scripts": {
    "svgr": "svgr -d src/Icons/ assets/",
    "start": "webpack-dev-server --config ./webpack.config.js --mode development"
  },
  "keywords": [],
  ...
}
複製代碼

最後,使用命令行安裝 SVGR CLI 包。web

npm install @svgr/cli --save-dev
複製代碼

如今,已經萬事俱備,你能夠在命令行中輸入 npm run svgr 來執行你的新 npm 命令。你能夠在命令行的輸出中,看到根據 SVG 文件生成的 JavaScript 文件。在命令執行完成後,你就能夠啓動應用,在頁面中看到被渲染爲 React 組件的 SVG 圖標。你能夠在 src/Icons 目錄中看到全部生成的 React 圖標組件。這些組件一樣能夠把 props 做爲參數,這意味着你能夠自行定義圖標的寬高。npm

這就是從 SVG 生成 React 組件的完整步驟,每次你加入了一個新 SVG 文件或者修改了原有的 SVG 文件時,你都須要再次運行 npm run svgr 命令。

使用 webpack 生成 React SVG 圖標組件

每次更新 SVG 文件,都手動執行命令並非一個最佳方案。你能夠將其集成到 webpack 配置中。如今你能夠把你的 src/Icons 文件夾清空,把 assets/ 文件夾中的 SVG 文件移動到 src/Icons 下,並刪掉 assets/ 文件夾了。如今你的目錄結構將以下所示:

src/
-- index.js
-- App.js
-- Icons/
---- twitter.svg
---- facebook.svg
---- github.svg
複製代碼

如今你能夠直接在 App 組件中直接引用 SVG 文件。

import React from 'react';
import TwitterIcon from './Icons/Twitter.svg';
import FacebookIcon from './Icons/Facebook.svg';
import GithubIcon from './Icons/Github.svg';
const App = () => (
  <div>
    <ul>
      <li>
        <TwitterIcon width="40px" height="40px" />
        <a href="https://twitter.com/rwieruch">Twitter</a>
      </li>
      <li>
        <FacebookIcon width="40px" height="40px" />
        <a href="https://www.facebook.com/rwieruch/">Facebook</a>
      </li>
      <li>
        <GithubIcon width="40px" height="40px" />
        <a href="https://github.com/rwieruch">Github</a>
      </li>
    </ul>
  </div>
);
export default App;
複製代碼

如今啓動應用會失敗,引入 SVG 文件顯然沒有這麼簡單。不過,咱們可讓 webpack 在每次啓動應用時,都自動引入正確的 SVG 文件。參考下面的代碼,修改你的 webpack.config.js 文件。

const webpack = require('webpack');
module.exports = {
  entry: './src/index.js',
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
      {
        test: /\.svg$/,
        use: ['@svgr/webpack'],
      },
    ],
  },
  ...
};
複製代碼

而後,爲 SVGR 安裝必要的 webpack 包

npm install @svgr/webpack --save-dev
複製代碼

一旦應用啓動,webpack 就會自動工做,你就不再須要糾結於 SVG 文件了。你能夠把你的 SVG 文件放在 src/ 目錄的任意位置,並被任意 React 組件所引用。如今,咱們也再也不須要 package.json 文件裏面的 SVGR 命令了。

另外一個可選方案:react-svg-loader

若是你使用 webpack,你可使用一些更簡單的 SVG loader 來代替 SVGR。譬如,能夠在你的 webpack 配置中使用 react-svg-loader 來代替 SVGR:

const webpack = require('webpack');
module.exports = {
  entry: './src/index.js',
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
      {
        loader: 'react-svg-loader',
        options: {
          jsx: true // true outputs JSX tags
        }
      }
    ],
  },
  ...
};
複製代碼

固然你須要先安裝它:

npm install react-svg-loader --save-dev
複製代碼

而後,你依然能夠用與 SVGR 同樣的方式來引入並使用你的 SVG 文件。這至關於一個 SVGR 的輕量級替代方案。

使用 SVGR 模板來作一些高級應用

在我最近一次開發 React 應用時,我遇到了一些有問題的 SVG 圖標,這些圖標的 viewBox 屬性並不完整。因爲這個屬性是爲 SVG 圖標提供大小所必需的,因此只要這個屬性沒有出如今圖標中,我就必須找到一種方法來引入這個屬性。原本,我能夠遍歷每一個 SVG 圖標來修復這個問題,可是,處理 500 多個圖標並非一項輕鬆的任務。接下來我將介紹如何用 SVGR 模板功能來解決這個問題。

webpack.config.js 文件中的 SVGR 默認模板看起來像是下面這樣:

...
module.exports = {
  entry: './src/index.js',
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
      {
        test: /\.svg$/,
        use: [
          {
            loader: '@svgr/webpack',
            options: {
              template: (
                { template },
                opts,
                { imports, componentName, props, jsx, exports }
              ) => template.ast` ${imports} const ${componentName} = (${props}) => { return ${jsx}; }; export default ${componentName}; `,
            },
          },
        ],
      },
    ],
  },
  ...
};
複製代碼

經過使用這個模板,你能夠更改 SVG 文件生成的代碼。假設咱們想要用藍色填充全部的圖標。則只須要用 fill 屬性擴展 props 對象:

...
module.exports = {
  entry: './src/index.js',
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: ['babel-loader'],
      },
      {
        test: /\.svg$/,
        use: [
          {
            loader: '@svgr/webpack',
            options: {
              template: (
                { template },
                opts,
                { imports, componentName, props, jsx, exports }
              ) => template.ast` ${imports} const ${componentName} = (${props}) => { props = { ...props, fill: 'blue' }; return ${jsx}; }; export default ${componentName}; `,
            },
          },
        ],
      },
    ],
  },
  ...
};
複製代碼

這就能夠給全部的圖標都增長 fill: blue 屬性了。 SVGR 自己已經提供了相似的簡單用例。只需查看他們的文檔,就能夠了解如何給 SVG 添加 / 替換 / 刪除屬性。

使用 SVGR 來自定義 viewBox 屬性

在咱們的例子中,咱們但願爲每一個不存在 viewBox 屬性的 SVG 圖標都添加該屬性。首先,咱們把一個正常的 SVG 中的 viewBox 屬性刪除,如今它就確定沒法正常顯示了。確認了問題已經出現後,咱們嘗試使用上面介紹的 SVGR 模板和一個額外的 React Hook 來修復此問題:

import React from 'react';
const useWithViewbox = ref => {
  React.useLayoutEffect(() => {
    if (
      ref.current !== null &&
      // 當沒有 viewBox 屬性時
      !ref.current.getAttribute('viewBox') &&
      // 當 jsdom 沒 bug 時
      // https://github.com/jsdom/jsdom/issues/1423
      ref.current.getBBox &&
      // 當其已經被渲染
      // https://stackoverflow.com/questions/45184101/error-ns-error-failure-in-firefox-while-use-getbbox
      ref.current.getBBox().width &&
      ref.current.getBBox().height
    ) {
      const box = ref.current.getBBox();
      ref.current.setAttribute(
        'viewBox',
        [box.x, box.y, box.width, box.height].join(' ')
      );
    }
  });
};
export default useWithViewbox;
複製代碼

React hook 須要這個 SVG 組件的引用(ref)來爲其設置 viewBox 屬性。如今 viewBox 屬性將根據渲染出的圖標來計算。若是圖標還沒有被渲染,或者已經存在 viewBox 屬性時,咱們就不會作任何事情。

這個 hook 應該放在離 src/Icons 目錄不遠的地方。

src/
-- index.js
-- App.js
-- useWithViewbox.js
-- Icons/
---- twitter.svg
---- facebook.svg
---- github.svg
複製代碼

如今,咱們能夠在 webpack.config.js 文件中爲 SVG 模板添加 hook:

...
module.exports = {
  entry: './src/index.js',
  module: {
    rules: [
      ...
      {
        test: /\.svg$/,
        use: [
          {
            loader: '@svgr/webpack',
            options: {
              template: (
                { template },
                opts,
                { imports, componentName, props, jsx, exports }
              ) => template.ast` ${imports} import useWithViewbox from '../useWithViewbox'; const ${componentName} = (${props}) => { const ref = React.createRef(); useWithViewbox(ref); props = { ...props, ref }; return ${jsx}; }; export default ${componentName}; `,
            },
          },
        ],
      },
    ],
  },
  ...
};
複製代碼

有了這個功能,SVGR的模板功能將向每一個生成的圖標組件添加 自定義 hook 。這個 hook 只在沒有 viewBox 屬性的圖標組件中執行。此時再次運行應用,就會發現全部圖標組件都能正確顯示了 —— 即便你可能已經從某個組件中刪除了 viewBox 屬性。


最後,我但願經過這篇文章,你能夠學到在命令行 / npm 命令或 webpack 中使用 SVGR 來在 React 中引入 SVG 圖標的方法。使用 webpack 實現的最終版 React 應用能夠在這個 GitHub repo 中找到。若是你遇到任何 bug,請在評論中告訴我。我也很樂於得知一些與「viewBox屬性丟失」相似的問題。請在評論中告訴我這些狀況。

若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索