这个待办事项应用采用了现代 React Native 组件化架构,通过清晰的职责分离实现了模块化设计。核心组件 TodoTabBar 作为主容器,负责状态管理和整体布局,而 TabButton 作为可复用组件,专注于单个标签按钮的渲染和交互处理。这种组件拆分策略不仅提高了代码的可读性和可维护性,也为后续的跨端适配奠定了基础。

标签栏

const TabButton: React.FC<{ 
  title: string, 
  icon: string, 
  isActive: boolean, 
  onPress: () => void 
}> = ({ title, icon, isActive, onPress }) => {
  return (
    <TouchableOpacity 
      style={[styles.tabButton, isActive && styles.activeTab]} 
      onPress={onPress} 
    >
      <Text style={[styles.tabIcon, isActive && styles.activeTabText]}>{icon}</Text>
      <Text style={[styles.tabText, isActive && styles.activeTabText]}>{title}</Text>
    </TouchableOpacity>
  );
};

TabButton 组件设计为高度可复用的通用组件,通过 props 接收标题、图标、激活状态和点击回调函数。使用条件样式 [styles.tabButton, isActive && styles.activeTab] 实现了激活状态的视觉反馈,这种方式简洁明了,易于维护。

状态管理

const [activeTab, setActiveTab] = useState('all');

// 标签数据定义
const tabs = [
  { id: 'all', title: '全部', icon: ICONS.all },
  { id: 'pending', title: '待办', icon: ICONS.pending },
  { id: 'completed', title: '已完成', icon: ICONS.completed },
  { id: 'add', title: '新增', icon: ICONS.add },
  { id: 'settings', title: '设置', icon: ICONS.settings },
];

使用 useState Hook 管理当前激活的标签状态,初始值为 ‘all’。标签数据通过数组定义,包含了标签的唯一标识、标题和图标,这种数据驱动的设计使得标签栏的扩展和修改变得简单直观。

底部标签栏

<View style={styles.tabBar}>
  {tabs.map(tab => (
    <TabButton
      key={tab.id}
      title={tab.title}
      icon={tab.icon}
      isActive={activeTab === tab.id}
      onPress={() => setActiveTab(tab.id)}
    />
  ))}
</View>

底部标签栏使用绝对定位实现,通过 position: 'absolute' 固定在屏幕底部。这种布局方式在移动应用中非常常见,尤其是在需要快速访问核心功能的场景中。标签按钮使用 flex: 1 实现了均匀分布,确保了在不同尺寸的屏幕上都能保持良好的视觉效果。

任务列表

<View style={styles.tasksContainer}>
  <Text style={styles.sectionTitle}>今日任务</Text>
  <View style={styles.taskItem}>
    <Text style={styles.taskText}>完成项目报告</Text>
    <Text style={styles.taskStatus}>待办</Text>
  </View>
  {/* 更多任务项 */}
</View>

任务列表采用卡片式布局,每个任务分类(今日任务、明日计划)作为一个独立的卡片。任务项使用 flexDirection: 'row'justifyContent: 'space-between' 实现了任务文本和状态的左右分布,这种布局方式清晰明了,符合用户的阅读习惯。


  1. 组件映射

    • SafeAreaViewSafeAreaFlex 容器
    • ScrollViewScroll 组件
    • TouchableOpacityButtonText + 手势事件
    • ViewFlex 容器
    • TextText 组件
  2. 布局适配

    • 鸿蒙系统的布局系统基于 Flexbox,与 React Native 兼容
    • 绝对定位在鸿蒙中同样支持,但需要注意不同平台的实现差异
    • 响应式布局计算逻辑可以直接复用,但需要注意获取屏幕尺寸的 API 差异
  3. 样式适配

    • 鸿蒙系统的样式定义方式与 React Native 类似,但属性名称可能略有不同
    • 例如,shadow 相关属性在鸿蒙中需要使用 elevation 或其他等效属性
    • 尺寸单位在鸿蒙中默认使用 vp,与 React Native 的密度无关像素概念类似
  4. 状态管理适配

    • React Native 中使用 useState Hook 管理状态
    • 鸿蒙系统中可以使用 @State 装饰器实现类似的状态管理
    • 对于复杂状态,可以使用鸿蒙的 AppStorageLocalStorage
  5. 类型定义优化

    • 建议为组件和数据添加明确的 TypeScript 类型定义,提高代码的可维护性和类型安全性:
    interface Tab {
      id: string;
      title: string;
      icon: string;
    }
    
    interface Task {
      id: string;
      text: string;
      status: 'pending' | 'completed';
      date: string;
    }
    
  6. 组件复用优化

    • 考虑使用 React.memo 包裹 TabButton 组件,减少不必要的重渲染:
    const TabButton: React.FC<{ 
      title: string, 
      icon: string, 
      isActive: boolean, 
      onPress: () => void 
    }> = React.memo(({ title, icon, isActive, onPress }) => {
      // 实现...
    });
    
  7. 列表

    • 对于较长的任务列表,建议使用 FlatList 替代 ScrollView + map 的组合,利用虚拟化提升滚动性能:
    <FlatList
      data={tasks}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => (
        <View style={styles.taskItem}>
          <Text style={styles.taskText}>{item.text}</Text>
          <Text style={styles.taskStatus}>{item.status}</Text>
        </View>
      )}
      ListHeaderComponent={
        <Text style={styles.sectionTitle}>今日任务</Text>
      }
    />
    
  8. 可访问性

    • 添加 accessibilityLabel 属性,提高应用的可访问性:
    <TouchableOpacity 
      style={[styles.tabButton, isActive && styles.activeTab]} 
      onPress={onPress}
      accessibilityLabel={`${title}标签`}
    >
      {/* 内容 */}
    </TouchableOpacity>
    

在将此组件迁移到鸿蒙系统时,需要注意以下几点:

  1. 组件转换

    • 使用鸿蒙 ArkUI 框架的组件体系,将 React Native 组件映射为对应的 ArkUI 组件
    • 保持组件树结构和逻辑不变,只替换具体的组件实现
  2. 状态管理

    • 鸿蒙系统中可以使用 @State@Prop 等装饰器实现类似的状态管理
    • 对于跨组件通信,可以使用事件总线或全局状态管理方案
  3. 布局系统

    • 鸿蒙系统的布局系统基于 Flexbox,与 React Native 兼容
    • 绝对定位在鸿蒙中同样支持,但需要注意不同平台的实现差异
  4. 标签栏实现

    • 鸿蒙系统中可以使用 TabBar 组件或自定义实现底部导航栏
    • 自定义实现时,需要注意鸿蒙的布局和样式系统特性
  5. 事件处理

    • 鸿蒙系统的事件处理机制与 React Native 类似,但语法可能不同
    • 例如,React Native 中使用 onPress,鸿蒙中可能使用 onClick 或其他等效属性

这个待办事项标签栏页面展示了 React Native 跨端开发的核心技术要点,通过合理的组件设计、清晰的状态管理和细致的样式管理,实现了一个功能完整、用户体验良好的待办事项应用。在鸿蒙系统适配方面,通过组件映射、样式适配和布局调整,可以快速实现跨端迁移,保持功能和视觉效果的一致性。


在React Native跨端开发体系中,底部Tab导航是移动端应用的核心交互范式,而可复用组件封装状态驱动的导航切换则是实现该范式的关键技术要点。本次解析的待办事项标签栏页面,基于React Native函数式组件与Hooks能力构建,完成了自定义底部TabBar的开发、可复用TabButton组件的封装、任务列表的模块化展示,同时实现了Tab切换的状态驱动更新,所有技术实现均基于React Native通用API与标准开发模式,可无缝适配React Native for HarmonyOS鸿蒙桥接方案,实现一套代码在Android、iOS、鸿蒙多端的原生渲染与交互一致性。本文将从通用组件封装、状态驱动Tab切换、布局适配设计、样式系统优化、鸿蒙跨端适配核心要点等维度,深度解读该页面的技术实现思路,同时剖析其在跨端应用开发中的工程化设计价值。

一、技术栈

本页面延续React Native + TypeScript的核心技术栈,以函数式组件为基础架构,结合useState实现状态管理,同时完成了自定义组件的封装,所有核心能力的选型均围绕鸿蒙跨端兼容组件复用性原生体验一致性三大原则展开,未引入第三方依赖,保证了代码的轻量性与多端适配性,核心选型与技术特性如下:

  • 基础UI与布局组件SafeAreaViewViewTextTouchableOpacity作为核心载体,均为React Native for HarmonyOS已完成深度桥接的基础组件,底层会被编译为各平台原生组件,基于原生渲染引擎执行,规避WebView渲染的性能损耗,保证鸿蒙、Android、iOS多端的原生视觉与交互体验;
  • 滚动与适配组件ScrollView承担任务列表的滚动容器职责,桥接各平台原生滚动控件,保证鸿蒙大屏设备(平板、折叠屏)的滚动流畅性,DimensionsAPI获取设备窗口宽度,为后续动态布局拓展提供基础,二者均为跨端屏幕适配的标准能力;
  • React核心能力useState实现Tab激活状态的管理,遵循React声明式编程状态驱动渲染核心思想,该思想与鸿蒙ArkUI的@State装饰器状态管理理念高度统一,为跨端业务逻辑的一致性提供基础;
  • 自定义组件封装:将Tab按钮封装为通用的TabButton组件,实现视图与逻辑的解耦,提升代码复用性,该封装方式符合React组件化开发规范,同时与鸿蒙ArkUI的自定义组件开发思路一致,便于跨端迭代与维护;
  • 轻量资源管理:以键值对形式封装ICONS全局图标库,选用系统原生支持的Emoji作为视觉元素,避免第三方图标库在鸿蒙平台的桥接兼容问题,减少项目依赖体积,提升多端编译与启动效率。

本页面的技术选型既满足了待办事项应用的业务需求,又为后续功能拓展(如Tab对应内容切换、任务增删改查)做好了技术铺垫,保证了代码的可扩展性与跨端迭代的平滑性。

二、通用组件封装

将底部Tab的单个按钮封装为可复用的TabButton组件,是本页面最核心的工程化设计亮点,该组件遵循单一职责原则,仅负责Tab按钮的视图渲染与点击事件触发,通过Props接收外部传递的参数,实现了组件的高度解耦与复用,其封装思路完全符合React Native跨端开发的组件化理念,同时与鸿蒙ArkUI的自定义组件开发规范高度契合,核心技术实现要点如下:

1. TypeScript强类型

TabButton组件通过React.FC<{ title: string, icon: string, isActive: boolean, onPress: () => void }>为Props添加了完整的TypeScript类型约束,明确了四个必传参数的类型:title为标签文本、icon为图标字符、isActive为激活状态标识、onPress为点击回调函数。强类型约束不仅能在开发阶段通过TS编译器检测出传参类型错误(如将数字类型传递给title),还为跨端团队协作提供了统一的入参规范——无论是React Native开发者还是鸿蒙原生开发者,均可通过类型定义快速理解组件的使用方式,无需阅读额外的注释文档,大幅提升了跨端开发的协作效率。

在鸿蒙跨端开发中,该类型约束可直接复用至鸿蒙ArkUI的自定义组件中,仅需将React的Props类型转换为ArkUI的@Prop装饰器类型,即可实现鸿蒙端的类型约束,保证了多端组件开发的类型一致性。

2. Props透传

TabButton组件内部未定义任何硬编码的内容与状态,所有视图展示的内容(标题、图标)、状态(是否激活)、行为(点击事件)均通过Props从外部透传而来,实现了组件与业务逻辑的完全解耦。例如,标签的“全部”“待办”文本、📚``⏳图标,均由父组件TodoTabBar通过Props传递,组件内部仅通过{title}``{icon}进行渲染;Tab的激活状态由父组件的activeTab状态控制,通过isActiveProps传递;点击事件由父组件定义的setActiveTab逻辑控制,通过onPressProps传递。

这种解耦设计在跨端开发中具有极高的工程价值:当需要针对鸿蒙平台定制Tab按钮的内容(如修改“新增”为“添加任务”)、样式(如调整图标尺寸)时,仅需在父组件中修改传递的Props参数,无需修改TabButton组件的内部代码;当需要在其他页面复用Tab按钮时,仅需传递不同的Props参数,即可实现不同的展示效果与点击逻辑,大幅提升了跨端开发的代码复用率与维护效率。

3. 条件样式

TabButton组件通过样式数组拼接的方式实现条件样式渲染,核心代码style={[styles.tabButton, isActive && styles.activeTab]}Text style={[styles.tabIcon, isActive && styles.activeTabText]},实现了“默认样式+激活样式”的动态切换。当isActivetrue时,会自动拼接激活样式,覆盖默认样式的部分属性(如背景色、文字颜色);当isActivefalse时,仅渲染默认样式。

这种条件样式实现方式是React Native的标准开发模式,完全基于JavaScript的逻辑判断,无任何平台专属API,在鸿蒙平台中,React Native for HarmonyOS会将样式数组的拼接逻辑完整桥接,自动合并默认样式与激活样式,实现与Android、iOS完全一致的样式切换效果。与鸿蒙ArkUI的if/else条件样式、@State驱动样式变化相比,二者的设计思想高度统一,均是通过状态控制样式的动态切换,保证了跨端样式开发的逻辑一致性。


真实演示案例代码:



// app.tsx
import React, { useState } from 'react';
import { SafeAreaView, View, Text, StyleSheet, TouchableOpacity, ScrollView, Dimensions } from 'react-native';

// 图标库
const ICONS = {
  home: '🏠',
  list: '📋',
  add: '➕',
  user: '👤',
  search: '🔍',
  settings: '⚙️',
  calendar: '📅',
  stats: '📊',
  completed: '✅',
  pending: '⏳',
  all: '📚',
};

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

const TabButton: React.FC<{ 
  title: string, 
  icon: string, 
  isActive: boolean, 
  onPress: () => void 
}> = ({ title, icon, isActive, onPress }) => {
  return (
    <TouchableOpacity 
      style={[styles.tabButton, isActive && styles.activeTab]}
      onPress={onPress}
    >
      <Text style={[styles.tabIcon, isActive && styles.activeTabText]}>{icon}</Text>
      <Text style={[styles.tabText, isActive && styles.activeTabText]}>{title}</Text>
    </TouchableOpacity>
  );
};

const TodoTabBar: React.FC = () => {
  const [activeTab, setActiveTab] = useState('all');

  const tabs = [
    { id: 'all', title: '全部', icon: ICONS.all },
    { id: 'pending', title: '待办', icon: ICONS.pending },
    { id: 'completed', title: '已完成', icon: ICONS.completed },
    { id: 'add', title: '新增', icon: ICONS.add },
    { id: 'settings', title: '设置', icon: ICONS.settings },
  ];

  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.title}>待办事项</Text>
        <Text style={styles.subtitle}>高效管理您的任务</Text>
      </View>

      <ScrollView contentContainerStyle={styles.content}>
        <View style={styles.tasksContainer}>
          <Text style={styles.sectionTitle}>今日任务</Text>
          <View style={styles.taskItem}>
            <Text style={styles.taskText}>完成项目报告</Text>
            <Text style={styles.taskStatus}>待办</Text>
          </View>
          <View style={styles.taskItem}>
            <Text style={styles.taskText}>购买生活用品</Text>
            <Text style={styles.taskStatus}>已完成</Text>
          </View>
          <View style={styles.taskItem}>
            <Text style={styles.taskText}>健身房锻炼</Text>
            <Text style={styles.taskStatus}>待办</Text>
          </View>
        </View>
        
        <View style={styles.tasksContainer}>
          <Text style={styles.sectionTitle}>明日计划</Text>
          <View style={styles.taskItem}>
            <Text style={styles.taskText}>会议准备</Text>
            <Text style={styles.taskStatus}>待办</Text>
          </View>
          <View style={styles.taskItem}>
            <Text style={styles.taskText}>朋友聚餐</Text>
            <Text style={styles.taskStatus}>待办</Text>
          </View>
        </View>
      </ScrollView>

      <View style={styles.tabBar}>
        {tabs.map(tab => (
          <TabButton
            key={tab.id}
            title={tab.title}
            icon={tab.icon}
            isActive={activeTab === tab.id}
            onPress={() => setActiveTab(tab.id)}
          />
        ))}
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8f9fa',
  },
  header: {
    paddingTop: 40,
    paddingBottom: 20,
    paddingHorizontal: 20,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#e9ecef',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    color: '#333',
    textAlign: 'center',
  },
  subtitle: {
    fontSize: 16,
    color: '#666',
    textAlign: 'center',
    marginTop: 8,
  },
  content: {
    padding: 16,
    paddingBottom: 80, // 为底部标签栏留出空间
  },
  tasksContainer: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4,
  },
  sectionTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 12,
  },
  taskItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    paddingVertical: 12,
    borderBottomWidth: 1,
    borderBottomColor: '#f0f0f0',
  },
  taskText: {
    fontSize: 16,
    color: '#333',
  },
  taskStatus: {
    fontSize: 12,
    color: '#666',
    backgroundColor: '#e2e8f0',
    paddingHorizontal: 8,
    paddingVertical: 4,
    borderRadius: 12,
  },
  tabBar: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    alignItems: 'center',
    backgroundColor: '#ffffff',
    borderTopWidth: 1,
    borderTopColor: '#e9ecef',
    paddingVertical: 12,
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
  },
  tabButton: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
    paddingVertical: 8,
  },
  activeTab: {
    backgroundColor: '#dbeafe',
    borderRadius: 8,
  },
  tabIcon: {
    fontSize: 20,
    marginBottom: 4,
    color: '#666',
  },
  activeTabText: {
    color: '#3B82F6',
    fontWeight: 'bold',
  },
  tabText: {
    fontSize: 12,
    color: '#666',
  },
});

export default TodoTabBar;

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
React Native跨端开发中,底部Tab导航通过组件化实现高效复用,利用Hooks管理状态驱动导航切换。该待办事项应用采用函数式组件架构,封装可复用的TabButton组件,配合useState管理激活状态,实现标签栏与任务列表的联动展示。所有实现基于RN标准API,确保跨平台一致性,为后续多端适配提供技术基础。

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

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐