前言
在 ReactNative 应用开发过程中,业务图标的有效管理是一个常见且重要的需求。与 Web 开发不同,移动端开发需要考虑以下特殊因素:
- 多分辨率适配:需要为不同屏幕密度(1x, 2x, 3x 等)提供适配方案,避免在高分辨率设备上出现模糊
- 应用包体积优化:图标资源需要尽可能精简,避免因大量图片资源导致应用包体积过大
- 动态更新能力:支持远程更新图标资源,而无需发布新版本应用
- 跨平台一致性:确保图标在 iOS 和 Android 平台显示效果一致
iconfont
Iconfont 方案的优势
Iconfont(字体图标)相比传统图片方案具有明显优势:
- 矢量特性:基于 SVG 的矢量图标可以无限缩放而不失真,完美适配各种分辨率设备
- 颜色可调:通过 CSS 的 color 属性即可动态改变图标颜色,支持主题切换等场景
- 体积小巧:一个 100KB 左右的字体文件通常可以包含 500+ 个图标,远小于同等数量的 PNG 图片
- 维护方便:所有图标集中管理,修改后只需更新字体文件,无需逐个替换图片资源
- 渲染性能:字体图标的渲染性能通常优于图片,特别是在列表等需要大量重复渲染的场景
创建IconFont项目
-
准备图标资源
在Iconfont 官网完成以下操作:
- 创建项目:建议按业务模块划分,如"用户中心"、"支付"等
- 上传或选择所需图标:支持 SVG 格式上传,保持图标质量
- 设置图标的 font-class 名称:采用"模块_功能"的命名规范,如"user_profile"
- 添加项目成员:方便团队协作维护
-
生成字体文件
- 点击"生成代码"按钮
- 选择"Font class"引用方式
- 复制生成的 CSS 链接
- 建议开启"自动同步"功能,当图标更新时自动获取最新版本

创建图标组件
安装依赖
bash
# 使用 react-native-svg 来渲染 SVG 图标
pnpm i react-native-svg
图标下载脚本
该脚本自动从 Iconfont 下载最新图标并生成 TypeScript 定义文件:
javascript
const https = require('https');
const fs = require('fs');
const path = require('path');
const { format } = require('prettier');
// 配置参数
const ICONFONT_URL = 'https://at.alicdn.com/t/c/font_4995190_77ur0b6rmun.js';
const OUTPUT_DIR = path.join(__dirname, '../src/constants');
const OUTPUT_FILE = path.join(OUTPUT_DIR, 'iconfontSymbols.ts');
// 创建输出目录
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
async function downloadIconfont() {
return new Promise((resolve, reject) => {
console.log('📡 正在下载iconfont文件...');
https.get(ICONFONT_URL, (res) => {
if (res.statusCode !== 200) {
return reject(new Error(`请求失败,状态码: ${res.statusCode}`));
}
let rawData = '';
res.setEncoding('utf8');
res.on('data', (chunk) => rawData += chunk);
res.on('end', () => {
console.log('✅ 下载完成');
resolve(rawData);
});
}).on('error', reject);
});
}
function extractSvgContent(svgString) {
return svgString
.replace(/^<symbol[^>]*>/, '')
.replace(/<\/symbol>$/, '');
}
async function generateIcons() {
try {
const raw = await downloadIconfont();
const symbolRegex = /<symbol\s+id="(?:icon-)?(.*?)"[^>]*>([\s\S]*?)<\/symbol>/g;
const symbols = [];
let match;
while ((match = symbolRegex.exec(raw)) !== null) {
symbols.push({
originalName: match[1],
safeName: match[1].replace(/^sugar-care-icon-/, '').replace(/-/g, '_'),
fullSymbol: match[0],
svgContent: extractSvgContent(match[0]),
viewBox: match[0].match(/viewBox="([^"]*)"/)?.[1] || '0 0 1024 1024'
});
}
if (symbols.length === 0) {
throw new Error('❌ 未检测到有效的symbol图标');
}
// 生成TypeScript内容
const tsContent = await format(`
// AUTO-GENERATED BY ICONFONT-SYMBOL-LOADER
// 更新时间: ${new Date().toISOString()}
// 源文件: ${ICONFONT_URL}
export interface IconDefinition {
symbol: string;
svg: string;
viewBox: string;
}
const icons = {
${symbols.map(icon => `
/** ${icon.originalName} */
${icon.safeName}: {
symbol: \`${icon.fullSymbol.replace(/`/g, '\\`')}\`,
svg: \`${icon.svgContent.replace(/`/g, '\\`')}\`,
viewBox: "${icon.viewBox}"
}`).join(',\n')}
} as const;
export type IconName = keyof typeof icons;
export default icons;
`, { parser: 'typescript', singleQuote: true });
fs.writeFileSync(OUTPUT_FILE, tsContent);
console.log(`✨ 成功生成 ${symbols.length} 个图标定义`);
console.log(`📂 输出文件: ${path.relative(process.cwd(), OUTPUT_FILE)}`);
console.log(`💡 使用提示: import icons, { IconName } from '@/constants/iconfontSymbols'`);
} catch (error) {
console.error('❌ 生成失败:', error.message);
process.exit(1);
}
}
generateIcons();
Icon组件实现
封装可复用的图标组件,支持动态大小和颜色:
typescript
import React from 'react';
import { SvgXml } from 'react-native-svg';
import { StyleProp, ViewStyle } from 'react-native';
import iconMap, { IconName } from '@constants/iconfontSymbols';
interface IconProps {
name: IconName; // 必填,图标名称
size?: number; // 可选,默认24px
color?: string; // 可选,图标颜色
style?: StyleProp<ViewStyle>; // 可选,容器样式
}
const Icon: React.FC<IconProps> = ({ name, size = 24, color, style }) => {
const iconData = iconMap[name];
if (!iconData) {
console.error(`图标 "${name}" 不存在`);
return null;
}
const { symbol, viewBox } = iconData;
if (!symbol) {
console.error(`图标 "${name}" 数据格式错误`);
return null;
}
// 提取symbol中的内容,构造完整的svg
const symbolId = symbol.match(/id="([^"]+)"/)?.[1] || '';
const symbolContent = symbol.match(/<symbol[^>]*>(.*)<\/symbol>/)?.[1] || '';
// 构造完整的svg字符串
let svgString = `<svg viewBox="${viewBox || '0 0 1024 1024'}" width="${size}" height="${size}">`;
// 颜色处理逻辑
if (color) {
if (symbolContent.includes('fill=')) {
// 替换现有fill属性
svgString += symbolContent.replace(/fill="[^"]*"/g, `fill="${color}"`);
} else {
// 为path添加fill属性
svgString += symbolContent.replace(/<path/g, `<path fill="${color}"`);
}
} else {
svgString += symbolContent;
}
svgString += '</svg>';
return (
<SvgXml
xml={svgString}
width={size}
height={size}
style={style}
/>
);
};
// 使用React.memo优化性能
export default React.memo(Icon);
使用示例:
typescript
// 基本使用
<Icon name="home" />
// 自定义大小和颜色
<Icon name="user" size={32} color="#1890ff" />
// 带样式的图标
<Icon
name="setting"
style={{ marginRight: 8 }}
/>
```## 前言
在 ReactNative 应用开发过程中,业务图标的有效管理是一个常见且重要的需求。与 Web 开发不同,移动端开发需要考虑以下特殊因素:
1. 多分辨率适配(1x, 2x, 3x 等)
2. 应用包体积优化
3. 动态更新能力
4. 跨平台一致性
## iconfont
### Iconfont 方案的优势
Iconfont(字体图标)相比传统图片方案具有明显优势:
1. **矢量特性**:无限缩放不模糊
2. **颜色可调**:通过 CSS 动态改变颜色
3. **体积小巧**:一个字体文件可包含数百个图标
4. **维护方便**:集中管理所有业务图标
### 创建IconFont项目
1. **准备图标资源**
在[Iconfont 官网](https://www.iconfont.cn)完成以下操作:
- 创建项目
- 上传或选择所需图标
- 设置图标的 font-class 名称
2. **生成字体文件**
- 点击生成新的代码

## 创建图标组件
### 安装依赖
```bash
pnpm i react-native-svg
图标下载脚本
javascript
const https = require('https');
const fs = require('fs');
const path = require('path');
const { format } = require('prettier');
// 配置参数
const ICONFONT_URL = 'https://at.alicdn.com/t/c/font_4995190_77ur0b6rmun.js';
const OUTPUT_DIR = path.join(__dirname, '../src/constants');
const OUTPUT_FILE = path.join(OUTPUT_DIR, 'iconfontSymbols.ts');
// 创建输出目录
if (!fs.existsSync(OUTPUT_DIR)) {
fs.mkdirSync(OUTPUT_DIR, { recursive: true });
}
async function downloadIconfont() {
return new Promise((resolve, reject) => {
console.log('📡 正在下载iconfont文件...');
https.get(ICONFONT_URL, (res) => {
if (res.statusCode !== 200) {
return reject(new Error(`请求失败,状态码: ${res.statusCode}`));
}
let rawData = '';
res.setEncoding('utf8');
res.on('data', (chunk) => rawData += chunk);
res.on('end', () => {
console.log('✅ 下载完成');
resolve(rawData);
});
}).on('error', reject);
});
}
function extractSvgContent(svgString) {
return svgString
.replace(/^<symbol[^>]*>/, '')
.replace(/<\/symbol>$/, '');
}
async function generateIcons() {
try {
const raw = await downloadIconfont();
const symbolRegex = /<symbol\s+id="(?:icon-)?(.*?)"[^>]*>([\s\S]*?)<\/symbol>/g;
const symbols = [];
let match;
while ((match = symbolRegex.exec(raw)) !== null) {
symbols.push({
originalName: match[1],
safeName: match[1].replace(/^sugar-care-icon-/, '').replace(/-/g, '_'),
fullSymbol: match[0],
svgContent: extractSvgContent(match[0]),
viewBox: match[0].match(/viewBox="([^"]*)"/)?.[1] || '0 0 1024 1024'
});
}
if (symbols.length === 0) {
throw new Error('❌ 未检测到有效的symbol图标');
}
// 生成TypeScript内容
const tsContent = await format(`
// AUTO-GENERATED BY ICONFONT-SYMBOL-LOADER
// 更新时间: ${new Date().toISOString()}
// 源文件: ${ICONFONT_URL}
export interface IconDefinition {
symbol: string;
svg: string;
viewBox: string;
}
const icons = {
${symbols.map(icon => `
/** ${icon.originalName} */
${icon.safeName}: {
symbol: \`${icon.fullSymbol.replace(/`/g, '\\`')}\`,
svg: \`${icon.svgContent.replace(/`/g, '\\`')}\`,
viewBox: "${icon.viewBox}"
}`).join(',\n')}
} as const;
export type IconName = keyof typeof icons;
export default icons;
`, { parser: 'typescript', singleQuote: true });
fs.writeFileSync(OUTPUT_FILE, tsContent);
console.log(`✨ 成功生成 ${symbols.length} 个图标定义`);
console.log(`📂 输出文件: ${path.relative(process.cwd(), OUTPUT_FILE)}`);
console.log(`💡 使用提示: import icons, { IconName } from '@/constants/iconfontSymbols'`);
} catch (error) {
console.error('❌ 生成失败:', error.message);
process.exit(1);
}
}
generateIcons();
Icon组件
typescript
import React from 'react';
import { SvgXml } from 'react-native-svg';
import { StyleProp, ViewStyle } from 'react-native';
import iconMap, { IconName } from '@constants/iconfontSymbols';
interface IconProps {
name: IconName;
size?: number;
color?: string;
style?: StyleProp<ViewStyle>; // 使用正确的 React Native 类型
}
const Icon: React.FC<IconProps> = ({ name, size = 24, color, style }) => {
const iconData = iconMap[name];
if (!iconData) {
console.error(`图标 "${name}" 不存在`);
return null;
}
const { symbol, viewBox } = iconData;
if (!symbol) {
console.error(`图标 "${name}" 数据格式错误`);
return null;
}
// 提取symbol中的内容,构造完整的svg
const symbolId = symbol.match(/id="([^"]+)"/)?.[1] || '';
const symbolContent = symbol.match(/<symbol[^>]*>(.*)<\/symbol>/)?.[1] || '';
// 构造完整的svg字符串
let svgString = `<svg viewBox="${viewBox || '0 0 1024 1024'}" width="${size}" height="${size}">`;
// 如果有color,则添加fill属性
if (color) {
if (symbolContent.includes('fill=')) {
svgString += symbolContent.replace(/fill="[^"]*"/g, `fill="${color}"`);
} else {
svgString += symbolContent.replace(/<path/g, `<path fill="${color}"`);
}
} else {
svgString += symbolContent;
}
svgString += '</svg>';
return (
<SvgXml
xml={svgString}
width={size}
height={size}
style={style}
/>
);
};
export default React.memo(Icon);
适用示例
typescript
// 基本使用
<Icon name="home" />
// 自定义大小和颜色
<Icon name="user" size={32} color="#1890ff" />
// 带样式的图标
<Icon
name="setting"
style={{ marginRight: 8 }}
/>