ReactNative开发实战——ReactNative 开发中的图标管理方案:基于 Iconfont 的自定义图标库实现

前言

在 ReactNative 应用开发过程中,业务图标的有效管理是一个常见且重要的需求。与 Web 开发不同,移动端开发需要考虑以下特殊因素:

  1. 多分辨率适配:需要为不同屏幕密度(1x, 2x, 3x 等)提供适配方案,避免在高分辨率设备上出现模糊
  2. 应用包体积优化:图标资源需要尽可能精简,避免因大量图片资源导致应用包体积过大
  3. 动态更新能力:支持远程更新图标资源,而无需发布新版本应用
  4. 跨平台一致性:确保图标在 iOS 和 Android 平台显示效果一致

iconfont

Iconfont 方案的优势

Iconfont(字体图标)相比传统图片方案具有明显优势:

  1. 矢量特性:基于 SVG 的矢量图标可以无限缩放而不失真,完美适配各种分辨率设备
  2. 颜色可调:通过 CSS 的 color 属性即可动态改变图标颜色,支持主题切换等场景
  3. 体积小巧:一个 100KB 左右的字体文件通常可以包含 500+ 个图标,远小于同等数量的 PNG 图片
  4. 维护方便:所有图标集中管理,修改后只需更新字体文件,无需逐个替换图片资源
  5. 渲染性能:字体图标的渲染性能通常优于图片,特别是在列表等需要大量重复渲染的场景

创建IconFont项目

  1. 准备图标资源

    Iconfont 官网完成以下操作:

    • 创建项目:建议按业务模块划分,如"用户中心"、"支付"等
    • 上传或选择所需图标:支持 SVG 格式上传,保持图标质量
    • 设置图标的 font-class 名称:采用"模块_功能"的命名规范,如"user_profile"
    • 添加项目成员:方便团队协作维护
  2. 生成字体文件

    • 点击"生成代码"按钮
    • 选择"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. **生成字体文件**
	
	- 点击生成新的代码
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/7c77ff3793654f0b880cc290fffc3dbe.png)
## 创建图标组件

### 安装依赖
```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 }} 
/>
 
相关推荐
chao_66666621 小时前
React Native + Expo 开发指南:编译、调试、构建全解析
javascript·react native·react.js
_pengliang1 天前
react native ios 2个modal第二个不显示
javascript·react native·react.js
wayne2141 天前
React Native 0.80 学习参考:一个完整可运行的实战项目
学习·react native·react.js
坚果派·白晓明2 天前
Windows 11 OpenHarmony版React Native开发环境搭建完整指南
react native·开源鸿蒙·rnoh
开心不就得了3 天前
React Native对接Sunmi打印sdk
javascript·react native·react.js
Joyee6914 天前
RN 的初版架构——运行时异常与异常捕获处理
react native·前端框架
前端不太难4 天前
RN 列表状态设计 Checklist
react native·list·状态模式
hongkid4 天前
React Native 如何打包正式apk
javascript·react native·react.js
光影少年4 天前
前端如何虚拟列表优化?
前端·react native·react.js
千里马-horse5 天前
Rect Native bridging 源码分析--Bool.h
javascript·c++·react native·react.js·bool