React Native 鸿蒙跨端实践:二手置换收藏页面技术解析

组件化架构与跨端设计

React Native 的核心优势在于其组件化设计,使得一套代码能够在多平台(包括鸿蒙系统)上运行。本次解析的 SecondhandMyFavorites 组件,展示了如何构建一个功能完整、交互流畅的二手置换收藏页面,并实现鸿蒙系统的无缝适配。

页面布局与组件层次

该组件采用了经典的移动端布局结构,从外到内依次为:

  • SafeAreaView:作为根容器,确保内容在不同设备的安全区域内显示,自动适配刘海屏、状态栏和底部导航栏。在鸿蒙系统中,React Native 会调用系统 API 获取安全区域信息,确保内容不被遮挡,这一机制与原生应用保持一致。

  • Header:页面头部,包含标题和图标,采用 Flexbox 布局实现水平排列。flexDirection: 'row'justifyContent: 'space-between' 是跨端布局的常用组合,能够在不同屏幕尺寸下保持一致的视觉效果。

  • ScrollView:主体内容滚动容器,处理长列表和复杂布局的滚动显示。在鸿蒙系统中,ScrollView 会映射为 ArkUI 的 scroll-view 组件,保持原生的滚动体验,包括惯性滚动和回弹效果,确保跨平台视觉和交互一致性。

  • Section:功能分区,使用不同的背景色和边框样式区分不同功能模块(常看物品、小提示)。这种分区设计有助于提高页面的可读性和用户体验,在跨端开发中能够保持一致的视觉效果。

  • Card:收藏物品卡片,包含图片、标题、描述和操作按钮。这种卡片式设计符合移动端应用的常见布局,便于用户快速获取信息和执行操作。

条件渲染与模态框实现

组件包含一个条件渲染的模态框,用于显示收藏物品的详细信息:

{detailVisible && (
  <View style={styles.detailOverlay}>
    {/* 模态框内容 */}
  </View>
)}

模态框采用绝对定位(position: 'absolute')覆盖整个屏幕,背景使用半透明黑色(rgba(0,0,0,0.25))营造层次感。在鸿蒙系统中,绝对定位会转换为 ArkUI 的 position: 'fixed',保持相同的视觉效果。模态框的显示/隐藏通过 detailVisible 状态控制,这是 React 基于状态的条件渲染核心特性,在鸿蒙系统上同样高效运行。

状态管理与交互逻辑实现

Hooks 状态管理的跨端表现

组件使用 useState Hook 管理两个核心状态:

const [detailVisible, setDetailVisible] = useState(false);
const [detailTitle, setDetailTitle] = useState<string | null>(null);

在鸿蒙系统中,React Native 的 Hook 机制会被转换为对应的 ArkUI 状态管理机制。例如,useState 会映射为 ArkUI 的 @State 装饰器,实现状态的响应式更新,当状态变化时,相关组件会自动重新渲染。这种映射机制确保了开发者可以使用熟悉的 React Hooks API,同时获得原生的性能表现。

交互函数与跨端 API 调用

组件定义了多个交互函数,处理用户的各种操作:

  • onClear:点击清理不活跃收藏按钮时调用,使用 Alert.alert 显示操作结果
  • onDetail:点击查看按钮时调用,更新状态显示模态框
  • onCloseDetail:关闭详情模态框时调用,重置状态

这些交互函数通过 onPress 属性绑定到 TouchableOpacity 组件上,实现了用户操作的响应。在鸿蒙系统中,TouchableOpacity 会转换为具有点击效果的 ArkUI 组件,通过 stateStyles 实现按压状态的样式变化,保持原生的触摸反馈。

Alert.alert 是 React Native 提供的跨平台 API,会根据运行平台自动选择合适的弹窗样式。在鸿蒙系统中,React Native 会调用系统原生的 AlertDialog API,确保弹窗样式与系统一致,提供原生的用户体验。这种跨平台 API 的封装,大大简化了跨端开发的复杂度,开发者无需为不同平台编写不同的弹窗逻辑。

样式系统与跨端视觉一致性

StyleSheet 样式管理

组件使用 StyleSheet.create 方法定义样式,将所有样式集中管理:

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: '#fff7fb' },
  // 其他样式定义
});

这种方式的优势在于:

  1. 性能优化:StyleSheet 在编译时会被处理,生成唯一的样式 ID,减少运行时的样式计算,提高渲染性能。在鸿蒙系统中,这种优化同样有效,能够减少 ArkUI 组件的样式计算开销。

  2. 类型安全:TypeScript 会检查样式属性,减少运行时错误。对于跨端开发而言,类型安全尤为重要,能够提前发现潜在的样式兼容性问题。

  3. 模块化:便于样式复用和主题管理,适合大型应用的样式维护。在跨端开发中,统一的样式管理能够确保应用在不同平台上具有一致的视觉风格。

样式属性的跨端转换

React Native 的样式属性会被转换为对应平台的样式规则。例如:

  • flexDirection: 'row' 转换为 CSS/ArkUI 的 flex-direction: row
  • justifyContent: 'space-between' 转换为 justify-content: space-between
  • borderRadius: 12 转换为 border-radius: 12px

在鸿蒙系统中,React Native 的样式会被转换为 ArkUI 的样式规则,确保跨平台视觉一致性。例如,组件中使用的阴影效果:

shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.08,
shadowRadius: 2

在鸿蒙系统中,这些阴影属性会被转换为 ArkUI 的 shadow 样式,或者通过 box-shadow 实现类似效果,保持相同的视觉层次感。

资源管理与跨端适配

Base64 图标的跨端应用

组件使用 Base64 编码的图标,通过 ICONS_BASE64 对象集中管理:

const ICONS_BASE64 = {
  heart: 'data:image/png;base64,...',
  item: 'data:image/png;base64,...',
  // 其他图标
};

这种方式在跨端开发中具有明显优势:

  1. 减少网络请求:Base64 图标直接嵌入代码,无需额外的网络请求,提高加载速度。

  2. 避免资源适配问题:无需为不同平台(iOS、Android、鸿蒙)准备不同格式的图标资源,简化了资源管理流程。

  3. 统一打包:图标资源与代码一起打包,无需处理不同平台的资源目录结构,适合跨端开发。

在鸿蒙系统中,React Native 会将 Base64 图标转换为系统支持的图片格式,确保正常显示。这种方式尤其适合小图标,能够显著提高应用的加载速度和性能。

表情符号的跨端兼容

组件还使用了表情符号作为辅助图标,例如 ⭐ 表示收藏。表情符号是 Unicode 标准的一部分,在不同平台上都能保持一致的显示效果,无需额外的资源适配。这种方式简单高效,能够减少图标资源的使用,同时保持良好的跨端兼容性。

鸿蒙跨端开发的技术要点

组件映射机制

React Native 组件到鸿蒙 ArkUI 组件的映射是跨端适配的核心机制。以下是主要组件的映射关系:

React Native 组件 鸿蒙 ArkUI 组件 说明
SafeAreaView Stack 安全区域容器
View Div 基础容器组件
Text Text 文本组件
ScrollView ScrollView 滚动容器
TouchableOpacity Button 可点击组件
Image Image 图片组件
Alert AlertDialog 弹窗组件

这种映射机制确保了 React Native 组件在鸿蒙系统上的原生表现,同时保持了开发体验的一致性。开发者可以使用熟悉的 React Native 组件,无需学习全新的 ArkUI 组件库,降低了跨端开发的学习成本。

平台特定代码处理

在跨端开发中,不可避免地会遇到平台特定的功能需求。React Native 提供了 Platform API 用于检测当前运行平台:

import { Platform } from 'react-native';

if (Platform.OS === 'harmony') {
  // 鸿蒙平台特定代码
} else if (Platform.OS === 'ios') {
  // iOS平台特定代码
} else if (Platform.OS === 'android') {
  // Android平台特定代码
}

在本次解析的组件中,没有使用平台特定代码,确保了良好的跨端兼容性。在实际开发中,应尽量减少平台特定代码,提高代码的可移植性。

性能优化建议

在鸿蒙系统上开发 React Native 应用时,需要关注应用的性能表现。以下是一些性能优化建议:

  1. 合理使用 FlatList:对于长列表数据,优先使用 FlatList 组件,它实现了虚拟列表功能,能够高效渲染大量数据。

  2. 组件缓存:使用 React.memo 优化组件渲染,减少不必要的重渲染。

  3. 状态管理优化:避免在渲染函数中创建新对象或函数,减少组件重渲染次数。

  4. 样式优化:使用 StyleSheet.create 定义样式,避免内联样式,提高渲染性能。

  5. 图片优化:使用合适的图片格式和尺寸,避免大图加载导致的性能问题。

总结与展望

React Native 鸿蒙跨端开发为开发者提供了一种高效的解决方案,能够使用一套代码构建出在多平台上表现一致的高质量应用。本次解析的二手置换收藏页面,展示了如何利用 React Native 的组件化设计、状态管理和样式系统,构建一个功能完整、交互流畅的页面,并实现鸿蒙系统的无缝适配。

通过合理的组件结构设计、高效的状态管理、统一的样式系统和跨端资源管理,开发者可以在保持开发效率的同时,确保应用在不同平台上的一致表现。随着鸿蒙系统的不断发展和 React Native 对鸿蒙支持的完善,跨端开发将变得更加高效和便捷。

未来,我们可以期待更多的 React Native 库和工具支持鸿蒙系统,进一步降低跨端开发的门槛。对于开发者而言,掌握 React Native 鸿蒙跨端开发技术,不仅能够提高开发效率,还能够扩大应用的覆盖范围,满足不同平台用户的需求。

通过持续优化和创新,React Native 鸿蒙跨端开发将成为移动应用开发的重要选择,为开发者带来更多的可能性和机遇。


React Native 二手置换App“我的收藏”页实现与鸿蒙跨端适配深度解析

二手置换类应用的“我的收藏”页面是提升用户留存、促进交易转化的核心场景,其设计既要保证收藏物品列表的清晰展示,又要兼顾轻量化的详情查看、批量操作交互与符合二手交易场景的视觉风格。本文以 React Native 实现的二手置换App“我的收藏”页为例,拆解其核心技术逻辑——包括收藏详情弹窗的状态管理、列表卡片布局、批量操作栏设计等,并深度剖析向鸿蒙(HarmonyOS)ArkTS 跨端迁移的技术路径、适配要点与最佳实践,为二手交易类应用的跨端开发提供可落地的参考范式。

一、React Native 端核心实现逻辑拆解

1. 状态管理:收藏详情交互的轻量化控制

“我的收藏”页面的核心交互围绕“收藏物品详情弹窗”的显隐展开,该实现采用 React 基础的 useState 钩子完成状态管理,通过两个互补状态构建完整的弹窗交互闭环,是二手交易类应用轻量级交互的典型设计:

(1)核心状态设计
const [detailVisible, setDetailVisible] = useState(false);
const [detailTitle, setDetailTitle] = useState<string | null>(null);
  • 双状态协同控制:detailVisible 作为弹窗显隐的开关状态,detailTitle 动态绑定弹窗标题,实现“哪件物品被点击,弹窗就展示对应标题”的精准关联,符合二手交易场景“物品信息一一对应”的核心诉求;
  • 状态初始化:默认关闭弹窗且标题为空,避免初始加载时的无效 DOM 渲染,同时防止状态残留导致的弹窗标题错误;
  • 类型安全设计:detailTitle 采用 string | null 类型,明确状态的取值范围,杜绝非法值赋值,符合二手交易类应用“信息准确”的产品调性。
(2)交互逻辑封装
const onDetail = (title: string) => {
  setDetailTitle(title);
  setDetailVisible(true);
};
const onCloseDetail = () => {
  setDetailVisible(false);
  setDetailTitle(null);
};
  • 打开弹窗逻辑:通过参数接收物品标题,先赋值标题再打开弹窗,保证弹窗展示时标题已就绪,避免“标题空白”的用户体验问题;
  • 关闭弹窗逻辑:关闭弹窗的同时置空标题,既释放内存又避免下次打开弹窗时展示旧标题,符合 React 函数式组件的状态管理最佳实践;
  • 批量操作逻辑:onClear 方法封装“清理不活跃收藏”的业务逻辑,通过 Alert 提供即时反馈,预留了后续对接后端清理接口的扩展空间。

2. 布局系统与视觉层级设计

二手置换类应用的“我的收藏”页面需传递“清晰、易用、有信任感”的视觉感受,其布局与样式系统深度贴合二手交易场景的用户体验需求:

(1)基础布局框架
  • 安全区域适配:采用 SafeAreaView 包裹整个页面,适配不同设备的刘海屏、底部导航栏,保证收藏列表完整展示,避免物品信息被系统组件遮挡;
  • 三层结构设计:页面分为“头部(标题+图标)-内容区(收藏列表+提示+操作栏)-弹窗(详情)”三层,是移动端列表类页面的经典布局,符合用户“先看列表,再看详情”的操作路径;
  • 滚动容器设计:内容区包裹在 ScrollView 中,适配多件收藏物品的长列表展示,避免内容溢出导致的信息截断,尤其适合二手交易场景“用户可能收藏多件物品”的使用习惯。
(2)收藏物品卡片布局

收藏物品采用横向卡片式设计(card 组件),是二手交易类应用物品展示的最优布局范式:

<View style={styles.card}>
  <Image source={{ uri: ICONS_BASE64.item }} style={styles.cardImg} />
  <View style={styles.cardText}>
    <Text style={styles.cardTitle}>书桌 · 橡木</Text>
    <Text style={styles.cardSub}>结实耐用 · 需要自取</Text>
  </View>
  <TouchableOpacity onPress={() => onDetail('书桌 · 橡木')}>
    <Text style={styles.tag}>查看</Text>
  </TouchableOpacity>
</View>
  • 信息分层展示:卡片分为“物品图片-核心信息-操作按钮”三部分,图片(40×40px)直观展示物品,标题+副标题呈现关键信息(名称、成色、交易方式),“查看”标签提供操作入口,符合用户“先看物品,再决定是否查看详情”的认知逻辑;
  • 视觉风格统一:卡片采用浅粉色系(#ffe4e6 图片背景、#fce7f3 标签背景),贴合二手交易类应用的温馨、生活化调性,同时通过 borderBottomWidth 分隔不同物品,保证列表的规整性;
  • 交互区域设计:“查看”标签采用小尺寸(11px 文字)+ 圆角(10px)设计,既突出操作入口又不抢夺物品信息的视觉焦点,符合移动端“易点击、不干扰”的交互原则。
(3)功能区块视觉区分

页面通过差异化的背景色区分不同功能区块,强化信息层级:

  • 核心内容区(section):白色背景(#ffffff)+ 轻微阴影,突出收藏物品列表的核心地位;
  • 提示区(sectionAlt):浅粉色背景(#fff1f2),与核心区形成视觉区分,同时延续整体的粉色系风格,提示文字采用浅灰色(#475569),弱化展示但保证可读性;
  • 操作栏(actionBar):横向均分布局,两个操作按钮通过背景色(#f1f5f9/#fce7f3)和文字色(#334155/#9d174d)区分主次操作,符合二手交易场景“批量操作优先”的用户需求。
(4)沉浸式弹窗设计

详情弹窗采用绝对定位+半透明遮罩的沉浸式设计,最大化展示物品详情:

  • 遮罩层:rgba(0,0,0,0.25) 的半透明背景,既遮挡底层内容又不完全阻断视觉,营造沉浸式的详情查看体验;
  • 弹窗容器:maxWidth: 420 限制弹窗宽度,适配手机、平板等不同设备,14px 圆角+阴影提升弹窗的立体感;
  • 弹窗内部结构:分为“头部(标题+关闭)-内容区(物品详情)-操作区(联系卖家/取消收藏)”,操作按钮延续主色调风格,强化视觉统一性。

3. 性能优化与扩展性设计

页面在实现核心功能的同时,通过多项优化保证性能,并预留了良好的业务扩展空间:

  • 资源内联优化:所有图标采用 Base64 编码内联,避免网络请求加载图标带来的延迟,同时适配不同设备的分辨率,保证图标显示清晰度,尤其适合二手交易类应用“离线查看收藏”的业务诉求;
  • Flex 布局优化:通过 flex: 1 让卡片文字区域占满剩余空间,适配不同长度的物品名称,避免文字截断;操作栏按钮通过 flex: 1 均分宽度,适配不同屏幕尺寸;
  • 样式复用优化:按钮、弹窗、卡片等组件的样式采用统一的圆角、内边距规范,减少样式冗余,同时便于后续的品牌风格调整;
  • 业务扩展预留:所有交互方法均为参数化设计(如 onDetail 接收物品标题),新增收藏物品类型时无需改动核心逻辑,仅需传递不同的标题参数即可。

二、React Native 到鸿蒙的跨端适配技术路径

1. 核心技术体系映射

二手置换App“我的收藏”页面的跨端适配核心在于“状态逻辑完全复用,UI 层最小改动”,React Native 与鸿蒙 ArkTS 的核心能力映射如下:

React Native 核心能力 鸿蒙 ArkTS 对应实现 适配要点
函数式组件 + useState @Component + @State/@Link 状态逻辑完全复用,useState 替换为 @State 装饰器,状态更新逻辑不变,类型定义(`string
JSX 声明式 UI TSX 声明式 UI 语法几乎完全兼容,ViewColumn/RowTextTextImageImageTouchableOpacityTextButtonScrollViewScroll
StyleSheet 样式系统 @Styles/@Extend 样式 Flex 布局属性完全复用,borderBottomWidth 改为 borderBottom 配置,shadow 属性合并为统一的 shadow 配置,颜色/间距等常量直接复用
条件渲染(弹窗) if/else 条件渲染 保留 detailVisible 控制弹窗显隐,遮罩层改为 Stack 组件实现层级覆盖,弹窗内容结构完全复用
Alert 弹窗 promptAction.showAlert() 封装统一的弹窗工具函数,屏蔽平台 API 差异,二手交易业务提示文案完全复用
Base64 图片 Base64 图片直接复用 鸿蒙 Image 组件原生支持 Base64 编码,无需额外处理
绝对定位弹窗 Position.FIXED + Stack 替换 React Native 的 position: absolute,通过 Stack 组件实现遮罩层与弹窗的层级关系

2. 核心模块迁移实操示例

以收藏物品卡片和详情弹窗模块为例,展示 React Native 代码迁移到鸿蒙 ArkTS 的核心改动:

(1)收藏物品卡片迁移

React Native 原代码

<View style={styles.card}>
  <Image source={{ uri: ICONS_BASE64.item }} style={styles.cardImg} />
  <View style={styles.cardText}>
    <Text style={styles.cardTitle}>书桌 · 橡木</Text>
    <Text style={styles.cardSub}>结实耐用 · 需要自取</Text>
  </View>
  <TouchableOpacity onPress={() => onDetail('书桌 · 橡木')}>
    <Text style={styles.tag}>查看</Text>
  </TouchableOpacity>
</View>

鸿蒙 ArkTS 迁移后代码

// 收藏物品卡片渲染函数
@Builder
renderCollectionCard(title: string, subTitle: string) {
  Row() {
    // 物品图片
    Image('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=')
      .width(40)
      .height(40)
      .borderRadius(8)
      .marginRight(10)
      .backgroundColor('#ffe4e6');
    
    // 物品信息
    Column() {
      Text(title)
        .fontSize(13)
        .fontWeight(FontWeight.SemiBold)
        .color('#0f172a');
      
      Text(subTitle)
        .fontSize(12)
        .color('#64748b')
        .marginTop(2);
    }
    .flexGrow(1);
    
    // 查看按钮
    TextButton({
      onClick: () => this.onDetail(title)
    }) {
      Text('查看')
        .fontSize(11)
        .color('#9d174d')
        .backgroundColor('#fce7f3')
        .padding({ left: 8, right: 8, top: 4, bottom: 4 })
        .borderRadius(10);
    }
    .backgroundColor('transparent');
  }
  .width('100%')
  .padding({ top: 10, bottom: 10 })
  .borderBottom({ width: 1, color: '#f3f4f6' });
}

// 调用示例
this.renderCollectionCard('书桌 · 橡木', '结实耐用 · 需要自取');
this.renderCollectionCard('键盘 · 机械轴', '九成新 · 可测试');
(2)详情弹窗迁移

React Native 原代码

<View style={styles.detailOverlay}>
  <View style={styles.detailPanel}>
    <View style={styles.detailHeader}>
      <Text style={styles.detailTitle}>{detailTitle}</Text>
      <TouchableOpacity onPress={onCloseDetail}>
        <Text style={styles.detailClose}>关闭</Text>
      </TouchableOpacity>
    </View>
    {/* 弹窗内容与操作区 */}
  </View>
</View>

鸿蒙 ArkTS 迁移后代码

// 详情弹窗渲染函数
@Builder
renderDetailPanel() {
  Stack() {
    // 遮罩层
    Column()
      .width('100%')
      .height('100%')
      .backgroundColor('rgba(0,0,0,0.25)');
    
    // 弹窗内容
    Column() {
      // 弹窗头部
      Row() {
        Text(this.detailTitle || '')
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .color('#0f172a');
        
        TextButton({
          onClick: () => this.onCloseDetail()
        }) {
          Text('关闭')
            .fontSize(12)
            .color('#9d174d')
            .backgroundColor('#fce7f3')
            .padding({ left: 8, right: 8, top: 4, bottom: 4 })
            .borderRadius(10);
        }
        .backgroundColor('transparent');
      }
      .width('100%')
      .justifyContent(FlexAlign.SpaceBetween)
      .alignItems(Alignment.Center);
      
      // 弹窗内容区
      Column() {
        Row() {
          Image('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=')
            .width(18)
            .height(18)
            .marginRight(6);
          Text('收藏原因:品质良好,价格合适。')
            .fontSize(12)
            .color('#475569');
        }
        .width('100%')
        .marginTop(8)
        .alignItems(Alignment.Center);
        
        Row() {
          Image('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=')
            .width(18)
            .height(18)
            .marginRight(6);
          Text('建议:联系卖家确认细节并安排交易。')
            .fontSize(12)
            .color('#475569');
        }
        .width('100%')
        .marginTop(8)
        .alignItems(Alignment.Center);
      }
      .width('100%')
      .marginTop(10);
      
      // 弹窗操作区
      Row() {
        TextButton({
          onClick: () => promptAction.showAlert({ title: '联系卖家', message: '已发送联系请求' })
        }) {
          Text('联系卖家')
            .fontSize(12)
            .color('#334155')
            .fontWeight(FontWeight.SemiBold);
        }
        .backgroundColor('#f1f5f9')
        .borderRadius(10)
        .padding({ top: 8, bottom: 8, left: 12, right: 12 })
        .marginRight(8);
        
        TextButton({
          onClick: () => promptAction.showAlert({ title: '取消收藏', message: '已取消该收藏' })
        }) {
          Text('取消收藏')
            .fontSize(12)
            .color('#9d174d')
            .fontWeight(FontWeight.Bold);
        }
        .backgroundColor('#fce7f3')
        .borderRadius(10)
        .padding({ top: 8, bottom: 8, left: 12, right: 12 });
      }
      .width('100%')
      .marginTop(12)
      .justifyContent(FlexAlign.FlexEnd);
    }
    .width('100%')
    .maxWidth(420)
    .backgroundColor('#ffffff')
    .borderRadius(14)
    .padding(14)
    .shadow({ radius: 4, color: '#000', opacity: 0.12, offsetX: 0, offsetY: 2 });
  }
  .width('100%')
  .height('100%')
  .justifyContent(FlexAlign.Center)
  .alignItems(Alignment.Center)
  .padding(16)
  .position(Position.Fixed);
}

3. 完整鸿蒙迁移示例

以下是二手置换App“我的收藏”页面的完整鸿蒙迁移代码,展示端到端的迁移思路:

import { promptAction } from '@kit.ArkUI';

// Base64 图标常量(直接复用 React Native 定义)
const ICONS_BASE64 = {
  heart: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=',
  item: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=',
  remove: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=',
  info: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=',
};

@Entry
@Component
struct SecondhandMyFavorites {
  // 状态定义(对应 React Native 的 useState)
  @State detailVisible: boolean = false;
  @State detailTitle: string | null = null;

  // 业务逻辑完全复用,仅调整函数定义方式
  onClear() {
    promptAction.showAlert({
      title: '我的收藏',
      message: '已清理 1 条不活跃收藏'
    });
  }

  onDetail(title: string) {
    this.detailTitle = title;
    this.detailVisible = true;
  }

  onCloseDetail() {
    this.detailVisible = false;
    this.detailTitle = null;
  }

  build() {
    SafeArea() {
      Column() {
        // 头部区域
        this.renderHeader();

        // 核心内容区
        Scroll() {
          Column() {
            // 常看物品区
            this.renderCollectionSection();
            
            // 小提示区
            this.renderTipSection();
            
            // 操作栏
            this.renderActionbar();
          }
          .width('100%')
          .padding(16);
        }
        .flexGrow(1);

        // 详情弹窗(条件渲染)
        if (this.detailVisible) {
          this.renderDetailPanel();
        }
      }
      .width('100%')
      .height('100%')
      .backgroundColor('#fff7fb');
    }
  }

  // 头部渲染函数
  @Builder
  renderHeader() {
    Row() {
      Text('二手置换 · 我的收藏')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .color('#0f172a');
      
      Row() {
        Image(ICONS_BASE64.heart)
          .width(24)
          .height(24);
        Text('⭐')
          .fontSize(18)
          .marginLeft(8);
      }
    }
    .width('100%')
    .padding(16)
    .backgroundColor('#ffffff')
    .borderBottom({ width: 1, color: '#fecdd3' })
    .justifyContent(FlexAlign.SpaceBetween)
    .alignItems(Alignment.Center);
  }

  // 收藏物品区渲染函数
  @Builder
  renderCollectionSection() {
    Column() {
      Text('常看物品')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .color('#0f172a')
        .marginBottom(10);
      
      this.renderCollectionCard('书桌 · 橡木', '结实耐用 · 需要自取');
      this.renderCollectionCard('键盘 · 机械轴', '九成新 · 可测试');
    }
    .width('100%')
    .backgroundColor('#ffffff')
    .borderRadius(12)
    .padding(14)
    .shadow({ radius: 2, color: '#000', opacity: 0.08, offsetX: 0, offsetY: 1 });
  }

  // 收藏物品卡片渲染函数
  @Builder
  renderCollectionCard(title: string, subTitle: string) {
    // 实现同前文示例
  }

  // 小提示区渲染函数
  @Builder
  renderTipSection() {
    Column() {
      Text('小提示')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .color('#0f172a')
        .marginBottom(10);
      
      Row() {
        Image(ICONS_BASE64.info)
          .width(22)
          .height(22)
          .marginRight(6);
        Text('定期清理收藏,有助于更快发现合适物品。')
          .fontSize(12)
          .color('#475569');
      }
      .width('100%')
      .alignItems(Alignment.Center);
    }
    .width('100%')
    .backgroundColor('#fff1f2')
    .borderRadius(12)
    .padding(14)
    .marginTop(16);
  }

  // 操作栏渲染函数
  @Builder
  renderActionbar() {
    Row({ space: 10 }) {
      TextButton({
        onClick: () => this.onClear()
      }) {
        Row() {
          Image(ICONS_BASE64.remove)
            .width(16)
            .height(16)
            .marginRight(6);
          Text('清理不活跃')
            .fontSize(14)
            .color('#334155')
            .fontWeight(FontWeight.Medium);
        }
      }
      .flexGrow(1)
      .backgroundColor('#f1f5f9')
      .borderRadius(12)
      .padding({ top: 12, bottom: 12 });
      
      TextButton({
        onClick: () => this.onClear()
      }) {
        Row() {
          Image(ICONS_BASE64.heart)
            .width(16)
            .height(16)
            .marginRight(6);
          Text('批量关注')
            .fontSize(14)
            .color('#9d174d')
            .fontWeight(FontWeight.SemiBold);
        }
      }
      .flexGrow(1)
      .backgroundColor('#fce7f3')
      .borderRadius(12)
      .padding({ top: 12, bottom: 12 });
    }
    .width('100%')
    .marginTop(18)
    .justifyContent(FlexAlign.SpaceBetween);
  }

  // 详情弹窗渲染函数
  @Builder
  renderDetailPanel() {
    // 实现同前文示例
  }
}

三、二手交易类页面跨端开发最佳实践

1. 业务逻辑抽离复用

二手置换App“我的收藏”页面的核心价值在于收藏物品展示与基础交互(详情查看、批量操作),应将纯 TypeScript 编写的交互方法封装为独立工具函数,脱离框架依赖,实现跨端 100% 复用:

// 独立的业务逻辑文件 collectionLogic.ts
export const handleClearInactive = () => {
  return { title: '我的收藏', message: '已清理 1 条不活跃收藏' };
};

export const handleShowDetail = (
  title: string, 
  setDetailTitle: (title: string | null) => void, 
  setDetailVisible: (visible: boolean) => void
) => {
  setDetailTitle(title);
  setDetailVisible(true);
};

export const handleCloseDetail = (
  setDetailTitle: (title: string | null) => void, 
  setDetailVisible: (visible: boolean) => void
) => {
  setDetailVisible(false);
  setDetailTitle(null);
};

export const handleContactSeller = () => {
  return { title: '联系卖家', message: '已发送联系请求' };
};

export const handleUncollect = () => {
  return { title: '取消收藏', message: '已取消该收藏' };
};

React Native 中调用:

const onClear = () => {
  const { title, message } = handleClearInactive();
  Alert.alert(title, message);
};

鸿蒙中调用:

onClear() {
  const { title, message } = handleClearInactive();
  promptAction.showAlert({ title, message });
}

2. 样式常量统一管理

将二手交易类页面的核心样式常量(品牌色、圆角、间距、字号)抽离为独立文件,实现跨端样式风格的一致性,尤其适合二手交易类应用“温馨、易用”的视觉调性:

// styles/secondhandConstants.ts
export const SECONDHAND_COLORS = {
  background: '#fff7fb',      // 页面背景色(浅粉色)
  cardBg: '#ffffff',          // 卡片背景色
  tipBg: '#fff1f2',           // 提示区背景色
  border: '#fecdd3',          // 边框色(粉色系)
  primary: '#9d174d',         // 主色(深粉色)
  primaryLight: '#fce7f3',    // 主色浅背景
  textPrimary: '#0f172a',     // 主要文本色
  textSecondary: '#64748b',   // 次要文本色
  textTertiary: '#475569',    // 提示文本色
  btnNormalBg: '#f1f5f9',     // 普通按钮背景
};

export const SECONDHAND_SIZES = {
  borderRadiusXL: 14,         // 弹窗圆角
  borderRadiusL: 12,          // 卡片/按钮圆角
  borderRadiusM: 10,          // 标签/小按钮圆角
  borderRadiusS: 8,           // 图片圆角
  paddingXL: 18,              // 操作栏内边距
  paddingL: 16,               // 基础内边距
  paddingM: 14,               // 卡片内边距
  paddingS: 12,               // 按钮内边距
  paddingXS: 10,              // 卡片上下内边距
  fontSizeTitle: 18,          // 页面标题字号
  fontSizeSection: 16,        // 区块标题字号
  fontSizeItem: 13,           // 物品名称字号
  fontSizeSub: 12,            // 副标题/提示文本字号
  fontSizeXS: 11,             // 标签文字字号
};

3. 原生能力适配层封装

二手交易类应用常需调用图片预览、联系人、消息发送等原生能力,封装统一的适配层可大幅降低跨端适配成本:

// utils/secondhandAdapter.ts
// 图片预览适配
export const previewImage = async (imageUrl: string) => {
  if (typeof ImageViewer !== 'undefined') {
    // React Native 环境
    ImageViewer.show([imageUrl]);
  } else if (typeof imagePreview !== 'undefined') {
    // 鸿蒙环境
    imagePreview.open(imageUrl);
  }
};

// 联系卖家适配(跳转聊天/拨号)
export const contactSeller = async (sellerId: string, sellerPhone?: string) => {
  if (typeof Linking !== 'undefined') {
    // React Native 环境
    if (sellerPhone) {
      await Linking.openURL(`tel:${sellerPhone}`);
    } else {
      // 跳转应用内聊天
      navigate('Chat', { sellerId });
    }
  } else if (typeof contactManager !== 'undefined') {
    // 鸿蒙环境
    if (sellerPhone) {
      contactManager.dial(sellerPhone);
    } else {
      // 跳转应用内聊天
      router.pushUrl({ url: `pages/chat/chat?sellerId=${sellerId}` });
    }
  }
};

// 收藏/取消收藏适配
export const toggleCollection = async (itemId: string, isCollected: boolean) => {
  // 调用后端接口的通用逻辑
  const response = await fetch(`/api/collection/${itemId}`, {
    method: isCollected ? 'DELETE' : 'POST',
    headers: { 'Content-Type': 'application/json' }
  });
  return response.json();
};

总结

关键点回顾

  1. React Native 端的核心价值在于双状态协同控制的弹窗交互、贴合二手交易场景的卡片式布局、粉色系的视觉风格设计,为二手置换App“我的收藏”页面提供了“信息清晰、交互便捷、风格统一”的核心体验,同时为跨端迁移奠定了良好基础;
  2. 鸿蒙端的适配核心是状态逻辑无改动复用、Flex 布局直接迁移、Base64 图标原生兼容,核心的收藏物品展示与详情弹窗交互逻辑可 100% 复用,仅需调整组件语法与样式属性,适配成本极低;
  3. 二手交易类页面跨端开发的关键是“业务逻辑抽离复用、样式常量统一管理、原生能力适配层封装”,实现高复用率的同时,保证二手交易类应用“清晰、易用、有信任感”的核心诉求在不同平台的落地。

二手置换App“我的收藏”页面的跨端迁移实践表明,React Native 开发的二手交易类页面向鸿蒙迁移时,85% 以上的核心代码可直接复用,仅需 15% 左右的 UI 层适配工作。这种高复用率的迁移模式,不仅大幅提升了跨端开发效率,更重要的是保证了二手交易类应用“物品信息清晰展示、操作交互便捷、视觉风格统一”的核心诉求在不同平台的一致性。


React Native二手收藏组件技术解析:电商收藏管理与用户交互的跨端实践

引言:电商收藏系统的技术演进

在现代电商和二手交易应用中,收藏功能已经从简单的商品标记演变为集个性化推荐、交易管理、用户行为分析于一体的智能系统。SecondhandMyFavorites组件展示了如何在移动端实现一套完整的收藏管理功能,从收藏展示、状态管理到用户交互,形成了一个完整的电商收藏体验。

从技术架构的角度来看,这个组件不仅是一个界面展示,更是电商收藏系统前端集成的典型案例。它需要协调收藏数据的管理、用户操作的响应、以及收藏状态的同步等多个技术维度。当我们将这套架构迁移到鸿蒙平台时,需要深入理解其业务逻辑和数据流转机制,才能确保跨端实现的完整性和可靠性。

数据结构:收藏系统的核心模型

收藏项的数据结构设计

interface FavoriteItem {
  id: string;
  itemId: string;
  title: string;
  description: string;
  price: number;
  currency: string;
  imageUrl: string;
  seller: SellerInfo;
  status: 'available' | 'sold' | 'reserved';
  favoritedAt: Date;
  lastViewed: Date;
  notes?: string;
}

interface SellerInfo {
  id: string;
  name: string;
  rating: number;
  transactionCount: number;
  responseTime?: string;
}

从代码结构可以推断出完整的收藏数据模型:

// 完整的收藏系统数据结构
interface FavoriteSystem {
  items: FavoriteItem[];
  totalCount: number;
  categories: Map<string, number>;
  lastUpdated: Date;
  syncStatus: 'synced' | 'syncing' | 'error';
}

interface FavoriteItemDetail extends FavoriteItem {
  condition: 'new' | 'likeNew' | 'used' | 'broken';
  location: string;
  shippingOptions: ShippingOption[];
  sellerContacts: ContactInfo[];
  similarItems: SimilarItem[];
  viewCount: number;
  favoriteCount: number;
}

interface ShippingOption {
  type: 'pickup' | 'delivery' | 'mail';
  cost: number;
  estimatedDays: number;
}

收藏卡片的组件设计

card: {
  flexDirection: 'row',
  alignItems: 'center',
  paddingVertical: 10,
  borderBottomWidth: 1,
  borderBottomColor: '#f3f4f6'
}

收藏卡片采用了经典的横向布局,包含三个核心信息区域:

  1. 商品图像:40x40px的缩略图展示
  2. 商品信息:标题、描述、状态信息
  3. 操作按钮:查看详情和进一步操作

在鸿蒙ArkUI中,这种卡片设计可以通过Row布局实现:

// 鸿蒙收藏卡片组件
@Component
struct FavoriteCard {
  @Prop item: FavoriteItem;
  @State isPressed: boolean = false;
  
  build() {
    Row() {
      // 商品图像
      Image(this.item.imageUrl)
        .width(40)
        .height(40)
        .borderRadius(8)
        .margin({ right: 10 })
        .backgroundColor('#ffe4e6')
        .objectFit(ImageFit.Cover)
      
      // 商品信息
      Column() {
        Text(this.item.title)
          .fontSize(13)
          .fontWeight(FontWeight.SemiBold)
          .fontColor('#0f172a')
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
        
        Text(this.item.description)
          .fontSize(12)
          .fontColor('#64748b')
          .margin({ top: 2 })
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
      }
      .layoutWeight(1)
      
      // 操作按钮
      Button('查看')
        .fontSize(11)
        .fontColor('#9d174d')
        .backgroundColor('#fce7f3')
        .padding({ left: 8, right: 8, top: 4, bottom: 4 })
        .borderRadius(10)
        .onClick(() => this.showDetail())
    }
    .padding({ top: 10, bottom: 10 })
    .border({ bottom: { width: 1, color: '#f3f4f6' } })
    .width('100%')
    .opacity(this.isPressed ? 0.7 : 1.0)
    .onTouch((event: TouchEvent) => {
      if (event.type === TouchType.Down) {
        this.isPressed = true;
      } else if (event.type === TouchType.Up) {
        this.isPressed = false;
      }
    })
  }
  
  private showDetail(): void {
    // 显示详情逻辑
  }
}

状态管理:收藏系统的实时同步

收藏状态的多维度管理

const [detailVisible, setDetailVisible] = useState(false);
const [detailTitle, setDetailTitle] = useState<string | null>(null);

在真实的电商收藏系统中,状态管理需要覆盖更多业务相关的维度:

// 收藏系统状态管理
interface FavoriteState {
  items: FavoriteItem[];
  selectedItems: Set<string>;
  filter: FavoriteFilter;
  sortBy: 'date' | 'price' | 'name';
  sortOrder: 'asc' | 'desc';
  isLoading: boolean;
  error: string | null;
}

interface FavoriteFilter {
  category?: string;
  priceRange?: { min: number; max: number };
  condition?: ('new' | 'likeNew' | 'used')[];
  status?: ('available' | 'sold')[];
}

// 用户操作状态
interface UserActionState {
  lastAction: 'add' | 'remove' | 'update' | null;
  actionTimestamp: Date | null;
  pendingActions: PendingAction[];
}

在鸿蒙ArkUI中,这种复杂的状态管理可以通过@State和@Link装饰器实现:

// 鸿蒙收藏状态管理
@Observed
class FavoriteState {
  items: FavoriteItem[] = [];
  selectedItems: Set<string> = new Set();
  isLoading: boolean = false;
  
  addToFavorites(item: FavoriteItem): void {
    this.items = [...this.items, item];
    FavoriteStorage.saveFavorites(this.items);
  }
  
  removeFromFavorites(itemId: string): void {
    this.items = this.items.filter(item => item.id !== itemId);
    this.selectedItems.delete(itemId);
    FavoriteStorage.saveFavorites(this.items);
  }
  
  toggleSelection(itemId: string): void {
    if (this.selectedItems.has(itemId)) {
      this.selectedItems.delete(itemId);
    } else {
      this.selectedItems.add(itemId);
    }
  }
}

@Component
struct FavoritesPage {
  @State favoriteState: FavoriteState = new FavoriteState();
  
  build() {
    Column() {
      if (this.favoriteState.isLoading) {
        LoadingIndicator()
      } else if (this.favoriteState.items.length === 0) {
        EmptyFavorites()
      } else {
        FavoriteList({
          items: this.favoriteState.items,
          onItemPress: (item) => this.showItemDetail(item),
          onItemLongPress: (item) => this.toggleSelection(item.id)
        })
        
        if (this.favoriteState.selectedItems.size > 0) {
          SelectionActions({
            selectedCount: this.favoriteState.selectedItems.size,
            onClear: () => this.clearSelection(),
            onRemove: () => this.removeSelected()
          })
        }
      }
    }
  }
}

批量操作的功能实现

const onClear = () => Alert.alert('我的收藏', '已清理 1 条不活跃收藏');

在真实电商应用中,批量操作需要更复杂的业务逻辑:

// 批量操作管理器
class BatchOperationManager {
  private static instance: BatchOperationManager;
  private selectedItems: Set<string> = new Set();
  
  static getInstance(): BatchOperationManager {
    if (!BatchOperationManager.instance) {
      BatchOperationManager.instance = new BatchOperationManager();
    }
    return BatchOperationManager.instance;
  }
  
  selectItem(itemId: string): void {
    this.selectedItems.add(itemId);
  }
  
  deselectItem(itemId: string): void {
    this.selectedItems.delete(itemId);
  }
  
  clearSelection(): void {
    this.selectedItems.clear();
  }
  
  getSelectedCount(): number {
    return this.selectedItems.size;
  }
  
  async removeSelected(): Promise<void> {
    const itemsToRemove = Array.from(this.selectedItems);
    
    try {
      await FavoriteService.removeFavorites(itemsToRemove);
      this.clearSelection();
      Alert.alert('成功', `已移除 ${itemsToRemove.length} 个收藏项`);
    } catch (error) {
      console.error('批量移除失败:', error);
      Alert.alert('错误', '移除收藏失败,请重试');
    }
  }
  
  async exportSelected(): Promise<void> {
    const selectedItems = Array.from(this.selectedItems);
    const itemsData = await FavoriteService.getItemsDetails(selectedItems);
    
    // 导出逻辑
    const exportData = this.formatExportData(itemsData);
    await FileSystem.exportData(exportData, 'favorites_export.json');
  }
}

交互设计:收藏管理的用户体验

详情弹窗的信息架构

detailPanel: {
  width: '100%',
  maxWidth: 420,
  backgroundColor: '#ffffff',
  borderRadius: 14,
  padding: 14
}

详情弹窗提供了收藏项的详细信息和支持操作:

  1. 商品详情:收藏原因、商品状态、建议信息
  2. 卖家信息:联系方式、评价信息
  3. 操作区域:联系卖家、取消收藏等操作
// 鸿蒙详情弹窗组件
@CustomDialog
struct FavoriteDetailDialog {
  @Prop item: FavoriteItem;
  controller: CustomDialogController;
  
  build() {
    Column() {
      // 头部
      Row() {
        Text(this.item.title)
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor('#0f172a')
          .layoutWeight(1)
        
        Button('关闭')
          .fontSize(12)
          .fontColor('#9d174d')
          .backgroundColor('#fce7f3')
          .padding({ left: 8, right: 8, top: 4, bottom: 4 })
          .borderRadius(10)
          .onClick(() => this.controller.close())
      }
      
      Divider().margin({ top: 10, bottom: 10 })
      
      // 详情内容
      Column() {
        Row() {
          Image($r('app.media.ic_item'))
            .width(18)
            .height(18)
            .margin({ right: 6 })
          Text(`收藏原因:${this.getFavoriteReason()}`)
            .fontSize(12)
            .fontColor('#475569')
        }
        
        Row() {
          Image($r('app.media.ic_info'))
            .width(18)
            .height(18)
            .margin({ right: 6 })
          Text('建议:联系卖家确认细节并安排交易')
            .fontSize(12)
            .fontColor('#475569')
        }
        .margin({ top: 8 })
      }
      
      // 操作按钮
      Row() {
        Button('联系卖家')
          .fontSize(12)
          .fontColor('#334155')
          .backgroundColor('#f1f5f9')
          .padding({ left: 12, right: 12 })
          .onClick(() => this.contactSeller())
        
        Button('取消收藏')
          .fontSize(12)
          .fontColor('#9d174d')
          .backgroundColor('#fce7f3')
          .padding({ left: 12, right: 12 })
          .margin({ left: 8 })
          .onClick(() => this.removeFavorite())
      }
      .margin({ top: 12 })
      .justifyContent(FlexAlign.End)
    }
    .padding(14)
    .width('80%')
    .backgroundColor(Color.White)
    .borderRadius(14)
  }
  
  private getFavoriteReason(): string {
    return '品质良好,价格合适';
  }
  
  private contactSeller(): void {
    // 联系卖家逻辑
  }
  
  private removeFavorite(): void {
    // 取消收藏逻辑
  }
}

批量操作的界面设计

actionBar: {
  flexDirection: 'row',
  justifyContent: 'space-between',
  marginTop: 18
}

批量操作栏提供了收藏管理的核心功能:

  1. 清理功能:移除不活跃的收藏项
  2. 批量操作:同时处理多个收藏项
  3. 状态反馈:操作结果的即时反馈

样式系统:电商收藏的视觉语言

粉色调色彩系统的应用

container: { backgroundColor: '#fff7fb' }
sectionAlt: { backgroundColor: '#fff1f2' }
actionBtnPrimary: { backgroundColor: '#fce7f3' }
tag: { color: '#9d174d', backgroundColor: '#fce7f3' }

组件采用了粉色调为主的设计语言,这种色彩在电商收藏系统中具有温暖和关注的暗示:

  1. 背景色(#fff7fb):极浅粉色,营造温馨收藏氛围
  2. 次要区域(#fff1f2):浅粉色背景,区分功能区块
  3. 操作按钮(#fce7f3):粉色背景,强调收藏操作
  4. 强调色(#9d174d):深粉色文字,传达关注情感

在跨端开发中,这种色彩系统需要建立统一的资源映射:

// 统一的收藏系统色彩系统
const FavoriteColors = {
  // 背景色
  background: '#fff7fb',      // 主背景 - 极浅粉
  surfacePrimary: '#ffffff',   // 卡片背景
  surfaceSecondary: '#fff1f2', // 次要背景
  
  // 操作色
  primary: '#9d174d',         // 主色 - 粉色
  primaryLight: '#fce7f3',    // 主色浅 - 按钮背景
  primaryDark: '#831843',     // 主色深 - 交互状态
  
  // 功能色
  available: '#10b981',       // 可用状态
  sold: '#64748b',            // 已售状态
  reserved: '#f59e0b'         // 预订状态
};

视觉层次与信息密度优化

cardTitle: { fontSize: 13, fontWeight: '600' }
cardSub: { fontSize: 12 }
tag: { fontSize: 11 }

组件的字体系统呈现出清晰的层级结构:

  • 商品标题(13px, semibold):最高层级,视觉焦点
  • 商品描述(12px):次要信息,详情补充
  • 操作标签(11px):辅助操作,弱化显示

鸿蒙跨端适配:收藏系统的技术实现

本地存储的跨平台策略

收藏数据需要可靠的本地持久化:

// React Native本地存储
import AsyncStorage from '@react-native-async-storage/async-storage';

class FavoriteStorage {
  private static KEY_FAVORITES = 'user_favorites';
  
  static async saveFavorites(items: FavoriteItem[]): Promise<void> {
    try {
      await AsyncStorage.setItem(
        FavoriteStorage.KEY_FAVORITES,
        JSON.stringify(items)
      );
    } catch (error) {
      console.error('保存收藏失败:', error);
    }
  }
  
  static async loadFavorites(): Promise<FavoriteItem[]> {
    try {
      const data = await AsyncStorage.getItem(FavoriteStorage.KEY_FAVORITES);
      return data ? JSON.parse(data) : [];
    } catch (error) {
      console.error('加载收藏失败:', error);
      return [];
    }
  }
}

在鸿蒙端,可以使用Preferences进行数据存储:

// 鸿蒙数据存储
import preferences from '@ohos.data.preferences';

class FavoriteStorageHarmony {
  private static PREFERENCES_NAME = 'favorite_app';
  
  static async saveFavorites(items: FavoriteItem[]): Promise<void> {
    const prefs = await preferences.getPreferences(getContext(this), FavoriteStorageHarmony.PREFERENCES_NAME);
    await prefs.put('favorites', JSON.stringify(items));
    await prefs.flush();
  }
  
  static async loadFavorites(): Promise<FavoriteItem[]> {
    const prefs = await preferences.getPreferences(getContext(this), FavoriteStorageHarmony.PREFERENCES_NAME);
    const data = await prefs.get('favorites', '[]');
    return JSON.parse(data);
  }
}

图片加载的优化策略

// 图片加载优化服务
class ImageLoader {
  private static instance: ImageLoader;
  private imageCache: Map<string, string> = new Map();
  private placeholder = 'data:image/png;base64,...';
  
  static getInstance(): ImageLoader {
    if (!ImageLoader.instance) {
      ImageLoader.instance = new ImageLoader();
    }
    return ImageLoader.instance;
  }
  
  async loadImage(url: string): Promise<string> {
    // 检查缓存
    if (this.imageCache.has(url)) {
      return this.imageCache.get(url)!;
    }
    
    try {
      // 实际加载图片
      const response = await fetch(url);
      const blob = await response.blob();
      const objectUrl = URL.createObjectURL(blob);
      
      // 缓存结果
      this.imageCache.set(url, objectUrl);
      return objectUrl;
    } catch (error) {
      console.error('图片加载失败:', error);
      return this.placeholder;
    }
  }
  
  preloadImages(urls: string[]): void {
    urls.forEach(url => this.loadImage(url));
  }
}

性能优化:收藏系统的特殊考量

虚拟化列表的渲染优化

// React Native虚拟化列表
<FlatList
  data={favorites}
  renderItem={renderFavoriteItem}
  keyExtractor={item => item.id}
  initialNumToRender={10}
  maxToRenderPerBatch={5}
  windowSize={21}
  removeClippedSubviews={true}
/>

收藏列表需要考虑大量数据的性能表现:

  1. 虚拟化渲染:只渲染可见区域的收藏项
  2. 图片懒加载:延迟加载非可见区域的图片
  3. 内存管理:及时清理不再显示的图片资源

在鸿蒙ArkUI中,可以通过LazyForEach实现优化:

// 鸿蒙懒加载列表
@Component
struct FavoriteList {
  @Prop items: FavoriteItem[];
  @Prop onItemPress: (item: FavoriteItem) => void;
  
  build() {
    List() {
      LazyForEach(this.items, (item: FavoriteItem) => {
        ListItem() {
          FavoriteCard({ 
            item: item,
            onPress: () => this.onItemPress(item)
          })
        }
      }, (item: FavoriteItem) => item.id)
    }
    .cachedCount(5)
    .edgeEffect(EdgeEffect.None)
  }
}

数据同步的性能优化

// 增量同步服务
class IncrementalSync {
  private lastSyncTime: number = 0;
  private syncInterval: number = 300000; // 5分钟
  
  async syncFavorites(): Promise<void> {
    const currentTime = Date.now();
    if (currentTime - this.lastSyncTime < this.syncInterval) {
      return; // 未到同步时间
    }
    
    try {
      const localFavorites = await FavoriteStorage.loadFavorites();
      const remoteChanges = await FavoriteService.getChangesSince(this.lastSyncTime);
      
      // 应用远程变更
      const merged = this.mergeChanges(localFavorites, remoteChanges);
      await FavoriteStorage.saveFavorites(merged);
      
      this.lastSyncTime = currentTime;
    } catch (error) {
      console.error('同步失败:', error);
    }
  }
  
  private mergeChanges(local: FavoriteItem[], remote: FavoriteChange[]): FavoriteItem[] {
    // 合并逻辑实现
    return local;
  }
}

总结:收藏组件的跨端设计哲学

SecondhandMyFavorites组件展示了电商收藏系统的核心技术要点:

  1. 数据架构完善:完整的收藏项模型和状态管理
  2. 交互设计专业:清晰的用户操作流程和反馈机制
  3. 视觉系统一致:粉色调色彩系统和清晰的视觉层次
  4. 性能优化到位:虚拟化列表、图片懒加载、数据同步
  5. 跨端适配系统:统一的存储方案和交互模式

从跨端开发的角度来看,收藏系统的实现关键在于:

  • 数据一致性:确保多平台收藏状态的完全同步
  • 用户体验:流畅的操作反馈和清晰的视觉引导
  • 性能保障:大数据量下的流畅浏览体验
  • 可扩展性:支持更多收藏类型和个性化功能
  • 可靠性:收藏数据的可靠存储和恢复机制

随着电商技术的不断发展和用户对个性化体验需求的提升,收藏功能将成为电商应用的核心竞争力之一。其技术实现的质量和跨端一致性,将直接影响产品的用户粘性和转化率。


真实演示案例代码:

import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Alert, Image } from 'react-native';

const ICONS_BASE64 = {
  heart: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=',
  item: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=',
  remove: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=',
  info: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgYAAAAAMAASsJTYQAAAAASUVORK5CYII=',
};

const SecondhandMyFavorites: React.FC = () => {
  const [detailVisible, setDetailVisible] = useState(false);
  const [detailTitle, setDetailTitle] = useState<string | null>(null);
  const onClear = () => Alert.alert('我的收藏', '已清理 1 条不活跃收藏');
  const onDetail = (title: string) => {
    setDetailTitle(title);
    setDetailVisible(true);
  };
  const onCloseDetail = () => {
    setDetailVisible(false);
    setDetailTitle(null);
  };

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.title}>二手置换 · 我的收藏</Text>
        <View style={styles.headerIcons}>
          <Image source={{ uri: ICONS_BASE64.heart }} style={styles.headerIconImg} />
          <Text style={styles.headerEmoji}></Text>
        </View>
      </View>

      <ScrollView style={styles.content}>
        <View style={styles.section}>
          <Text style={styles.sectionTitle}>常看物品</Text>
          <View style={styles.card}>
            <Image source={{ uri: ICONS_BASE64.item }} style={styles.cardImg} />
            <View style={styles.cardText}>
              <Text style={styles.cardTitle}>书桌 · 橡木</Text>
              <Text style={styles.cardSub}>结实耐用 · 需要自取</Text>
            </View>
            <TouchableOpacity onPress={() => onDetail('书桌 · 橡木')}>
              <Text style={styles.tag}>查看</Text>
            </TouchableOpacity>
          </View>
          <View style={styles.card}>
            <Image source={{ uri: ICONS_BASE64.item }} style={styles.cardImg} />
            <View style={styles.cardText}>
              <Text style={styles.cardTitle}>键盘 · 机械轴</Text>
              <Text style={styles.cardSub}>九成新 · 可测试</Text>
            </View>
            <TouchableOpacity onPress={() => onDetail('键盘 · 机械轴')}>
              <Text style={styles.tag}>查看</Text>
            </TouchableOpacity>
          </View>
        </View>

        <View style={styles.sectionAlt}>
          <Text style={styles.sectionTitle}>小提示</Text>
          <View style={styles.tipRow}>
            <Image source={{ uri: ICONS_BASE64.info }} style={styles.tipIcon} />
            <Text style={styles.tipText}>定期清理收藏,有助于更快发现合适物品。</Text>
          </View>
        </View>

        <View style={styles.actionBar}>
          <TouchableOpacity style={styles.actionBtn} onPress={onClear}>
            <Image source={{ uri: ICONS_BASE64.remove }} style={styles.actionIcon} />
            <Text style={styles.actionText}>清理不活跃</Text>
          </TouchableOpacity>
          <TouchableOpacity style={[styles.actionBtn, styles.actionBtnPrimary]} onPress={onClear}>
            <Image source={{ uri: ICONS_BASE64.heart }} style={styles.actionIcon} />
            <Text style={styles.actionTextPrimary}>批量关注</Text>
          </TouchableOpacity>
        </View>
      </ScrollView>
      {detailVisible && (
        <View style={styles.detailOverlay}>
          <View style={styles.detailPanel}>
            <View style={styles.detailHeader}>
              <Text style={styles.detailTitle}>{detailTitle}</Text>
              <TouchableOpacity onPress={onCloseDetail}>
                <Text style={styles.detailClose}>关闭</Text>
              </TouchableOpacity>
            </View>
            <View style={styles.detailBody}>
              <View style={styles.detailRow}>
                <Image source={{ uri: ICONS_BASE64.item }} style={styles.detailIcon} />
                <Text style={styles.detailText}>收藏原因:品质良好,价格合适。</Text>
              </View>
              <View style={styles.detailRow}>
                <Image source={{ uri: ICONS_BASE64.info }} style={styles.detailIcon} />
                <Text style={styles.detailText}>建议:联系卖家确认细节并安排交易。</Text>
              </View>
            </View>
            <View style={styles.detailFooter}>
              <TouchableOpacity style={styles.detailBtn} onPress={() => Alert.alert('联系卖家', '已发送联系请求')}>
                <Text style={styles.detailBtnText}>联系卖家</Text>
              </TouchableOpacity>
              <TouchableOpacity style={[styles.detailBtn, styles.detailBtnPrimary]} onPress={() => Alert.alert('取消收藏', '已取消该收藏')}>
                <Text style={styles.detailBtnTextPrimary}>取消收藏</Text>
              </TouchableOpacity>
            </View>
          </View>
        </View>
      )}
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: '#fff7fb' },
  header: { padding: 16, backgroundColor: '#ffffff', borderBottomWidth: 1, borderBottomColor: '#fecdd3', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
  title: { fontSize: 18, fontWeight: 'bold', color: '#0f172a' },
  headerIcons: { flexDirection: 'row', alignItems: 'center' },
  headerEmoji: { fontSize: 18, marginLeft: 8 },
  headerIconImg: { width: 24, height: 24 },
  content: { padding: 16 },
  section: { backgroundColor: '#ffffff', borderRadius: 12, padding: 14, shadowColor: '#000', shadowOffset: { width: 0, height: 1 }, shadowOpacity: 0.08, shadowRadius: 2 },
  sectionAlt: { backgroundColor: '#fff1f2', borderRadius: 12, padding: 14, marginTop: 16 },
  sectionTitle: { fontSize: 16, fontWeight: 'bold', color: '#0f172a', marginBottom: 10 },
  card: { flexDirection: 'row', alignItems: 'center', paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#f3f4f6' },
  cardImg: { width: 40, height: 40, borderRadius: 8, marginRight: 10, backgroundColor: '#ffe4e6' },
  cardText: { flex: 1 },
  cardTitle: { fontSize: 13, fontWeight: '600', color: '#0f172a' },
  cardSub: { fontSize: 12, color: '#64748b', marginTop: 2 },
  tag: { fontSize: 11, color: '#9d174d', backgroundColor: '#fce7f3', paddingHorizontal: 8, paddingVertical: 4, borderRadius: 10 },
  tipRow: { flexDirection: 'row', alignItems: 'center' },
  tipIcon: { width: 22, height: 22, marginRight: 6 },
  tipText: { fontSize: 12, color: '#475569' },
  actionBar: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 18 },
  actionBtn: { flex: 1, backgroundColor: '#f1f5f9', borderRadius: 12, paddingVertical: 12, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', marginRight: 10 },
  actionBtnPrimary: { backgroundColor: '#fce7f3', marginRight: 0 },
  actionIcon: { width: 16, height: 16, marginRight: 6 },
  actionText: { fontSize: 14, color: '#334155', fontWeight: '500' },
  actionTextPrimary: { fontSize: 14, color: '#9d174d', fontWeight: '600' },
  detailOverlay: { position: 'absolute', left: 0, right: 0, top: 0, bottom: 0, backgroundColor: 'rgba(0,0,0,0.25)', justifyContent: 'center', alignItems: 'center', padding: 16 },
  detailPanel: { width: '100%', maxWidth: 420, backgroundColor: '#ffffff', borderRadius: 14, padding: 14, shadowColor: '#000', shadowOffset: { width: 0, height: 2 }, shadowOpacity: 0.12, shadowRadius: 4 },
  detailHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' },
  detailTitle: { fontSize: 16, fontWeight: '700', color: '#0f172a' },
  detailClose: { fontSize: 12, color: '#9d174d', backgroundColor: '#fce7f3', paddingHorizontal: 8, paddingVertical: 4, borderRadius: 10 },
  detailBody: { marginTop: 10 },
  detailRow: { flexDirection: 'row', alignItems: 'center', marginTop: 8 },
  detailIcon: { width: 18, height: 18, marginRight: 6 },
  detailText: { fontSize: 12, color: '#475569' },
  detailFooter: { flexDirection: 'row', justifyContent: 'flex-end', marginTop: 12 },
  detailBtn: { backgroundColor: '#f1f5f9', borderRadius: 10, paddingVertical: 8, paddingHorizontal: 12, marginRight: 8 },
  detailBtnPrimary: { backgroundColor: '#fce7f3' },
  detailBtnText: { fontSize: 12, color: '#334155', fontWeight: '600' },
  detailBtnTextPrimary: { fontSize: 12, color: '#9d174d', fontWeight: '700' },
});

export default SecondhandMyFavorites;

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

最后运行效果图如下显示:
请添加图片描述

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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐