搞一个自个的React Native图标库
想在React Native(以下称RN)使用图标,其实办法很多,想简单省事的话,直接梭哈 react-native-vector-icons 就可以,这个属于目前RN平台上最为热门的图标解决方案了,使用广泛也比较稳定,不足之处在于需要将字体文件打包进app,不能热更新。

这里不深究 react-native-vector-icons 的实现原理,来看看如果我们自己想做一个自己的图标库,应该怎么来做呢?
一、怎么显示一个图标?
要想展示一个图标,可以有几种方式:webfont、svg或者图片。
webfont,网页上比较常见的图标库实现方式,需要将字体文件打包到App内部。
svg,体积小,矢量图,拉伸不失真,且可以通过svg属性控制颜色。
图片,使用简单,但是无法动态设置颜色,且无法适应不同倍率的屏幕。
综合来说,webfont需要将字体文件打包到App内部,不能热更,svg本事是xml代码形式的,易于控制,相对来说svg比较适合做图标库。
二、用svg实现图标库的思路
对于图标库来说,需要满足以下两个基本条件:
可以设置大小,任意大小不变形,svg本身就是矢量的,天生就支持。
可以设置颜色,可以通过设置svg fill属性来实现。
那么,思路很简单,只需要开发者,提前做好svg图标。将svg文件解析出来,动态设置颜色,图标大小可以通过设置元素width/height来实现。
三、RN 加载svg图片
一般来说,我们会用 react-native-svg 来加载 svg。
yarn add react-native-svg加载svg的方式有多种,可以加载远程svg,或者自己编写svg代码均可。
// 使用远程svg图片<SvgUriwidth="100%"height="100%"uri="http://thenewcode.com/assets/images/thumbnails/homer-simpson.svg"/>
// 自行编写svg代码<Svg height="50%" width="50%" viewBox="0 0 100 100"><Circlecx="50"cy="50"r="45"stroke="blue"strokeWidth="2.5"fill="green"/><Rectx="15"y="15"width="70"height="70"stroke="red"strokeWidth="2"fill="yellow"/></Svg>
当然也可以加载本地的svg图片,需要配置metro config和babel,有兴趣可以前往文档进行阅读。
三、解析svg文件
解析svg文件可以通过 react-native-svg-uri 来实现。
npm install react-native-svg-uri --save<SvgUri width="200" height="200" source={require('./img/homer.svg')} />在react-native中默认只能require png和xml文件,需要require svg文件,需要做额外的配置。
此外,频繁的require本地文件也会减慢速度,我们可以读取svg文件,解析xml内容,仅保留path标签,其余部分内容对我们显示图片来说是无用的。
我们可以编写一个脚本,批量读取svg文件,解析xml数据,react-native-svg-uri也预留了使用xmlData的接口。
<SvgUri width="200" height="200" svgXmlData={svgXmlData} fill="red" /react-native-svg-uri 渲染svg图片依赖于 react-native-svg,但是 react-native-svg-uri 这个库已经很久不更新了,其依赖的 react-native-svg 版本过低,会导致实际使用过程,因为原生和react-native-svg-uri 使用的react-native-svg版本不同而报错。
react-native-svg-uri 常年不更新,且代码简单,建议直接将仓库代码扒下来放到自己的工具代码中使用,ts版本也可以从作者仓库里面复制一份,代码可以在 src/lib/react-native-svg-uri 找到,仓库地址可以点击下方查看原文获取。
四、具体步骤
仔细串一串svg图标渲染的整个流程:svg文件->脚本解析->svg xml data->react-native-svg-uri渲染svg图片。
1、准备svg素材
svg素材可以由设计师提供,也可以自行前往iconfont下载,或者自己制作。

2、脚本处理
require大量静态资源影响性能,且需要做额外的配置,为了方便处理,这里我们将所有的svg文件简单处理一下,转换成一个js文件。这里我使用nodejs编写,可以选自己喜欢的脚本语言处理一下。
const path = require('path');const fs = require('fs');const svgFileDir = path.resolve(__dirname, '../../src/assets/svg');function readSvgFile(svgFileName) {return new Promise((resolve, reject) => {fs.readFile(path.join(svgFileDir, svgFileName), 'utf8', (error, svgFile) => {let svgPath = svgFile.replace(/<\?xml.*?\?>|<\!--.*?-->|<!DOCTYPE.*?>/g, '');svgPath = svgPath.replace(/[\r\n]*/g, '');if (error) {return reject(error);}if (svgFileName.indexOf('.svg') === -1) {return resolve({});}resolve({[svgFileName.slice(0, svgFileName.lastIndexOf('.'))]: svgPath,});});});}function readSvgDir() {return new Promise((resolve, reject) => {fs.readdir(svgFileDir, (error, svgFiles) => {if (error) {return reject(error);}Promise.all(svgFiles.map(svgFileName => readSvgFile(svgFileName))).then(data => resolve(data)).catch(err => reject(err));});});}readSvgDir().then(data => {const svgFile = `export default {${data.filter(item => Object.keys(item)[0]).map(item => `'${Object.keys(item)[0]}': \`${Object.values(item)[0]}\``)}}\n`;fs.writeFile(path.resolve(svgFileDir, 'index.js'), svgFile, err => {if (err) {throw new Error(err);}});}).catch(error => {throw new Error(error);});
处理的产物,类似于这样。
export default {'arrow-down': `svg data`,'arrow-up': `svg data`,...}
3、封装Icon Compoment
上面两步已经准备好了所有素材,只需要将对应的svg xml data配合size以及color做渲染即可。
import React from 'react';import { Pressable, StyleProp, ViewStyle } from 'react-native';import SvgUri from '../../lib/react-native-svg-uri';import svgs from '../../assets/svg';export type IconProps = {// icon名字name: string;// 颜色color?: string;// 大小size?: number;// 点击回调onPress?: () => void;// stylestyle?: StyleProp<ViewStyle>;};const Navbar: React.FC<IconProps> = props => {const { name, color = '#666', size = 20, onPress, style = {} } = props;let svgXmlData = (svgs as any)[name];if (!svgXmlData) {throw new Error(`No Icon Named ${name} Was Found!`);}return (<Pressable onPress={onPress} style={style}><SvgUri width={size} height={size} svgXmlData={svgXmlData} fill={color} /></Pressable>);};export default Navbar;
使用方式
<Icon name="arrow-down" color="red" />处理到这里,基本已经完成了,需要新增图标时,只需要将svg文件放到设定好的文件夹中,然后再执行svg的处理脚本即可,为了方便使用,可以添加一个npm script脚本。
{"scripts": {"build:svg": "node scripts/svg/index.js"}}
以上所有代码,均可在 https://github.com/AspenLuoQiang/react-native-ui-view 仓库中找到,或点击下方查看原文进入代码仓库。
