欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
在鸿蒙(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 圆角样式。
标签组件的样式系统是跨端适配的核心,其通过 getTagStyle 和 getTextColor 两个核心方法实现动态样式计算,保证在鸿蒙多终端下的视觉一致性。
样式组合逻辑
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];
}
};
样式计算采用分层组合的设计思路:
- 基础层 :
styles.tag定义标签的通用样式(如内边距、对齐方式),保证不同终端的基础形态一致; - 尺寸层 :通过动态拼接样式名(
tagSize${size.charAt(0).toUpperCase() + size.slice(1)})适配不同尺寸,这种动态匹配方式避免了大量的条件判断,同时便于扩展新的尺寸规格; - 形态层 :
round/fill/outline等属性控制标签的视觉形态,可根据鸿蒙不同终端的设计规范灵活调整------比如智慧屏端可强制开启fill填充样式提升视觉辨识度; - 状态层 :
disabled样式层控制禁用状态,保证交互状态的视觉反馈在多终端一致; - 类型层 :不同
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>
)}
可关闭标签的实现体现了跨端交互的核心设计原则:
- 状态互斥 :
closable && !disabled的条件判断保证禁用状态下关闭按钮不可交互,避免无效操作,这在鸿蒙智慧屏的遥控器操作场景中尤为重要------遥控器的焦点交互不允许无效元素抢占焦点; - 原生交互组件 :使用
TouchableOpacity作为关闭按钮的交互容器,在鸿蒙系统中会被转换为原生可点击组件,保证点击反馈的一致性------手机端的触控反馈、平板端的按压反馈、智慧屏端的焦点反馈均能原生适配; - 极简关闭标识 :使用
×字符作为关闭图标,避免图片资源在不同终端的适配问题,同时保证视觉辨识度。
图标集成:
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 实践,通过可选属性和字面量类型,提供了清晰的配置选项和类型约束,确保了组件使用时的类型安全。
核心实现
标签组件的核心实现逻辑主要体现在 getTagStyle 和 getTextColor 两个函数中:
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';
}
};
这两个函数的技术要点:
- 动态样式计算:根据组件的 props 动态计算标签的样式和文本颜色
- 样式组合:通过数组的方式组合多个样式,实现样式的灵活叠加
- 条件样式:根据不同的 props 值(如 round、fill、outline、disabled)添加对应的样式
- 类型驱动:根据 type 属性确定标签的主题色和文本颜色
- 状态感知:考虑 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>
);
渲染逻辑的技术要点:
- 条件渲染:根据 icon 和 closable 属性条件渲染图标和关闭按钮
- 图标处理 :通过
String.fromCharCode和parseInt将图标代码转换为实际显示的图标 - 事件处理:为关闭按钮添加点击事件,触发 onClose 回调
- 状态禁用:当 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]);
};
// 渲染逻辑
};
主应用组件的技术要点:
- 状态管理:使用 useState 管理标签列表的状态
- 动态标签:实现了添加和删除标签的功能
- 类型安全:通过类型断言确保新标签的 type 属性类型正确
- 示例展示:展示了标签组件的各种使用场景,包括不同类型、尺寸、样式的标签
标签组件的样式系统设计灵活,支持多种样式组合:
- 基础样式:定义标签的基本结构和布局
- 尺寸样式:支持 small、medium、large 三种尺寸
- 类型样式:支持 default、primary、success、warning、error、info、secondary 七种类型
- 修饰样式:支持 round(圆角)、fill(填充)、outline(轮廓)等修饰效果
- 状态样式:支持 disabled 状态的样式
样式计算
标签组件通过 getTagStyle 函数动态计算样式,实现了样式的灵活组合:
- 基础样式组合:将基础样式、尺寸样式、修饰样式和状态样式组合在一起
- 类型样式叠加:根据 type 属性添加对应的类型样式,实现不同主题色的标签
- 条件样式:根据 props 的值条件性地添加样式,如 round、fill、outline、disabled 等
文本颜色计算
标签组件通过 getTextColor 函数动态计算文本颜色,确保文本在不同样式下的可读性:
- 禁用状态:禁用状态下文本颜色固定为灰色
- 填充和轮廓样式:填充和轮廓样式下文本颜色固定为白色
- 类型驱动:根据 type 属性确定文本颜色,与标签的主题色匹配
标签组件实现了丰富的功能特性:
- 多种类型:支持 default、primary、success、warning、error、info、secondary 七种类型,对应不同的主题色
- 多种尺寸:支持 small、medium、large 三种尺寸,适应不同的布局需求
- 丰富样式:支持 round(圆角)、fill(填充)、outline(轮廓)等样式修饰
- 可关闭:支持 closable 属性,实现可关闭的标签
- 图标支持:支持通过 icon 属性添加图标,增强标签的视觉效果
- 禁用状态:支持 disabled 属性,实现禁用状态的标签
- 灵活组合:各种属性可以灵活组合,实现多样化的标签效果
标签组件的关闭功能通过以下代码实现:
typescript
{closable && !disabled && (
<TouchableOpacity onPress={onClose} style={styles.tagCloseButton}>
<Text style={[styles.tagCloseText, { color: getTextColor() }]}>
×
</Text>
</TouchableOpacity>
)}
这种实现方式的技术要点:
- 条件渲染:只有当 closable 为 true 且 disabled 为 false 时才渲染关闭按钮
- 事件处理:为关闭按钮添加点击事件,触发 onClose 回调
- 样式同步:关闭按钮的文本颜色与标签的文本颜色保持一致,确保视觉效果的统一
实际应用示例
基础标签示例
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 类型系统、动态样式计算和灵活的属性配置,实现了多样化的标签效果。
组件的技术亮点包括:
- 丰富的配置选项:支持多种类型、尺寸、样式和交互模式
- 灵活的样式系统:通过动态样式计算,实现了样式的灵活组合
- 类型安全:充分利用 TypeScript 的类型系统,确保组件使用时的类型安全
- 跨端兼容性:在设计时充分考虑了 React Native 和 HarmonyOS 的跨端兼容性
- 良好的可维护性:代码结构清晰,逻辑分明,易于理解和维护
该组件可以广泛应用于分类标记、状态指示、筛选条件、标签云、通知标记、用户标签等场景,为应用提供了直观、美观的标签功能。通过本文的技术解读,希望能够为 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单位保证尺寸一致性。该组件实现了从手机、平板到智慧屏的全场景适配,为鸿蒙生态提供了一套标准化、高性能的标签组件解决方案。