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 }} 
/>
 
相关推荐
song5018 小时前
React Native for OpenHarmony 实战:三方库 react-native-css-transformer 的鸿蒙化适配指南
css·人工智能·深度学习·react native·transformer
enjoywindstorm3 天前
移动端原生框架对比评测
react native
传奇开心果编程3 天前
【声明式UI开发实用技术学与练】第8课 状态提升与下放
学习·flutter·react native·ui·swiftui·composer
传奇开心果编程4 天前
【现代声明式UI学与练】第4课 列表渲染与 key——如何高效渲染列表、key 的作用、列表重排时的状态保持
学习·flutter·react native·ui·swiftui·android jetpack
传奇开心果编程5 天前
【现代声明式UI学与练】第7课 组件通信——父传子、子传父、跨层级通信、状态提升与 Context
学习·flutter·react native·ui·swiftui·android jetpack
500845 天前
React Native for OpenHarmony 实战:三方库 react-native-url-polyfill 的鸿蒙化适配指南
javascript·react native·react.js·性能优化·electron·harmonyos
传奇开心果编程5 天前
【现代声明式UI学与练】第9课 性能优化——渲染优化、列表优化、内存优化、启动优化
学习·flutter·react native·ui·性能优化·swiftui·android jetpack
500845 天前
React Native for OpenHarmony 实战:三方库 react-native-volume-control 的鸿蒙化适配指南
javascript·react native·react.js·electron·harmonyos
5008410 天前
React Native for OpenHarmony 实战:三方库 react-native-crypto-js 的鸿蒙化适配指南
javascript·react native·性能优化·electron·harmonyos
5008410 天前
React Native for OpenHarmony 实战:三方库 react-native-device-name 的鸿蒙化适配指南
深度学习·react native·react.js·机器学习·harmonyos