【精通篇】打造React Native鸿蒙跨平台开发高级复合组件库开发系列:Grid 宫格(展示内容或进行页面导航)

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。


在鸿蒙(HarmonyOS)全场景分布式应用生态下,标签(Tag)组件作为信息分类与状态标记的核心UI元素,其跨端开发的核心挑战在于构建统一的样式体系、保证多终端交互一致性,同时兼顾不同设备的视觉适配特性。本文将从组件架构、样式设计、交互逻辑、跨端适配等维度,深度解读这套 React Native 标签组件的技术实现,展现如何构建一套适配手机、平板、智慧屏等鸿蒙全场景设备的标准化标签组件。

组件架构

这套标签组件采用"配置化属性 + 动态样式计算"的核心架构,是 React Native 适配鸿蒙跨端开发的典型实践。组件通过 TypeScript 接口定义完整的属性配置体系,将样式逻辑与渲染逻辑解耦,既保证了代码的可维护性,又便于针对不同鸿蒙终端进行精细化的样式适配。

tsx 复制代码
interface TagProps {
  children: React.ReactNode;
  type?: 'default' | 'primary' | 'success' | 'warning' | 'error' | 'info' | 'secondary';
  size?: 'small' | 'medium' | 'large';
  closable?: boolean;
  onClose?: () => void;
  icon?: keyof typeof TAG_ICONS;
  round?: boolean;
  fill?: boolean;
  outline?: boolean;
  disabled?: boolean;
}

属性设计覆盖了标签组件的全维度配置:type 定义视觉类型,size 控制尺寸规格,round/fill/outline 定义视觉形态,closable/disabled 控制交互状态,icon 支持图标扩展。这种全维度的配置化设计,使得组件无需修改核心逻辑,即可通过属性调整适配鸿蒙不同终端的展示需求------比如智慧屏端可默认使用 large 尺寸,手机端默认使用 medium 尺寸,平板端适配 round 圆角样式。


标签组件的样式系统是跨端适配的核心,其通过 getTagStylegetTextColor 两个核心方法实现动态样式计算,保证在鸿蒙多终端下的视觉一致性。

样式组合逻辑

tsx 复制代码
const getTagStyle = () => {
  const baseStyle = [
    styles.tag,
    styles[`tagSize${size.charAt(0).toUpperCase() + size.slice(1)}`],
    round && styles.tagRound,
    fill && styles.tagFill,
    outline && styles.tagOutline,
    disabled && styles.tagDisabled
  ];
  
  switch (type) {
    case 'primary':
      return [baseStyle, styles.tagPrimary];
    case 'success':
      return [baseStyle, styles.tagSuccess];
    // 其他类型分支...
    default:
      return [baseStyle, styles.tagDefault];
  }
};

样式计算采用分层组合的设计思路:

  1. 基础层styles.tag 定义标签的通用样式(如内边距、对齐方式),保证不同终端的基础形态一致;
  2. 尺寸层 :通过动态拼接样式名(tagSize${size.charAt(0).toUpperCase() + size.slice(1)})适配不同尺寸,这种动态匹配方式避免了大量的条件判断,同时便于扩展新的尺寸规格;
  3. 形态层round/fill/outline 等属性控制标签的视觉形态,可根据鸿蒙不同终端的设计规范灵活调整------比如智慧屏端可强制开启 fill 填充样式提升视觉辨识度;
  4. 状态层disabled 样式层控制禁用状态,保证交互状态的视觉反馈在多终端一致;
  5. 类型层 :不同 type 对应不同的主题样式,通过标准化的色值体系(如 primary 对应 #3b82f6)保证鸿蒙多终端的色彩一致性。

文字颜色

tsx 复制代码
const getTextColor = () => {
  if (disabled) return '#9ca3af';
  
  if (fill || outline) {
    return '#ffffff';
  }
  
  switch (type) {
    // 不同类型的文字颜色匹配...
  }
};

文字颜色的计算逻辑充分考虑了视觉对比度和跨端可读性:

  • 禁用状态统一使用 #9ca3af 浅灰色,保证在鸿蒙不同屏幕亮度下的辨识度;
  • 填充/边框样式下统一使用白色文字,避免因背景色变化导致的可读性问题;
  • 不同类型标签匹配对应的主题文字色值,色值选择遵循 WCAG 对比度标准,适配鸿蒙智慧屏等大屏设备的远距离阅读需求。

标签组件的交互逻辑设计兼顾了触控与非触控终端的使用场景,是适配鸿蒙全场景交互的关键。

可关闭标签:

tsx 复制代码
{closable && !disabled && (
  <TouchableOpacity onPress={onClose} style={styles.tagCloseButton}>
    <Text style={[styles.tagCloseText, { color: getTextColor() }]}>
      ×
    </Text>
  </TouchableOpacity>
)}

可关闭标签的实现体现了跨端交互的核心设计原则:

  1. 状态互斥closable && !disabled 的条件判断保证禁用状态下关闭按钮不可交互,避免无效操作,这在鸿蒙智慧屏的遥控器操作场景中尤为重要------遥控器的焦点交互不允许无效元素抢占焦点;
  2. 原生交互组件 :使用 TouchableOpacity 作为关闭按钮的交互容器,在鸿蒙系统中会被转换为原生可点击组件,保证点击反馈的一致性------手机端的触控反馈、平板端的按压反馈、智慧屏端的焦点反馈均能原生适配;
  3. 极简关闭标识 :使用 × 字符作为关闭图标,避免图片资源在不同终端的适配问题,同时保证视觉辨识度。

图标集成:

tsx 复制代码
{icon && (
  <View style={styles.tagIcon}>
    <Text style={[styles.tagIconText, { color: getTextColor() }]}>
      {String.fromCharCode(parseInt(TAG_ICONS[icon].substring(TAG_ICONS[icon].length - 4, TAG_ICONS[icon].length - 1), 16))}
    </Text>
  </View>
)}

图标集成采用字符编码的轻量化方案,是 React Native 适配鸿蒙跨端的高效实践:

  • 通过 Unicode 字符编码渲染图标,避免了图片资源的多终端适配(如不同分辨率、不同 DPI 的鸿蒙设备);
  • 动态解析 TAG_ICONS 中的编码值,保证图标样式与标签文字颜色的同步,提升视觉统一性;
  • 字符图标相比图片图标具有更小的体积,适配鸿蒙轻量级应用(如原子化服务)的资源限制。

TagComponentApp 主组件中,展示了标签组件与业务逻辑的集成方式,体现了 React Native 鸿蒙跨端开发的最佳实践:

动态标签管理

tsx 复制代码
const [tags, setTags] = useState([/* 初始标签数据 */]);

const removeTag = (id: number) => {
  setTags(tags.filter(tag => tag.id !== id));
};

const addTag = () => {
  const newTag = {
    id: tags.length + 1,
    text: `新标签 ${tags.length + 1}`,
    type: ['default', 'primary', 'success', 'warning', 'error', 'info', 'secondary'][Math.floor(Math.random() * 7)] as /* 类型断言 */
  };
  setTags([...tags, newTag]);
};

动态标签的增删逻辑基于 React useState 实现,完全不依赖平台特定 API,保证了在鸿蒙不同集成模式下的兼容性:

  • 纯 React Native 项目中可直接使用;
  • 鸿蒙 ArkTS 混合开发中可通过状态同步机制对接分布式数据管理;
  • 原子化服务中可适配轻量级状态管理需求。

多场景展示:

组件展示了基础样式、不同尺寸、圆角样式、边框样式、带图标、可关闭、填充样式、禁用状态等全场景的使用方式,覆盖了鸿蒙手机、平板、智慧屏的典型使用场景:

  • 手机端:展示紧凑的标签列表,支持触控关闭;
  • 平板端:适配中等尺寸标签,强化圆角和填充样式提升视觉体验;
  • 智慧屏端:使用大尺寸标签,开启 fill 填充样式提升远距离辨识度,禁用关闭按钮避免误操作。

这套标签组件在 React Native 适配鸿蒙的过程中,贯穿了以下核心优化思路:

DP 单位与相对尺寸

组件所有尺寸均采用 React Native 原生的 DP 单位(设备无关像素),而非像素单位,保证在不同屏幕密度的鸿蒙设备上视觉大小一致。比如 small/medium/large 尺寸的定义基于 DP 单位,在手机(160DPI)、平板(240DPI)、智慧屏(320DPI)上均能保持设计预期的大小比例。

交互

标签组件的交互设计采用极简的反馈方式(如关闭按钮的点击反馈仅依赖 TouchableOpacity 的默认透明度变化),避免了复杂动画在鸿蒙低性能设备上的卡顿问题,同时符合鸿蒙系统的交互设计规范。

状态可视化

禁用状态、不同类型标签的视觉差异足够明显,适配鸿蒙智慧屏等远距离观看的场景,保证用户能快速识别标签的状态和类型。

资源

组件不依赖外部图片、字体等资源,仅通过代码实现所有视觉效果,避免了鸿蒙分布式应用中资源同步的复杂问题,同时降低了应用包体积。

总结

这套 React Native 标签组件不仅实现了多类型、多尺寸、多形态的标签展示能力,更构建了一套完整的鸿蒙跨端适配体系。核心要点可总结为:

  • 架构层面:全维度配置化属性设计,样式与渲染解耦,适配鸿蒙多终端的展示需求;
  • 样式层面:动态分层的样式计算体系,保证多终端视觉统一与可扩展性;
  • 交互层面:原生交互组件 + 状态互斥控制,适配鸿蒙触控/非触控终端的交互习惯;
  • 资源层面:字符图标替代图片资源,降低跨端资源适配成本。

在实际的鸿蒙跨端项目中,可基于该组件进一步扩展,比如支持鸿蒙系统的深色模式适配、对接分布式数据管理实现多设备标签状态同步、适配智慧屏的焦点导航逻辑,充分发挥 React Native "一次开发,多端部署"的技术优势,构建真正适配鸿蒙全场景生态的标签组件。


本文将深入分析一个功能丰富的 React Native 标签组件实现,该组件采用了现代化的函数式组件架构,支持多种样式、尺寸和交互模式,同时兼顾了 React Native 与 HarmonyOS 的跨端兼容性。

接口设计

组件首先通过 TypeScript 接口定义了核心配置选项:

typescript 复制代码
interface TagProps {
  children: React.ReactNode;
  type?: 'default' | 'primary' | 'success' | 'warning' | 'error' | 'info' | 'secondary';
  size?: 'small' | 'medium' | 'large';
  closable?: boolean;
  onClose?: () => void;
  icon?: keyof typeof TAG_ICONS;
  round?: boolean;
  fill?: boolean;
  outline?: boolean;
  disabled?: boolean;
}

这种类型定义方式体现了良好的 TypeScript 实践,通过可选属性和字面量类型,提供了清晰的配置选项和类型约束,确保了组件使用时的类型安全。

核心实现

标签组件的核心实现逻辑主要体现在 getTagStylegetTextColor 两个函数中:

typescript 复制代码
const getTagStyle = () => {
  const baseStyle = [
    styles.tag,
    styles[`tagSize${size.charAt(0).toUpperCase() + size.slice(1)}`],
    round && styles.tagRound,
    fill && styles.tagFill,
    outline && styles.tagOutline,
    disabled && styles.tagDisabled
  ];
  
  switch (type) {
    case 'primary':
      return [baseStyle, styles.tagPrimary];
    case 'success':
      return [baseStyle, styles.tagSuccess];
    case 'warning':
      return [baseStyle, styles.tagWarning];
    case 'error':
      return [baseStyle, styles.tagError];
    case 'info':
      return [baseStyle, styles.tagInfo];
    case 'secondary':
      return [baseStyle, styles.tagSecondary];
    default:
      return [baseStyle, styles.tagDefault];
  }
};

const getTextColor = () => {
  if (disabled) return '#9ca3af';
  
  if (fill || outline) {
    return '#ffffff';
  }
  
  switch (type) {
    case 'primary':
      return '#3b82f6';
    case 'success':
      return '#10b981';
    case 'warning':
      return '#f59e0b';
    case 'error':
      return '#ef4444';
    case 'info':
      return '#60a5fa';
    case 'secondary':
      return '#6b7280';
    default:
      return '#6b7280';
  }
};

这两个函数的技术要点:

  1. 动态样式计算:根据组件的 props 动态计算标签的样式和文本颜色
  2. 样式组合:通过数组的方式组合多个样式,实现样式的灵活叠加
  3. 条件样式:根据不同的 props 值(如 round、fill、outline、disabled)添加对应的样式
  4. 类型驱动:根据 type 属性确定标签的主题色和文本颜色
  5. 状态感知:考虑 disabled 状态对样式和颜色的影响

组件渲染

标签组件的渲染逻辑清晰明了:

typescript 复制代码
return (
  <View style={getTagStyle()}>
    {icon && (
      <View style={styles.tagIcon}>
        <Text style={[styles.tagIconText, { color: getTextColor() }]}>
          {String.fromCharCode(parseInt(TAG_ICONS[icon].substring(TAG_ICONS[icon].length - 4, TAG_ICONS[icon].length - 1), 16))}
        </Text>
      </View>
    )}
    <Text style={[styles.tagText, { color: getTextColor() }]}>{children}</Text>
    {closable && !disabled && (
      <TouchableOpacity onPress={onClose} style={styles.tagCloseButton}>
        <Text style={[styles.tagCloseText, { color: getTextColor() }]}>
          ×
        </Text>
      </TouchableOpacity>
    )}
  </View>
);

渲染逻辑的技术要点:

  1. 条件渲染:根据 icon 和 closable 属性条件渲染图标和关闭按钮
  2. 图标处理 :通过 String.fromCharCodeparseInt 将图标代码转换为实际显示的图标
  3. 事件处理:为关闭按钮添加点击事件,触发 onClose 回调
  4. 状态禁用:当 disabled 为 true 时,禁用关闭按钮的渲染和交互

主应用

主应用组件展示了标签组件的使用示例:

typescript 复制代码
const TagComponentApp = () => {
  const [tags, setTags] = useState([
    { id: 1, text: '默认标签', type: 'default' },
    { id: 2, text: '主要标签', type: 'primary' },
    { id: 3, text: '成功标签', type: 'success' },
    { id: 4, text: '警告标签', type: 'warning' },
    { id: 5, text: '错误标签', type: 'error' },
    { id: 6, text: '信息标签', type: 'info' },
    { id: 7, text: '次要标签', type: 'secondary' },
  ]);

  const removeTag = (id: number) => {
    setTags(tags.filter(tag => tag.id !== id));
  };

  const addTag = () => {
    const newTag = {
      id: tags.length + 1,
      text: `新标签 ${tags.length + 1}`,
      type: ['default', 'primary', 'success', 'warning', 'error', 'info', 'secondary'][Math.floor(Math.random() * 7)] as 'default' | 'primary' | 'success' | 'warning' | 'error' | 'info' | 'secondary'
    };
    setTags([...tags, newTag]);
  };

  // 渲染逻辑
};

主应用组件的技术要点:

  1. 状态管理:使用 useState 管理标签列表的状态
  2. 动态标签:实现了添加和删除标签的功能
  3. 类型安全:通过类型断言确保新标签的 type 属性类型正确
  4. 示例展示:展示了标签组件的各种使用场景,包括不同类型、尺寸、样式的标签

标签组件的样式系统设计灵活,支持多种样式组合:

  1. 基础样式:定义标签的基本结构和布局
  2. 尺寸样式:支持 small、medium、large 三种尺寸
  3. 类型样式:支持 default、primary、success、warning、error、info、secondary 七种类型
  4. 修饰样式:支持 round(圆角)、fill(填充)、outline(轮廓)等修饰效果
  5. 状态样式:支持 disabled 状态的样式

样式计算

标签组件通过 getTagStyle 函数动态计算样式,实现了样式的灵活组合:

  1. 基础样式组合:将基础样式、尺寸样式、修饰样式和状态样式组合在一起
  2. 类型样式叠加:根据 type 属性添加对应的类型样式,实现不同主题色的标签
  3. 条件样式:根据 props 的值条件性地添加样式,如 round、fill、outline、disabled 等

文本颜色计算

标签组件通过 getTextColor 函数动态计算文本颜色,确保文本在不同样式下的可读性:

  1. 禁用状态:禁用状态下文本颜色固定为灰色
  2. 填充和轮廓样式:填充和轮廓样式下文本颜色固定为白色
  3. 类型驱动:根据 type 属性确定文本颜色,与标签的主题色匹配

标签组件实现了丰富的功能特性:

  1. 多种类型:支持 default、primary、success、warning、error、info、secondary 七种类型,对应不同的主题色
  2. 多种尺寸:支持 small、medium、large 三种尺寸,适应不同的布局需求
  3. 丰富样式:支持 round(圆角)、fill(填充)、outline(轮廓)等样式修饰
  4. 可关闭:支持 closable 属性,实现可关闭的标签
  5. 图标支持:支持通过 icon 属性添加图标,增强标签的视觉效果
  6. 禁用状态:支持 disabled 属性,实现禁用状态的标签
  7. 灵活组合:各种属性可以灵活组合,实现多样化的标签效果

标签组件的关闭功能通过以下代码实现:

typescript 复制代码
{closable && !disabled && (
  <TouchableOpacity onPress={onClose} style={styles.tagCloseButton}>
    <Text style={[styles.tagCloseText, { color: getTextColor() }]}>
      ×
    </Text>
  </TouchableOpacity>
)}

这种实现方式的技术要点:

  1. 条件渲染:只有当 closable 为 true 且 disabled 为 false 时才渲染关闭按钮
  2. 事件处理:为关闭按钮添加点击事件,触发 onClose 回调
  3. 样式同步:关闭按钮的文本颜色与标签的文本颜色保持一致,确保视觉效果的统一

实际应用示例

基础标签示例

typescript 复制代码
<Tag type="default">默认</Tag>
<Tag type="primary">主要</Tag>
<Tag type="success">成功</Tag>
<Tag type="warning">警告</Tag>
<Tag type="error">错误</Tag>
<Tag type="info">信息</Tag>
<Tag type="secondary">次要</Tag>

不同尺寸示例

typescript 复制代码
<Tag type="primary" size="small">小标签</Tag>
<Tag type="success" size="medium">中标签</Tag>
<Tag type="warning" size="large">大标签</Tag>

特殊样式示例

typescript 复制代码
<Tag type="primary" round>圆角标签</Tag>
<Tag type="success" round fill>填充圆角</Tag>
<Tag type="warning" round size="large">大圆角</Tag>
<Tag type="primary" outline>边框样式</Tag>

带图标示例

typescript 复制代码
<Tag type="success" icon="check">已完成</Tag>
<Tag type="warning" icon="warning">注意</Tag>
<Tag type="info" icon="info">提示</Tag>
<Tag type="error" icon="error">错误</Tag>
<Tag type="primary" icon="star">收藏</Tag>
<Tag type="secondary" icon="bell">通知</Tag>

可关闭标签示例

typescript 复制代码
<Tag type="primary" closable onClose={() => console.log('关闭标签')}>可关闭标签</Tag>

总结

本文分析的 React Native 标签组件展示了如何实现一个功能丰富、灵活可配置的标签组件。该组件采用了现代化的 React 开发实践,通过 TypeScript 类型系统、动态样式计算和灵活的属性配置,实现了多样化的标签效果。

组件的技术亮点包括:

  1. 丰富的配置选项:支持多种类型、尺寸、样式和交互模式
  2. 灵活的样式系统:通过动态样式计算,实现了样式的灵活组合
  3. 类型安全:充分利用 TypeScript 的类型系统,确保组件使用时的类型安全
  4. 跨端兼容性:在设计时充分考虑了 React Native 和 HarmonyOS 的跨端兼容性
  5. 良好的可维护性:代码结构清晰,逻辑分明,易于理解和维护

该组件可以广泛应用于分类标记、状态指示、筛选条件、标签云、通知标记、用户标签等场景,为应用提供了直观、美观的标签功能。通过本文的技术解读,希望能够为 React Native 跨端开发提供有益的参考,帮助开发者构建更高质量、更具用户友好性的移动应用。


真实演示案例代码:

js 复制代码
import React, { useState } from 'react';
import { View, Text, StyleSheet, ScrollView, Dimensions, TouchableOpacity } from 'react-native';

// Base64 Icons for Tag component
const TAG_ICONS = {
  check: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggIGQ9Ik05IDE2LjVMMTMuNSAxMiAxNSAxMy41TDEwIDE4LjVMMTAgNi41IiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K',
  close: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggIGQ9Ik0xOCA2TDYgMTgiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPHBhdGggIGQ9Ik02IDZMMTggMTgiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=',
  info: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggIGQ9Ik0xMiAyQzYuNDggMiAyIDYuNDggMiAxMnM0LjQ4IDEwIDEwIDEwIDEwLTQuNDggMTAtMTFTMTcuNTIgMiAxMiAyWiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggIGQ9Ik0xMiAxNnYtNCIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggIGQ9Ik0xMiA4aDAuMDEiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=',
  warning: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggIGQ9Ik0xMiAyTDMgMjFoMTJsOS0xOVoiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgPHBhdGggIGQ9Ik0xMiAxNVYxMSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggIGQ9Ik0xMiA4aDAuMDEiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=',
  success: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggIGQ9Ik05IDE2LjVMMTMuNSAxMiAxNSAxMy41TDEwIDE4LjVMMTAgNi41IiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8cGF0aCBkPSJNMTIgMkM2LjQ4IDIgMiA2LjQ4IDIgMTJzNC40OCAxMCAxMCAxMCAxMC00LjQ4IDEwLTEwUzE3LjUyIDIgMTIgMloiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=',
  error: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggIGQ9Ik0xMiAyQzYuNDggMiAyIDYuNDggMiAxMnM0LjQ4IDEwIDEwIDEwIDEwLTQuNDggMTAtMTFTMTcuNTIgMiAxMiAyWiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggIGQ9Ik0xNSA5TDEyIDEyIDkgOSIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cg==',
  star: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggIGQ9Ik0xMiAyLjVMMTQuMDkgNy41MkwxOS41IDEwLjQ4TDE1LjkxIDEzLjQ0TDE3LjE4IDE5LjA5TDEyIDE2LjIzTDYuODIgMTkuMDlMOC4wOSAxMy40NEwyLjY4IDEwLjQ4TDguMDkgNy41MkwxMiAyLjVaIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K',
  heart: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggIGQ9Ik0xMiAyMS41TDEgMTEuMkMxLjYyIDkuNTIgMy40IDguMjUgNS40MiA3LjcxQzcuNDUgNy4xNyA5LjYzIDcuNCAxMS40NSA4LjNMIDEyIDguNThMMTIuNTUgOC4zQzE0LjM3IDcuNCAxNi41NSA3LjE3IDE4LjU4IDcuNzFDMjAuNiA4LjI1IDIyLjM4IDkuNTIgMjMuMDEgMTEuMkwxMiAyMS41WiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cg==',
  bell: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggIGQ9Ik0yMSAxMWE4IDggMCAwIDAtLTE2IDBjMCA0LjUgMy41IDguMDYgNy45NCA4LjJhMS4xIDEuMSAwIDAgMCAuMTIgMGMyLjI0LS4wNSA0LjAzLjkzIDUuMjktMi41NEMxOS4xNiAxOC4wOSAyMSAxNS4yNSAyMSAxMUE5IDkgMCAwIDAgMTIgMkExIDAgMCAwIDAgMTIgM1Y1QTUgNSAwIDAgMSAxMCAxMEE1IDUgMCAwIDEgMTIgM0gyMUMyMSA1LjI1IDIxIDggMjEgMTF6IiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K'
};

// Tag Component
interface TagProps {
  children: React.ReactNode;
  type?: 'default' | 'primary' | 'success' | 'warning' | 'error' | 'info' | 'secondary';
  size?: 'small' | 'medium' | 'large';
  closable?: boolean;
  onClose?: () => void;
  icon?: keyof typeof TAG_ICONS;
  round?: boolean;
  fill?: boolean;
  outline?: boolean;
  disabled?: boolean;
}

const Tag: React.FC<TagProps> = ({ 
  children, 
  type = 'default', 
  size = 'medium', 
  closable = false, 
  onClose, 
  icon, 
  round = false,
  fill = false,
  outline = false,
  disabled = false
}) => {
  const getTagStyle = () => {
    const baseStyle = [
      styles.tag,
      styles[`tagSize${size.charAt(0).toUpperCase() + size.slice(1)}`],
      round && styles.tagRound,
      fill && styles.tagFill,
      outline && styles.tagOutline,
      disabled && styles.tagDisabled
    ];
    
    switch (type) {
      case 'primary':
        return [baseStyle, styles.tagPrimary];
      case 'success':
        return [baseStyle, styles.tagSuccess];
      case 'warning':
        return [baseStyle, styles.tagWarning];
      case 'error':
        return [baseStyle, styles.tagError];
      case 'info':
        return [baseStyle, styles.tagInfo];
      case 'secondary':
        return [baseStyle, styles.tagSecondary];
      default:
        return [baseStyle, styles.tagDefault];
    }
  };

  const getTextColor = () => {
    if (disabled) return '#9ca3af';
    
    if (fill || outline) {
      return '#ffffff';
    }
    
    switch (type) {
      case 'primary':
        return '#3b82f6';
      case 'success':
        return '#10b981';
      case 'warning':
        return '#f59e0b';
      case 'error':
        return '#ef4444';
      case 'info':
        return '#60a5fa';
      case 'secondary':
        return '#6b7280';
      default:
        return '#6b7280';
    }
  };

  return (
    <View style={getTagStyle()}>
      {icon && (
        <View style={styles.tagIcon}>
          <Text style={[styles.tagIconText, { color: getTextColor() }]}>
            {String.fromCharCode(parseInt(TAG_ICONS[icon].substring(TAG_ICONS[icon].length - 4, TAG_ICONS[icon].length - 1), 16))}
          </Text>
        </View>
      )}
      <Text style={[styles.tagText, { color: getTextColor() }]}>{children}</Text>
      {closable && !disabled && (
        <TouchableOpacity onPress={onClose} style={styles.tagCloseButton}>
          <Text style={[styles.tagCloseText, { color: getTextColor() }]}>
            ×
          </Text>
        </TouchableOpacity>
      )}
    </View>
  );
};

// Main App Component
const TagComponentApp = () => {
  const [tags, setTags] = useState([
    { id: 1, text: '默认标签', type: 'default' },
    { id: 2, text: '主要标签', type: 'primary' },
    { id: 3, text: '成功标签', type: 'success' },
    { id: 4, text: '警告标签', type: 'warning' },
    { id: 5, text: '错误标签', type: 'error' },
    { id: 6, text: '信息标签', type: 'info' },
    { id: 7, text: '次要标签', type: 'secondary' },
  ]);

  const removeTag = (id: number) => {
    setTags(tags.filter(tag => tag.id !== id));
  };

  const addTag = () => {
    const newTag = {
      id: tags.length + 1,
      text: `新标签 ${tags.length + 1}`,
      type: ['default', 'primary', 'success', 'warning', 'error', 'info', 'secondary'][Math.floor(Math.random() * 7)] as 'default' | 'primary' | 'success' | 'warning' | 'error' | 'info' | 'secondary'
    };
    setTags([...tags, newTag]);
  };

  return (
    <View style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.headerTitle}>标签组件</Text>
        <Text style={styles.headerSubtitle}>通过type属性控制标签颜色</Text>
      </View>
      
      <ScrollView contentContainerStyle={styles.content}>
        <View style={styles.tagsSection}>
          <Text style={styles.sectionTitle}>基础标签</Text>
          <View style={styles.tagRow}>
            <Tag type="default">默认</Tag>
            <Tag type="primary">主要</Tag>
            <Tag type="success">成功</Tag>
            <Tag type="warning">警告</Tag>
            <Tag type="error">错误</Tag>
            <Tag type="info">信息</Tag>
            <Tag type="secondary">次要</Tag>
          </View>
        </View>
        
        <View style={styles.tagsSection}>
          <Text style={styles.sectionTitle}>不同尺寸</Text>
          <View style={styles.tagRow}>
            <Tag type="primary" size="small">小标签</Tag>
            <Tag type="success" size="medium">中标签</Tag>
            <Tag type="warning" size="large">大标签</Tag>
          </View>
        </View>
        
        <View style={styles.tagsSection}>
          <Text style={styles.sectionTitle}>圆角标签</Text>
          <View style={styles.tagRow}>
            <Tag type="primary" round>圆角标签</Tag>
            <Tag type="success" round fill>填充圆角</Tag>
            <Tag type="warning" round size="large">大圆角</Tag>
          </View>
        </View>
        
        <View style={styles.tagsSection}>
          <Text style={styles.sectionTitle}>边框样式</Text>
          <View style={styles.tagRow}>
            <Tag type="primary" outline>边框样式</Tag>
            <Tag type="success" outline>成功边框</Tag>
            <Tag type="warning" outline>警告边框</Tag>
            <Tag type="error" outline>错误边框</Tag>
          </View>
        </View>
        
        <View style={styles.tagsSection}>
          <Text style={styles.sectionTitle}>带图标的标签</Text>
          <View style={styles.tagRow}>
            <Tag type="success" icon="check">已完成</Tag>
            <Tag type="warning" icon="warning">注意</Tag>
            <Tag type="info" icon="info">提示</Tag>
            <Tag type="error" icon="error">错误</Tag>
            <Tag type="primary" icon="star">收藏</Tag>
            <Tag type="secondary" icon="bell">通知</Tag>
          </View>
        </View>
        
        <View style={styles.tagsSection}>
          <Text style={styles.sectionTitle}>可关闭标签</Text>
          <View style={styles.tagRow}>
            {tags.map((tag) => (
              <Tag 
                key={tag.id} 
                type={tag.type as any} 
                closable 
                onClose={() => removeTag(tag.id)}
              >
                {tag.text}
              </Tag>
            ))}
          </View>
        </View>
        
        <View style={styles.tagsSection}>
          <Text style={styles.sectionTitle}>填充样式</Text>
          <View style={styles.tagRow}>
            <Tag type="primary" fill>主要填充</Tag>
            <Tag type="success" fill>成功填充</Tag>
            <Tag type="warning" fill>警告填充</Tag>
            <Tag type="error" fill>错误填充</Tag>
            <Tag type="info" fill>信息填充</Tag>
            <Tag type="secondary" fill>次要填充</Tag>
          </View>
        </View>
        
        <View style={styles.tagsSection}>
          <Text style={styles.sectionTitle}>禁用状态</Text>
          <View style={styles.tagRow}>
            <Tag type="primary" disabled>禁用标签</Tag>
            <Tag type="success" disabled icon="check">禁用成功</Tag>
            <Tag type="warning" disabled round>禁用圆角</Tag>
            <Tag type="error" disabled fill>禁用填充</Tag>
          </View>
        </View>
        
        <TouchableOpacity style={styles.addButton} onPress={addTag}>
          <Text style={styles.addButtonText}>添加标签</Text>
        </TouchableOpacity>
        
        <View style={styles.featuresSection}>
          <Text style={styles.featuresTitle}>功能特性</Text>
          <View style={styles.featureList}>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}>•</Text>
              <Text style={styles.featureText}>多种类型样式</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}>•</Text>
              <Text style={styles.featureText}>不同尺寸规格</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}>•</Text>
              <Text style={styles.featureText}>圆角和填充样式</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}>•</Text>
              <Text style={styles.featureText}>图标和关闭按钮</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}>•</Text>
              <Text style={styles.featureText}>丰富的Base64图标</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}>•</Text>
              <Text style={styles.featureText}>边框样式</Text>
            </View>
            <View style={styles.featureItem}>
              <Text style={styles.featureBullet}>•</Text>
              <Text style={styles.featureText}>禁用状态</Text>
            </View>
          </View>
        </View>
        
        <View style={styles.usageSection}>
          <Text style={styles.usageTitle}>使用说明</Text>
          <Text style={styles.usageText}>
            标签组件用于标记和分类信息,通过type属性控制不同的颜色样式,
            适用于状态标记、分类标签、筛选条件等场景。
          </Text>
        </View>
      </ScrollView>
      
      <View style={styles.footer}>
        <Text style={styles.footerText}>© 2023 标签组件 | 现代化UI组件库</Text>
      </View>
    </View>
  );
};

const { width, height } = Dimensions.get('window');

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f3f4f6',
  },
  header: {
    backgroundColor: '#1f2937',
    paddingTop: 30,
    paddingBottom: 25,
    paddingHorizontal: 20,
    borderBottomWidth: 1,
    borderBottomColor: '#374151',
  },
  headerTitle: {
    fontSize: 28,
    fontWeight: '700',
    color: '#f9fafb',
    textAlign: 'center',
    marginBottom: 5,
  },
  headerSubtitle: {
    fontSize: 16,
    color: '#d1d5db',
    textAlign: 'center',
  },
  content: {
    padding: 20,
  },
  tagsSection: {
    marginBottom: 25,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#1f2937',
    marginBottom: 15,
  },
  tagRow: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    gap: 10,
  },
  tag: {
    flexDirection: 'row',
    alignItems: 'center',
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 4,
    borderWidth: 1,
    borderColor: 'transparent',
    marginRight: 8,
    marginBottom: 8,
  },
  tagSizeSmall: {
    paddingHorizontal: 8,
    paddingVertical: 4,
    borderRadius: 3,
  },
  tagSizeMedium: {
    paddingHorizontal: 12,
    paddingVertical: 6,
    borderRadius: 4,
  },
  tagSizeLarge: {
    paddingHorizontal: 16,
    paddingVertical: 8,
    borderRadius: 5,
  },
  tagRound: {
    borderRadius: 20,
  },
  tagFill: {
    borderColor: 'transparent',
  },
  tagOutline: {
    backgroundColor: 'transparent',
  },
  tagDisabled: {
    opacity: 0.5,
  },
  tagDefault: {
    backgroundColor: '#f9fafb',
    borderColor: '#e5e7eb',
  },
  tagPrimary: {
    backgroundColor: '#eff6ff',
    borderColor: '#3b82f6',
  },
  tagSuccess: {
    backgroundColor: '#ecfdf5',
    borderColor: '#10b981',
  },
  tagWarning: {
    backgroundColor: '#fef3c7',
    borderColor: '#f59e0b',
  },
  tagError: {
    backgroundColor: '#fef2f2',
    borderColor: '#ef4444',
  },
  tagInfo: {
    backgroundColor: '#eff6ff',
    borderColor: '#60a5fa',
  },
  tagSecondary: {
    backgroundColor: '#f3f4f6',
    borderColor: '#9ca3af',
  },
  tagText: {
    fontSize: 14,
    fontWeight: '500',
  },
  tagIcon: {
    marginRight: 4,
  },
  tagIconText: {
    fontSize: 14,
  },
  tagCloseButton: {
    marginLeft: 6,
    padding: 2,
  },
  tagCloseText: {
    fontSize: 16,
    fontWeight: 'bold',
  },
  addButton: {
    backgroundColor: '#3b82f6',
    paddingVertical: 14,
    borderRadius: 8,
    alignItems: 'center',
    marginBottom: 25,
  },
  addButtonText: {
    color: '#ffffff',
    fontWeight: '600',
    fontSize: 16,
  },
  featuresSection: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 20,
    marginBottom: 25,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.08,
    shadowRadius: 2,
  },
  featuresTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#1f2937',
    marginBottom: 15,
  },
  featureList: {
    paddingLeft: 15,
  },
  featureItem: {
    flexDirection: 'row',
    marginBottom: 10,
  },
  featureBullet: {
    fontSize: 16,
    color: '#3b82f6',
    marginRight: 8,
  },
  featureText: {
    fontSize: 16,
    color: '#6b7280',
    flex: 1,
  },
  usageSection: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 20,
    marginBottom: 30,
    elevation: 2,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.08,
    shadowRadius: 2,
  },
  usageTitle: {
    fontSize: 18,
    fontWeight: '600',
    color: '#1f2937',
    marginBottom: 10,
  },
  usageText: {
    fontSize: 16,
    color: '#6b7280',
    lineHeight: 24,
  },
  footer: {
    paddingVertical: 20,
    alignItems: 'center',
    backgroundColor: '#1f2937',
  },
  footerText: {
    color: '#d1d5db',
    fontSize: 14,
  },
});

export default TagComponentApp;

打包

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

最后运行效果图如下显示:

本文深入解析了基于React Native的鸿蒙跨平台标签组件实现方案。组件采用"配置化属性+动态样式计算"架构,通过TypeScript接口定义全维度属性配置体系,支持多类型、多尺寸、多形态的标签展示。核心亮点包括:分层组合的样式计算逻辑确保跨端视觉一致性;原生交互组件适配不同终端交互方式;字符图标方案避免资源适配问题;DP单位保证尺寸一致性。该组件实现了从手机、平板到智慧屏的全场景适配,为鸿蒙生态提供了一套标准化、高性能的标签组件解决方案。

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

相关推荐
夕除1 小时前
js-20
开发语言·javascript·windows
@––––––1 小时前
力扣hot100—系列8-回溯算法
javascript·算法·leetcode
phltxy2 小时前
Vue核心进阶:v-model深度解析+ref+nextTick实战
前端·javascript·vue.js
木斯佳2 小时前
HarmonyOS实战(解决方案篇)—企业AI资产利旧:如何将已有智能体快速接入鸿蒙生态
人工智能·华为·harmonyos
三小河2 小时前
React 样式——styled-components
前端·javascript·后端
随逸1772 小时前
《React 入门实战:从零搭建 TodoList》
react.js
Jing_Rainbow2 小时前
【React-10/Lesson94(2026-01-04)】React 性能优化专题:useMemo & useCallback 深度解析🚀
前端·javascript·react.js
白中白121382 小时前
Vue系列-3
前端·javascript·vue.js
前端不太难2 小时前
三层解耦之后,鸿蒙 App 的真正瓶颈
华为·状态模式·harmonyos