现代 CSS

SVG在React中的运用

特别声明:如果您喜欢小站的内容,可以点击申请会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!或添加QQ:874472854(^_^)

在Web的世界当中,SVG并不是什么新的语言,只不过早年前SVG在Web的应用并不太广泛。不过近年来,它在Web的应用越来越广泛。最为常见的就是一些Icon图标,复杂一点是一些矢量图(比如不规则的图形),甚至还可以使用SVG的滤镜、动效等功能,实现一些复杂的UI效果。不过这些并不是今天要和大家聊的主题,今天想和大家聊的是 SVG在前端框架中的应用,比如React框架。

为了更好的向大家展示SVG在React的使用,接下来的示例都是在create-react-app来构建的工程。

在继续往下阅读之前,建议你对SVG有一定的了解,如果你在SVG这方面没有任何的基础,建议你花点时间阅读:

如何在React中引入SVG

如果你运行“create-react-app”构建的项目,其中React的Logo图就是一个SVG:

在此我们就可以提出第一个问题:如何在React项目中引入SVG? 在回答这个问题之前,我们先来回忆一下,在HTML中是如何引入SVG的。

在HTML中引入SVG主要方式有:

<!-- 1: 在img中引入.svg文件 -->
<img src="path/logo.svg" alt="React" />

<!-- 2: 在picture中引入.svg文件 -->
<picture>
    <source type="image/svg+xml" srcset="path/logo.svg" />
    <img src="path/fallback.png" alt="react" />
</picture>

<!-- 3: CSS的background-image中引入.svg文件 -->
.logo {
    background-image: url('path/logo.svg')
}

<!-- 4: HTML中内联(将SVG代码直接插入HTML) -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
    <g fill="#61DAFB">
        <path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
        <circle cx="420.9" cy="296.5" r="45.7"/>
        <path d="M520.5 78.1z"/>
    </g>
</svg>

<!-- 5: object标签中引入.svg文件 -->
<object type="image/svg+xml" data="path/logo.svg">
    <img src="path/logo.svg">
</object>

<!-- 6: embed标签中引入.svg文件 -->
<embed type="image/svg+xml" src="path/logo.svg" />

<!-- 7: iframe标签中引入.svg文件 -->
<iframe src="path/logo.svg"></iframe>

不过最为常见的还是四种,不过我个人更推荐将SVG代码内联到HTML中。

回到React的世界中来。在项目初始化的时候,在src/目录下就有一个logo.svg文件,你会发现在App.tsx文件中:

import logo from './logo.svg';

function App() {
    return (
        <div className="App">
        <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <!-- ... -->
        </header>
        </div>
    );
}

通过import.svg文件引入进来,并且运用到<img>src属性上:

<img src={logo} className="App-logo" alt="logo" />

在编译之后,你会发现上面的使用和在HTML中<img>引入.svg相同:

另外,在React框架上开发项目的话,使用Webpack加载器来加载SVG已经是非常成熟的技术,而且还可以直接将.svg文件作为组件引入到任何React模块中,并在构建时进行优化。比如上面的App.tsx修改为:

// App.tsx

import React from 'react';
// import logo from './logo.svg';
import { ReactComponent as ReactLogo } from './logo.svg';
import './App.css';

function App() {
    return (
        <div className="App">
            <header className="App-header">
                {/* <img src={logo} className="App-logo" alt="logo" /> */}
                <ReactLogo className="App-logo" />
            </header>
        </div>
    );
}

export default App;

编译出来的代码也不是<img>加载.svg文件的方式了,而是直接将SVG代码内联在页面的DOM中:

除此之外,我们还可以直接将SVG代码放到相应的组件模块中,不过要将SVG代转换成JSX代码

// App.tsx
function App() {
    return (
        <div className="App">
            <header className="App-header">
                {/* <img src={logo} className="App-logo" alt="logo" /> */}
                {/* <ReactLogo className="App-logo" /> */}
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3" className="App-logo">
                    <g fill="#61DAFB">
                        <path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
                        <circle cx="420.9" cy="296.5" r="45.7"/>
                        <path d="M520.5 78.1z"/>
                    </g>
                </svg>
            </header>
        </div>
    );
}

如果同一个模块中会有多个SVG引入的话,这种方式会让你的代码变得复杂,而且难维护。为了让代码更易于维护,也更易于管理,我们可以把相应的SVG文件当作一个React组件来使用。比如:

// src/ReactLogo.tsx
import React from 'react'

const ReactLogo = (props: any) => (
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3" {...props}>
        <g fill="#61DAFB">
            <path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
            <circle cx="420.9" cy="296.5" r="45.7"/>
            <path d="M520.5 78.1z"/>
        </g>
    </svg>
)

export default ReactLogo

然后在需要使用的地方引入该组件:

// App.tsx
import React from 'react';
import ReactLogo from './ReactLogo'
import './App.css';

function App() {
    return (
        <div className="App">
            <header className="App-header">
                <ReactLogo className="App-logo" />
            </header>
        </div>
    );
}

export default App;

最终得到的效果是一样的。但我们的代码要变得更干净,也更易于复用。

将SVG转换为React组件

前面提到过,在React中使用SVG时,更建议将SVG当作React的组件来使用。也就是说,将SVG转换成React组件。这样做的主要原因是:

  • SVG转换成React组件可以去掉一些多余的SVG代码
  • 不会增加额外的请求
  • 更容易地控制和维护SVG
  • 可以将冗余和晦涩的SVG转换为可定制的React组件,这些组件公开一个简单的声明式API

不过前面我们看到的示例,都是手动将SVG转换为React组件。但在React体系中,我们可以借助一些工程能力,自动化的将SVG转换为React组件。

SVG的使用链路

在使用SVG时,首先要获取相应的SVG文件或相应的SVG代码。一般获取代码主要有两种方式。从第三方平台获取SVG文件或代码,比如在iconfont.cn获取Icon:

对应的代码如下:

<svg t="1598182813208" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="877" width="200" height="200">
    <path d="M511.96 512.2m-433.5 0a433.5 433.5 0 1 0 867 0 433.5 433.5 0 1 0-867 0Z" fill="#666666" p-id="878"></path>
    <path d="M710.14 496.72L426.28 332.83c-11.92-6.88-26.82 1.72-26.82 15.49v327.77c0 13.77 14.9 22.37 26.82 15.49l283.86-163.89c11.92-6.88 11.92-24.09 0-30.97z" fill="#FFFFFF" p-id="879"></path>
</svg>

另外一种方式是从设计软件中获取SVG的代码,比如Sketch软件:

导出来SVG代码如下:

<?xml version="1.0" encoding="UTF-8"?>
<svg width="426px" height="325px" viewBox="0 0 426 325" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <title>编组</title>
    <g id="页面-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <g id="编组" transform="translate(16.000000, 16.000000)">
            <circle id="椭圆形" stroke="#979797" stroke-width="32" cx="103.5" cy="103.5" r="103.5"></circle>
            <path d="M200.750305,119.980981 L399.936148,234.980981 C409.502,240.503828 412.779504,252.735636 407.256656,262.301489 L386.256656,298.674556 C380.733809,308.240408 368.502,311.517911 358.936148,305.995064 L159.750305,190.995064 L200.750305,119.980981 Z" id="矩形" fill="#979797"></path>
        </g>
    </g>
</svg>

相比较而言,从iconfont.cn平台上获取的SVG代码比较干净,但从Sketch设计软件中获取的代码有过多无用的代码。不过我们可以使用SVGOMG工具对导出来的SVG代码进行优化:

结过SVGOMG工具优化之后的SVG代码要干净的多:

<svg xmlns="http://www.w3.org/2000/svg" width="426" height="325" viewBox="0 0 426 325">
    <g fill="none" fill-rule="evenodd" transform="translate(16 16)">
        <circle cx="103.5" cy="103.5" r="103.5" stroke="#979797" stroke-width="32"/>
        <path fill="#979797" d="M200.750305,119.980981 L399.936148,234.980981 C409.502,240.503828 412.779504,252.735636 407.256656,262.301489 L386.256656,298.674556 C380.733809,308.240408 368.502,311.517911 358.936148,305.995064 L159.750305,190.995064 L200.750305,119.980981 Z"/>
    </g>
</svg>

不过,我们直接将上面的SVG代码放到React中使用的话,是会报错的:

也就是说,我们还需要将SVG代码转换成JSX的语法格式。同样的,可以使用在线工具,比如SVG2JSX来做转换操作:

<svg
    xmlns="http://www.w3.org/2000/svg"
    width="426"
    height="325"
    viewBox="0 0 426 325"
    >
    <g fill="none" fillRule="evenodd" transform="translate(16 16)">
        <circle
        cx="103.5"
        cy="103.5"
        r="103.5"
        stroke="#979797"
        strokeWidth="32"
        ></circle>
        <path
        fill="#979797"
        d="M200.75 119.981l199.186 115c9.566 5.523 12.844 17.755 7.32 27.32l-21 36.374c-5.522 9.565-17.754 12.843-27.32 7.32l-199.186-115 41-71.014z"
        ></path>
    </g>
</svg>

不过我更推荐Iconset这款APP

这并还没有结束,还需要创建一个React组件:

// SearchIcon.tsx
import React from 'react'

const SearchIcon = (props: any) => (
    <svg className="icon" height="200" width="200" viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg">
        <defs/>
        <path d="M953.504 908.256l-152.608-163.296c61.856-74.496 95.872-167.36 95.872-265.12 0-229.344-186.624-415.968-416.032-415.968-229.344 0-415.968 186.592-415.968 415.968s186.624 415.968 416 415.968c60.096-0.032 118.048-12.576 172.224-37.248 16.096-7.328 23.2-26.304 15.872-42.368-7.328-16.128-26.4-23.264-42.368-15.872-45.856 20.864-94.88 31.456-145.76 31.488-194.08 0-351.968-157.888-351.968-351.968 0-194.048 157.888-351.968 351.968-351.968 194.112 0 352.032 157.888 352.032 351.968 0 91.36-34.848 177.92-98.08 243.744-12.256 12.736-11.84 32.992 0.864 45.248 0.96 0.928 2.208 1.28 3.296 2.08 0.864 1.28 1.312 2.752 2.4 3.904l165.504 177.088c6.272 6.752 14.816 10.144 23.36 10.144 7.84 0 15.68-2.848 21.856-8.64C964.864 941.408 965.568 921.152 953.504 908.256z"/>
    </svg>
)

export default SearchIcon

// App.tsx
import React from 'react';
import SearchIcon from './SearchIcon'
import './App.css';

function App() {
    return (
        <div className="App">
            <header className="App-header">
                <SearchIcon />
            </header>
        </div>
    );
}

export default App;

用张图来描述整个操作流程:

自动化将SVG转React组件

接下来,通过工程链路来实现SVG转React组件,即自动化。这个时候就要拿出SVGR

SVGR可以获取外部的.svg文件并将其转换为React组件。SVGR针对不同的应用程序,提供了不同的解决方案:

  • @svgr/cli:CLI工具,提供了简单的命令,可以将单个文件或整个SVG文件目录转换为React组件(.js文件)
  • @svgr/core:这是一个Node应用程序,可以创建一个自定义脚本或基于SVGR构建另一个工具
  • @svgr/webpack:SVGR用作Webpack加载器,将SVG作为React组件直接导入,这也是使用最多的一种方案
  • @svgr/rollup:SVGR用作Rollup插件,直接将SVG作为React组件导入
  • @svgr/parcel-plugin-svgr:SVGR用作Parcel插件,直接将SVG作为React组件导入

接下来,我们来看看如何在项目中使用SVGR。

SVGR CLI

使用命令行的方式,即SVG CLI。 首行安装@svgr/cli:

⇒ npm i @svgr/cli

同时在工程的src目录下创建一个新的目录assets,并在assets目下创建一个icons目录,用来放置第三方平台下载下来的.svg

这个时候在命令终端执行:

⇒ npx @svgr/cli ./src/assets/icons/set.svg

会在命令终端生成SvgSet组件:

我们可以把这个SvgSet组件的代码复制到工程中:

// src/components
import * as React from "react";

function SvgSet(props) {
    return (
        <svg
        className="set_svg__icon"
        viewBox="0 0 1024 1024"
        width={200}
        height={200}
        {...props}
        >
            <defs>
                <style />
            </defs>
            <path
                d="M385.506 81.318A60.235 60.235 0 00325.27 69.27a530.673 530.673 0 00-60.236 31.623 405.685 405.685 0 00-54.211 31.624 77.704 77.704 0 00-19.878 63.247l5.12 15.059a167.153 167.153 0 01-15.36 115.35 162.033 162.033 0 01-87.341 67.162l-15.963 3.916a60.235 60.235 0 00-43.37 47.284A397.252 397.252 0 0030.119 512a391.53 391.53 0 003.915 67.162 65.958 65.958 0 0043.37 47.285l15.962 3.915a160.226 160.226 0 0190.353 71.078 164.442 164.442 0 0115.962 114.447l-3.915 15.962a57.525 57.525 0 0019.576 62.946s15.963 12.047 55.417 35.54a469.534 469.534 0 0060.235 31.623 63.247 63.247 0 0063.247-15.662l12.047-11.745a144.866 144.866 0 0198.485-42.767v-194.26a189.741 189.741 0 010-379.18V137.638a152.395 152.395 0 01-106.617-43.37z"
                fill="#50749A"
            />
            <path
                d="M974.005 444.536a60.235 60.235 0 00-43.37-47.284l-15.962-3.916a162.033 162.033 0 01-86.739-67.162 167.153 167.153 0 01-14.758-115.35l4.217-15.963a77.704 77.704 0 00-19.878-63.247 405.685 405.685 0 00-56.62-30.72 530.673 530.673 0 00-60.236-31.623 60.235 60.235 0 00-60.235 12.047l-11.746 11.746a152.395 152.395 0 01-106.617 43.369v180.706a189.741 189.741 0 010 379.181v194.259a144.264 144.264 0 01100.292 43.068l12.047 11.746a63.247 63.247 0 0063.247 15.661 469.534 469.534 0 0060.235-31.623c39.454-23.492 55.417-35.54 55.417-35.54a57.525 57.525 0 0019.877-62.042l-3.915-15.962a164.442 164.442 0 0115.963-114.447 160.226 160.226 0 0190.352-71.078l15.963-3.915a65.958 65.958 0 0043.37-47.586A391.53 391.53 0 00977.92 512a397.252 397.252 0 00-3.915-67.464z"
                fill="#50749A"
            />
        </svg>
    );
}

export default SvgSet;

这样就可以在需要使用的地方调用已创建好的SvgSet组件:

// App.tsx
import React from 'react';
import SvgSet from './components/SvgSet'
import './App.css';

function App() {
    return (
        <div className="App">
            <header className="App-header">
                <SvgSet />
            </header>
        </div>
    );
}

export default App;

页面上就可以渲染出所需要的SVG图标:

不过这样做有点麻烦,需要为每个.svg文件执行转换命令。不过@svgr/cli还提供了另一个命令:

⇒ npx @svgr/cli [-d out-dir] [--ignore-existing] [src-dir]

上面的命令可以将整个目录中的.svg文件转换成React组件。不过我更推荐在项目的package.json配置文件中scripts命令中添加相应的脚本命令:

// package.json

"scripts": {
    "svgr": "svgr -d ./src/components/icons/ ./src/assets/icons/",
},

其中./src/components/icons/是用来放置.svg转换出来的React组件(是一个.js文件),./src/assets/icons/是用来放置需要转换的.svg文件。这个时候还需要安装svgr

⇒ npm install @svgr/cli --save-dev

这样就可以在命令终端执行:

⇒ npm run svgr

执行完该命令之后,/src/assets/icons/目录下的所有.svg文件都会转换出相应的React组件,这些组件都会放在src/components/icons/目录下:

这样就可以引入转换出来的React组件:

// App.tsx
import React from 'react';
import {Password} from './components/icons';

import './App.css';

function App() {
    return (
        <div className="App">
            <header className="App-header">
                <Password />
            </header>
        </div>
    );
}

export default App;

这样Password组件就会在页面中渲染出来:

SVGR Webpack

SVGR还有一个Webpack版本,而且这个版本使用最为广泛,我们来看看在自己的工程中怎么配置Webpack版本的SVGR。

如果你对Webpack不太了解的话,建议阅读一下学一点Webpack配置基本配置Webpack的优化

我们的工程是基于Create-react-app构建的,有关于Webpack的配置是被隐藏的,如果需要对Webpack做其他的配置,需要先执行:

⇒ npm run eject

执行完命令之后会在工程的根目录下会生成一个config目录,在这个目录中会包含Webpack配置的相关文件,比如webpack.config.js

如此一来就可以在webpack.config.js中的module配置SVGR(或者其他有关于SVG的配置,稍后会介绍):

不过在Create-react-app的webpack.config.js配置中已经包含了SVGR的相关配置:

也就是说,在工程中不需添加额外的配置,就可以将.svg文件当作React组件使用,比如在App.tsx文件中引入一个.svg文件,就可以当作React组件使用:

// App.tsx
import React from 'react';

import {ReactComponent as SvgUser} from './assets/icons/user.svg'

import './App.css';

function App() {
    return (
        <div className="App">
        <header className="App-header">
            <SvgUser />
        </header>
        </div>
    );
}

export default App;

这个时候页面就会渲染出对应的SVG:

如果你的工程不是基于Create-react-app来构建的话,也只需要在webpack.config.js对SVG做简单的配置。在webpack.config.js中添加SVGR相关的配置之前,先在命令终端执行下面的命令来安装Webpack版本的SVGR:

⇒ npm install @svgr/webpack --save-dev

安装之后,在webpack.config.jsmodule添加有关于.svg相关的配置。

具体配置文件会和自己的工程有所差异,比如以学一点Webpack配置的示例,有关于Webpack的基本配置放在了webpack.common.js中。

module.exports = {
    module: {
        rules: [
            {
                test: /\.svg/,
                exclude: /node_modules/, // 排除不处理的目录
                include: path.resolve(__dirname, '../src'), // 精确指定要处理的目录
                use: [
                    'babel-loader',
                    '@svgr/webpack',
                ],
            },
        ]
    }
}

配置完成之后,就可以在需要使用SVG的时候,将.svg当作React组件使用:

import * as React from 'react';

import SvgPressed from '@images/pressed.svg';

const App = (props: any) => (
    <div {...props}>
        <SvgPressed />
    </div>
);
export default App;

App.tsx中将pressed.svg当作SvgPressed组件使用。渲染出来的效果如下:

至于其他几个版本的使用这里就不阐述,如果感兴趣的话请阅读SVGR官网相关的使用指南

SVGR高级运用

前面我们一起了解了如何在React工程中通过SVGR自动化的将SVG转换成React组件。上面演示的都是SVGR的最基本运用,其实SVGR的Webpack版本还有一些高级运用,比如给SVGR定制模板,自定义vewBox属性等。接下来我们就花一点时间来看看如何实现这些功能。

同样的,为了更好的演示,我们在工程中先内置一些.svg文件,这些SVG文件统一放置在assets/svgs目录下:

按照前面的介绍,在工程中安装@svgr/webpack:

⇒ npm install @svgr/webpack --save-dev

webpack.commons.js中添加@svgr/webpack相关的基础配置:

[
    // svg loader
    // images目录下的.svg文件通过svg-url-loader来加载
    // 用于运用在img的src属性和CSS中(比如background-image, mask-image, border-image等)
    {
        test: /\.svg/,
        exclude: /node_modules/, // 排除不处理的目录
        include: path.resolve(__dirname, '../src/assets/images'), // 精确指定要处理的目录
        use: [
            {
                loader: 'svg-url-loader',
                options: {
                    limit: 1024, // 小于10kb的图片编译成base64编码,大于的单独打包成图片
                    name: 'images/[hash]-[name].[ext]', // Placeholder占位符
                    publicPath: '/assets/', // 最终生成的CSS代码中,图片URL前缀
                    outputPath: 'assets', // 图片输出的实际路径(相对于/dist目录)
                    noquotes: true,
                },
            },
        ],
    },

    // SVGR Loader
    // 将SVG文件当作React组件使用
    // SVG内联的使用
    {
        test: /\.svg/,
        exclude: /node_modules/, // 排除不处理的目录
        include: path.resolve(__dirname, '../src/assets/svgs'), // 精确指定要处理的目录
        use: [
            {
                loader: 'babel-loader',
            },
            {
                loader: '@svgr/webpack',
            },
        ],
    },
],

这样就可以将src/assets/svgs/目录下所有.svg文件当作React组件使用:

// App.tsx
import * as React from 'react';
import './app.css';

import SvgShare from '@svgs/share.svg';

const App = (props: any) => (
    <div {...props}>
        <SvgShare />
    </div>
);

export default App;

注意:如果你的环境是TypeScript环境下的话,使用上面的方式引入.svg文件时,估计会碰到像下图这样的错误提示:

可以尝试着在项目的根目录下创建一个新的目录,比如typings,并且在该目录下新增custom.d.ts文件,然后添加下面这段代码:

// typings/custom.d.ts
declare module '*.svg' {
    const content: any;
    export default content;
}

接着在tsconfig.json中添加下面的配置:

// tsconfig.json
{
    // ...

    "files": ["./typings/custom.d.ts"],
}

保存修改之后,命令行中重新运行一下,就可以解决这个错误。

上面我们看到的是SVGR Webpack版本最基本的配置。我们可以在该基础上进行更高级的设置。比如在@svgr/webpackloader选项,通过options来做扩展:

// SVGR Loader
// 将SVG文件当作React组件使用
// SVG内联的使用
{
    test: /\.svg/,
    exclude: /node_modules/, // 排除不处理的目录
    include: path.resolve(__dirname, '../src/assets/svgs'), // 精确指定要处理的目录
    use: [
        {
            loader: 'babel-loader',
        },
        {
            loader: '@svgr/webpack',
            options: {
                template: (
                    { template },
                    opts,
                    { imports, componentName, props, jsx, exports }
                ) => template.ast`
                    ${imports}

                    const ${componentName} = (${props}) => {
                        props = {...props, fill: '#ff3366' }
                        return ${jsx}
                    }

                    export default ${componentName}
                `,
            },
        },
    ],
},

按照这种方式可能用来定制你自己需要的组件模板。如果仅是为了改变<svg>的属性,那就没有必要,我们可以在调用组件的时候直接修改相应的属性,比如:

<SvgShare  fill="#ff3366" />

如果没有额外的定制需求,SVGR的默认配置就足够满足我们日常的使用

SVG + Webpack + React

了解了SVGR之后,是否感觉到她的强大之处。其实有了SVGR(或者说在你的项目中使用SVGR),项目中使用SVG将会变得非常的灵活和简单。不过我还是想花一点点时间和大家一起聊聊SVG、Webpack和React结合在一起的一些工作方式。如果你感兴趣的话,请继续往下阅读。

特别声明,接下来示例中介绍的SVG在Webpack的相关配置是基于学一点Webpack配置中已有的Webpack环境做的相关扩展。你可以基于 Step17 分支进行扩展

为了更好的阐述SVG在Webpack中如何进行配置。先把文件目录做一些整理:

并且将/build/webpack.common.js配置文件中有关于.svg相关的配置全部注释掉:

前面我们提到过,在React中使用SVG的多种方式。如果不阅读代码的话,它们呈现给用户的效果都是一致的。但不同的方式在编译出来的代码方面还是有很大的差异。在实际开发中,我们可以从工程链路上对SVG做一些处理。也就是说,在Webpack中加载一些加载器来处理SVG。

先从最简单的方式出发。

const App = () => <img src="/path/image.svg" alt="" />;

上面的方式是通过<img>标签来加载.svg文件,而且这种方式也常被开发者使用。但是,SVG通常很小,在这种情况之下,期望使用Data URL来避免https的请求。而要将SVG转换为Data URL,除了使用第三方平台或相关的转换工具之外,还可以使用Webpack的加载器,比如url-loader

要使用url-loader需要先安装该加载器:

⇒ npm i --save-dev url-loader

然后在webpack.common.js中添加相应配置:

{
    test: /\.svg/,
    exclude: /node_modules/, // 排除不处理的目录
    include: path.resolve(__dirname, '../src'), // 精确指定要处理的目录
    use: [
        {
            loader: 'url-loader',
            options: {
                limit: 10000,
            },
        },
    ],
},

App.tsx引用一个.svg

// App.tsx
import * as React from 'react';
import './app.css';

import BabyUrl from '../../../../assets/svgs/baby.svg';

const App = (props: any) => (
    <div {...props}>
        <img src={BabyUrl} alt="Baby" />
    </div>
);

export default App;

编译出来的结果像下图这样:

Webpack的url-loader加载器实际上是将图像转换为 base64 URIs。SVG是XML标记语言,是xml字符串,使用base64 URIs并不是必须的。相反,我们可以使用svg-url-loader加载器,它将以UTF-8编码的 Data URL字符串的形式加载.svg文件。这样做好处是:

  • 生成的字符串更短(对于2k大小的图标可以缩短~2倍)
  • 编译出来的字符串使用gzip能得到更好的压缩
  • 浏览器解析UTF-8编码字符串比等效的base64更快

同样的,要使用svg-url-loader加载器,需要先安装:

⇒ npm i --save-dev svg-url-loader

然后在webpack.common.jssvg-url-loader进行配置:

// svg loader
{
    test: /\.svg/,
    exclude: /node_modules/, // 排除不处理的目录
    include: path.resolve(__dirname, '../src'), // 精确指定要处理的目录
    use: [
        {
            loader: 'svg-url-loader',
            options: {
                limit: 10000,
                name: '[path][name].[ext]',
            },
        },
    ],
},

使用方式和前面的相同。而且在CSS的background-image引用.svg时,也会将相应的.svg文件转换为base64 URIs

// app.css
.box {
    background: url('../../../../assets/svgs/food.svg') repeat left top;
    width: 50vw;
    height: 30vh;
}

编译出来的结果如下:

虽然使用url-loadersvg-url-loader都可以将小于指定文件大小的.svg转换成base64 URIs,也能减少https的请求。但更推荐的是将SVG代码内联到DOM中,即内联使用SVG。这也是最佳的一种方式。对于内联SVG的使用,理论上是不需要Webpack的加载器,可以直接将SVG代码复制到React的组件中。

虽然说在开发的时候,可以直接将SVG代码复制到相应的React组件中,但这可能不是最好的方式。比如说我们通过Sketch设计软件对SVG做了调整,我们需要重新复制粘贴。不过在React开发中,我们还有一种更优秀的方案,就是使用react-svg-loader加载器。

执行下面命令,就可以安装react-svg-loader

⇒  npm i --save-dev react-svg-loader

webpack.common.js中添加react-svg-loader相关的配置:

{
    test: /\.svg/,
    exclude: /node_modules/, // 排除不处理的目录
    include: path.resolve(__dirname, '../src'), // 精确指定要处理的目录
    use: [
        {
            loader: "babel-loader"
        },
        {
            loader: 'react-svg-loader',
        }
    ],
},

有关于react-svg-loader更详细的配置,还可以点击这里查阅

配置完react-svg-loader之后,就可以把单个.svg文件当作React组件来使用:

// App.tsx
import * as React from 'react';
import './app.css';

import SvgFood from '../../../../assets/svgs/food.svg';

const App = (props: any) => (
    <div {...props}>
        <SvgFood />
    </div>
);
export default App;

在浏览器上的呈现效果如下:

但我们有的时候可能需要将.svg文件当作图片引用,比如在<img>src引入,或者在CSS的background-imagemask-image中引入。比如:

// App.tsx
import * as React from 'react';
import './app.css';

import BabyUrl from '../../../../assets/svgs/baby.svg';
import SvgFood from '../../../../assets/svgs/food.svg';

const App = (props: any) => (
    <div {...props} styleName="box">
        <!-- src中引入.svg -->
        <img src={BabyUrl} alt="Baby" />
        <SvgFood />
    </div>
);
export default App;

// app.css
.box {
    background: url('../../../../assets/svgs/food.svg') repeat left top;
    width: 50vw;
    height: 30vh;
}

这个时候这些方式引入的.svg失败了:

注意,在该工程体系中使用的CSS Module方式来管理CSS的类名

从上图你可能发现了,尤其是在CSS中引入.svg时,这个时候它看起来更像下面这样:

background-image: url(functionImage(props){returnReact.createElement(
    'svg',
    props,
    React.createElement(...)
)});

针对这样的场景,我们需要将svg-url-loaderreact-svg-loader结合起来使用。通过excludeinclude来区分。比如说,要用于<img>src或CSS中的.svg文件,都指定放置在src/assets/images目录下,该目录下的.svg都通过svg-url-loader来加载处理。而要内联的.svg文件,都指定放置在src/assets/svgs目录下,并且通过react-svg-loader来加载处理。同时使用exclude来指定不要处理的文件路径。

// webpack.common.js
{
    test: /\.svg$/,
    exclude: /node_modules/, // 排除不处理的目录
    include: path.resolve(__dirname, '../src/assets/images'), // 精确指定要处理的目录
    use: [
        {
            loader: 'svg-url-loader',
            options: {
                limit: 10000,
                name: '[path][name].[ext]',
            },
        },
    ],
},
{
    test: /\.svg$/,
    exclude: /node_modules/, // 排除不处理的目录
    include: path.resolve(__dirname, '../src/assets/svgs'), // 精确指定要处理的目录
    use: [
        {
            loader: 'babel-loader',
        },
        {
            loader: 'react-svg-loader',
            options: {
                jsx: true,
            },
        },
    ],
},

这种方式有一点不好的地方就是需要将相应的.svg放置在对应的文件目录下:

使用的时候可以像下面这样使用:

// App.tsx
import * as React from 'react';
import './app.css';

// 用于img的src
import PressedUrl from '../../../../assets/images/pressed.svg';

// 内联SVG
import SvgFood from '../../../../assets/svgs/food.svg';

const App = (props: any) => (
    <div {...props} styleName="box">
        <img src={PressedUrl} alt="Baby" />
        <SvgFood />
    </div>
);
export default App;

/* app.css */
.box {
    background: url("../../../../assets/images/security.svg") repeat left top;
    width: 50vw;
    height: 30vh;
}

这个时候你在浏览器中看到的效果如下:

你可能也已经发现了,react-svg-loader的使用和前面介绍的SVGR Webpack版本非常的相似,事实上也是这样的,它其实就是SVGR的一个轻量版本。而且react-svg-loader也像SVGR一样提供了不同的版本

小结

文章从不同的角度出发,介绍了如何在React中使用SVG。在React中可以直接将.svg文件当做图像的方式在<img>和CSS中使用,也可以手动将SVG内联到相应的React组件中。但是我们借助一些工具和Webpack的加载器,可以将这部分工作自动化完成,比如使用SVGR CLI直接将指定目录下的.svg转换成React组件(.js),也可以使用SVGR的Webpack版本,将独立的.svg文件当作React组件使用。另外,在Webpack中,还可以具体的场景来指定不同的加载器。

有了这些基础,我们就可以用这方面的知识来构建一个React的图标系统,如果你对这方面感兴趣的话,欢迎持续关注后续的更新。如果你在这方面有更好的建议或经验,欢迎在下面的评论中与我们共享。如果你是基于Vue框架开发项目,也希望在项目中使用这方面的技术,那么建议你花点时间阅读《如何在Vue项目中使用SVG Icon》一文。

返回顶部