- 原文地址:How to use SVG Icons as React Components?
- 原文做者:ROBIN WIERUCH
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:TiaossuP
- 校對者:vitoxli、ZavierTang
我一直在 React 應用中使用 SVG 感到困惑。我在網上看到了不少在 React 中使用 SVG 的方案,但真正實踐起來時,幾乎都沒成功過。如今我要介紹一個很是簡單易行的方式來解決這個問題。前端
提示:這篇文章中的圖標均來自 Flaticon。若是你用了這個網站的圖標,別忘了向做者 / 平臺表示感謝!node
你能夠在你的 React 項目中建立一個專門用來存放 .svg 圖標文件的文件夾。以此,你能夠手動/自動生成 React 組件。接下來的兩個章節,我將展現兩種方式,一種是使用命令行與 npm 命令手動建立圖標組件,另外一種是使用 webpack 自動建立 React 組件。這兩種方式都使用了一個名爲 SVGR 的工具(這個工具也被 create-react-app 等工具普遍使用)。react
在這個章節,咱們從手動建立 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
命令。
每次更新 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 命令了。
若是你使用 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 的輕量級替代方案。
在我最近一次開發 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 添加 / 替換 / 刪除屬性。
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 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。