通常我们消费 svg 的方式比较简单:
<img src={require('filename.svg')} />
即可以在网页上打印出 svg 的图案。
但是为了满足作为一个图标库的各种需要(控制大小,旋转角度,填充颜色等),使用 img 标签是没办法达成的;所以我们需要把SVG作为 Component 使用;
查找方案:
如果使用的是Create-React-App的话,就比较便捷。在 CRA2.x开始,CRA在背后调用了一个叫做svgr的工具,所以我们可以:
import { ReactComponent as MyLogo } from './logo.svg'; // 选择 ReactComponent 导出项目
const App = () => (
<div id="App">
<MyLogo />
</div>
);
export default App;
eject 以后查看 CRA为我们做的配置:
'use strict';
const path = require('path');
const camelcase = require('camelcase');
// This is a custom Jest transformer turning file imports into filenames.
// <http://facebook.github.io/jest/docs/en/webpack.html>
module.exports = {
process(src, filename) {
const assetFilename = JSON.stringify(path.basename(filename));
if (filename.match(/\.svg$/)) {
// Based on how SVGR generates a component name:
// <https://github.com/smooth-code/svgr/blob/01b194cf967347d43d4cbe6b434404731b87cf27/packages/core/src/state.js#L6>
const pascalCaseFilename = camelcase(path.parse(filename).name, {
pascalCase: true,
});
const componentName = `Svg${pascalCaseFilename}`;
return `const React = require('react');
module.exports = {
__esModule: true,
default: ${assetFilename},
ReactComponent: React.forwardRef(function ${componentName}(props, ref) {
return {
$$typeof: Symbol.for('react.element'),
type: 'svg',
ref: ref,
key: null,
props: Object.assign({}, props, {
children: ${assetFilename}
})
};
}),
};`;
}
return `module.exports = ${assetFilename};`;
},
};
注意到,CRA为 webpack loader生成配置的时候,导出了俩内容:1. 静态svg文件(default);2. ReactComponent 与它的模版内容。
然而,当我们构建流程不是 CRA ,或者有更多的自定义特性的时候,我们需要自行配置 svgr。
Svgr是什么?
Svgr 是一系列把 svg 文件转成 ReactComponent 的工具,包括了 webpack loader,一些命令行工具, 以及工具流配件。
普通的业务调用场景使用 webpack-loader 即可以满足需要,基本上按照CRA(或者链接)的配置即可。
如果我们希望,譬如批量转换 svg ,动态引入等特性,就需要用到命令行工具。
- 安装:
yarn add --dev @svgr/cli
- 编写npm脚本:
"scripts": {
"boot": "npx @svgr/cli --template template.js --ext tsx --config-file .svgrrc.json -d svgrs svgs"
}
- 准备svg文件目录与编写模版文件template.js:
/* eslint-disable */
function template({ template }, opts, { imports, componentName, props, jsx, exports }) {
const typeScriptTpl = template.smart({ plugins: ['typescript'] });
return typeScriptTpl.ast`
import * as React from 'react';
const ${componentName} = (props: React.SVGProps<SVGSVGElement>) => ${jsx};
export default ${componentName};
`;
}
module.exports = template;
- 运行 boot 以后我们得到了一个svgrs 文件夹
在这里,输出的是 tsx 是因为我们在命令制定了ext是 tsx。如果只是需要jsx 的话就不需要特意指定。可以看出,svgr命令行工具,按照我们给定 template.js 的格式输出了一个完整的 react component 文件。
- 编写图标库主体逻辑:
import classNames from 'classnames';
import React, { CSSProperties, FC, SVGAttributes } from 'react';
import '../../src/scss/base.scss';
import './index.scss';
import { ConfigConsumer, ConfigProps } from '../ConfigProvider';
import { toDashed } from './svgs/util';
type IconSize = 'small' | 'normal' | 'large';
export type Icons =
| 'PendingStatus'
| 'Book'
// ...
interface Props {
// ...
}
/**
*
* How to dynamic import assets?
* <https://github.com/survivejs/webpack-book/issues/80#issuecomment-216068406>
*
*/
export const dynamicImportSvgs = require.context('./svgrs', true);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type NativeAttrs = Omit<SVGAttributes<any>, keyof Props>;
export type IconProps = NativeAttrs & Props;
const Icon: FC<IconProps> = ({
type,
rotate,
color,
size = 'normal',
style,
className,
classes,
...props
}) => {
if (typeof type === 'undefined') {
throw new Error(`Icon type empty: ${type}`);
}
const IconType = dynamicImportSvgs(`./${type}.tsx`).default;
return (
<ConfigConsumer>
{({ prefixCls: customPrefixCls }: ConfigProps) => {
const prefixCls = `${customPrefixCls}-icon`;
const classNameString = classNames(
prefixCls,
`${prefixCls}${toDashed(type)}`,
`${prefixCls}-${size}`,
className,
classes
);
return (
<IconType
className={classNameString}
fill="currentColor"
style={{
transform: `rotate(${rotate}deg)`,
color,
...style,
}}
{...props}
/>
);
}}
</ConfigConsumer>
);
};
export default Icon;
// Just for stories render props table
export const IconPropsForDoc: FC<Props> = () => null;
以上组件除了常规的组件逻辑编写以外,还实现了动态调用组件的逻辑,以方便我们在敲击Icon名称的时候可以展示出对应组件的视图。
最终,使用文档生成器输出的API列表:
一些小问题与解决办法
Q:我的组件生成出来的svg是黑色的,或者变样子了。
A: 使用sketch或者一些设计工具导出的文件是硬编码了svg的属性,所以我们需要替换掉一些色值的属性,以便于我们可以通过 Component props 去控制。 svgr 支持配置,在目录新增 .svgrrc.json , 既可以配置在转换过程中把一些硬编码的属性(比如#000色值)替换:
{
"icon": true,
"replaceAttrValues": {
"#000": "{props.fill}",
"#666": "{props.fill}",
"#181818": "{props.fill}",
"#111": "{props.fill}",
"#111111": "{props.fill}",
"#999": "{props.fill}",
"#999999": "{props.fill}"
}
}
Q:我没办法通过css设置svg的颜色。
A:"currentColor" 允许 svg 通过css color 属性来改变 fill 的值。 所以我们在组件设置属性 fill=”currentColor”,让消费者可以通过 css 更改颜色。