- 原文地址: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、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。