该待办事项列表组件采用了典型的功能组件设计,结合状态驱动的 UI 渲染模式。组件核心由待办事项列表、浮动操作按钮(FAB)和新增表单三部分组成,通过状态管理实现各模块间的协同工作。这种模块化设计在 React Native 鸿蒙跨端开发中具有良好的适应性,能够清晰地映射到不同平台的组件架构。

组件使用 useState Hook 管理待办事项数据、表单可见性及表单字段状态,这是 React 生态中最基础且广泛使用的状态管理方式。在 HarmonyOS ArkUI 中,useState 可以映射到 @State 装饰器,两者都能实现状态的响应式更新,确保 UI 与数据的一致性。

数据模型

应用采用 TypeScript 定义了 TodoItem 类型,包含 idtitledescriptioncompletedprioritydueDatecreatedAt 等属性。强类型设计在跨端开发中具有显著优势:

  • 编译阶段捕获类型错误,减少运行时异常
  • 提供清晰的接口定义,便于团队协作
  • 支持 IDE 智能提示,提高开发效率
  • 确保数据结构在不同平台上的一致性

priority 字段采用联合类型 'low' | 'medium' | 'high',限制了可选值范围,增强了数据的可靠性。这种类型约束在 HarmonyOS ArkUI 中同样受到支持,能够直接复用。

UI 组件

组件使用了 React Native 核心组件库构建用户界面,主要包括:

  • 核心布局组件SafeAreaView 确保内容在安全区域内显示,适配不同设备的刘海屏和底部手势区域;ScrollViewFlatList(从 renderTodoItem 函数可推断)用于高效渲染待办事项列表。

  • 交互组件TouchableOpacity 实现可点击的待办事项卡片、复选框和浮动操作按钮,提供视觉反馈;TextInput 用于表单输入,支持多行文本和不同键盘类型。

  • 样式系统StyleSheet 实现样式与逻辑分离,采用 Flexbox 布局构建响应式界面;Dimensions 获取屏幕尺寸,确保在不同设备上的良好显示效果。

  • 反馈组件Alert 用于操作确认和提示信息,如删除待办事项时的确认对话框。

在 HarmonyOS ArkUI 中,这些组件可以映射到对应的 ArkUI 组件:

  • SafeAreaView 对应 ArkUI 的 SafeArea 组件
  • FlatList 对应 ArkUI 的 List 组件,同样支持虚拟列表优化
  • TouchableOpacity 对应 ArkUI 的 ButtonGesture 组件
  • TextInput 对应 ArkUI 的 TextInput 组件
  • Alert 对应 ArkUI 的 dialog.showAlertDialog API

状态管理

组件的状态管理集中在待办事项数据和表单状态两个方面:

  1. 待办事项管理

    • addTodo 函数实现新增待办事项逻辑,包括表单验证、数据创建和状态更新
    • toggleComplete 函数切换待办事项的完成状态,采用不可变数据更新方式
    • deleteTodo 函数实现待办事项删除,包含确认对话框
  2. 表单状态管理

    • showAddForm 控制新增表单的显示与隐藏
    • 表单字段(标题、描述、优先级、截止日期)各自对应独立的状态变量
    • resetForm 函数重置表单状态

这些业务逻辑在跨端开发中可以直接复用,无需针对不同平台进行修改。需要注意的是,React Native 的状态更新是异步的,而 ArkUI 的状态更新可能是同步的,但这种差异对业务逻辑的影响较小,可以通过统一的状态管理模式来处理。

响应式布局

组件使用 StyleSheet 定义样式,采用了模块化的样式设计,将不同组件的样式分离,便于维护和扩展。样式系统基于 Flexbox 布局,能够自适应不同屏幕尺寸,这在 React Native 和 HarmonyOS ArkUI 中都有良好的支持。

组件中的浮动操作按钮(FAB)采用绝对定位实现,通过 bottomright 属性固定在屏幕右下角,这种设计在移动应用中非常常见,能够快速访问核心功能。在 HarmonyOS ArkUI 中,可以使用 Position 属性实现类似的定位效果。

优先级标签的样式根据优先级动态变化,通过 getPriorityColor 函数返回不同的颜色值,这种动态样式在跨端开发中可以直接复用,无需针对不同平台进行修改。

用户体验

组件实现了丰富的交互功能,包括:

  • 待办事项的添加、删除和状态切换
  • 优先级的视觉区分
  • 完成状态的直观反馈(勾选图标和删除线)
  • 表单验证和操作确认
  • 浮动操作按钮的快捷访问

这些交互设计在跨端开发中需要保持一致性,确保用户在不同平台上获得相似的体验。例如,删除操作的确认对话框、表单验证的提示信息、完成状态的视觉反馈等,都应该在 React Native 和 HarmonyOS 上保持一致。


该待办事项列表组件展示了 React Native 鸿蒙跨端开发的核心技术要点,包括组件设计、状态管理、UI 实现、交互设计和样式系统等。通过合理的架构设计和技术选型,可以实现高效、一致的跨端应用开发。

随着 HarmonyOS 生态的不断发展和 React Native 跨端工具链的优化,开发者将能够更便捷地构建跨平台的高质量应用。掌握 React Native 与 HarmonyOS 跨端开发技术,将为开发者带来更广阔的发展空间和更多的应用场景。

该待办事项组件的设计思路和实现方式为 React Native 鸿蒙跨端开发提供了良好的参考,展示了如何通过合理的架构设计和技术选型,实现高效、一致的跨端应用开发。


待办清单(Todo List)作为移动端高频交互的核心场景,其“增删改查+状态管理+优先级可视化”的复合能力,是检验跨端开发一致性的典型案例。本文以 React Native 开发的待办清单组件(含浮动操作按钮FAB、表单交互、状态可视化)为例,深度拆解其“数据模型设计、状态驱动交互、列表渲染优化、表单封装”的核心实现逻辑,并系统阐述向鸿蒙(HarmonyOS)ArkTS 跨端迁移的技术路径,聚焦“状态管理等价转换、列表渲染跨端一致、表单交互语义复用”三大核心维度,为移动端待办类组件的跨端开发提供可落地的技术参考。

状态管理

该待办清单组件构建了“多层级状态管理 + 强类型数据约束”的底层架构,完全贴合待办场景的业务特性,也是跨端复用的核心基础:

  • 业务类型定义:TodoItem 类型精准覆盖待办项的全维度属性,包含 id(唯一标识)、title(标题)、description(描述)、completed(完成状态)、priority(优先级)、dueDate(截止日期)、createdAt(创建时间),7 个字段完整支撑待办项的展示、状态、时效管理;priority 采用联合类型(low/medium/high)、completed 采用布尔类型,通过 TypeScript 强类型约束避免非法数据录入,为跨端数据一致性提供底层保障。
  • 多层级状态设计:
    • 核心数据状态:todos 管理待办项数组,初始值预置 5 个覆盖不同优先级、完成状态的待办项,模拟真实业务场景;
    • 表单交互状态:showAddForm 控制添加表单的显隐,newTodoTitle/newTodoDescription 等管理表单输入值,newTodoPriority 限定优先级选择范围,形成完整的表单状态闭环;
    • 状态更新策略:所有状态更新均遵循 React 不可变数据原则(如 setTodos([newTodo, ...todos])map/filter 操作原数组),避免直接修改数据导致的状态不一致,也为跨端复用提供无副作用的纯逻辑资产。
  • 核心业务逻辑:封装 addTodo(添加待办)、toggleComplete(切换完成状态)、deleteTodo(删除待办)、resetForm(重置表单)等纯函数,每个函数职责单一——addTodo 包含表单校验、数据构造、状态更新、反馈提示全流程,deleteTodo 封装确认弹窗逻辑,toggleComplete 仅修改目标项的 completed 状态,这种模块化的逻辑封装大幅提升跨端复用效率。

列表渲染优化与视觉状态可视化

React Native 端采用 FlatList 替代基础 ScrollView 实现待办列表渲染,结合“状态驱动样式”实现优先级、完成状态的可视化,兼顾性能与体验:

  • 高性能列表渲染:
    • 选用 FlatList 而非 map 遍历 View,利用其“懒加载、复用单元格、减少重绘”的特性,优化长列表渲染性能;
    • 显式指定 keyExtractor={item => item.id},保证列表项的唯一性标识,避免列表更新时的不必要重渲染;
    • 关闭垂直滚动指示器(showsVerticalScrollIndicator={false}),提升视觉简洁度。
  • 状态可视化设计:
    • 完成状态映射:通过 completed 状态动态控制待办项背景色(完成:#f1f5f9、未完成:#ffffff)、文本删除线(textDecorationLine: item.completed ? 'line-through' : 'none')、复选框图标(完成:✓、未完成:○),形成多维度的完成状态视觉反馈;
    • 优先级可视化:封装 getPriorityColor 纯函数,实现“优先级 → 颜色”的映射(高:红色 #ef4444、中:橙色 #f59e0b、低:绿色 #10b981),并通过 priorityBadge 徽章组件展示优先级文本,徽章背景色采用“主色 + 20% 透明度”(如 ${getPriorityColor(item.priority)}20),既突出优先级又保证视觉柔和度;
    • 时效提示:截止日期文本采用红色(#ef4444)高亮,强化时效感知,创建时间采用浅灰色弱化展示,形成信息层级。

表单封装

React Native 端将添加待办的表单封装为独立模块,通过条件渲染控制显隐,结合浮动操作按钮(FAB)实现“快速添加”的移动端交互范式:

  • 表单结构设计:表单包含标题输入、描述多行输入、优先级选择、截止日期输入、操作按钮五大模块,采用 flexDirection: 'row' 实现优先级与日期的横向布局,兼顾空间利用率与操作便捷性;
  • 优先级选择交互:通过三个 TouchableOpacity 封装优先级选项,利用 newTodoPriority === 'low' && styles.priorityOptionSelected 实现选中状态的样式高亮,形成清晰的选择反馈;
  • 浮动操作按钮(FAB):固定在页面右下角的圆形按钮,点击切换 showAddForm 状态,图标同步切换(+ / ✕),符合移动端“快捷操作”的交互习惯;
  • 表单校验与反馈:addTodo 函数首先校验标题是否为空,为空则弹出提示;添加成功后重置表单、隐藏表单、弹出成功提示,形成完整的交互闭环。

样式分层

该组件的样式设计遵循“功能分层 + 视觉层级”原则,将样式分为容器层、列表项层、表单层、辅助层四大类,每层样式职责单一,便于跨端迁移时逐层级等价转换:

  • 容器层样式:statsCard(统计卡片)、todosContainer(列表容器)、addForm(表单容器)统一采用 borderRadius: 12elevation: 1 阴影,保证视觉风格一致性;
  • 列表项层样式:todoItem 采用 borderRadius: 8 圆角、marginBottom: 12 间距,内部文本通过不同的 fontSize/color 区分层级(标题:16px 中粗、描述:14px 灰色、时效文本:12px);
  • 表单层样式:输入框采用 borderWidth: 1 边框、borderRadius: 8 圆角,按钮区分“取消(浅灰)”和“保存(蓝色)”,强化操作意图;
  • 辅助层样式:统计卡片采用横向布局展示“总计/已完成/待完成”数据,底部导航的 activeNavItem 通过顶部 2px 蓝色边框标识当前选中项,符合移动端导航设计规范。

核心技术体系的等价映射

待办清单组件跨端适配的核心是“数据模型 100% 复用、状态管理等价转换、列表渲染语义一致、表单交互复刻”,React Native 与鸿蒙 ArkTS 的核心能力可实现精准映射,以下是关键技术点的等价转换:

React Native 核心能力 鸿蒙 ArkTS 等价实现 待办组件适配要点
TypeScript 类型定义(TodoItem) TypeScript 类型定义(TodoItem) 完全复用类型定义,保证待办项数据结构一致性
useState 状态管理 @State 装饰器 多层级状态一一映射,更新逻辑语义等价
FlatList 列表渲染 List 组件 懒加载、key 标识、渲染函数等价复用
TouchableOpacity 点击交互 Gesture 手势/Button 组件 onPressonTap/onClick,交互逻辑一致
条件渲染(表单显隐) if 条件语句 复用 showAddForm 控制表单显隐逻辑
TextInput 输入框 TextInput 组件 单行/多行输入、占位符、值绑定等价映射
Alert.alert 弹窗反馈 AlertDialog.show 确认弹窗、提示弹窗内容完全复用
StyleSheet.create 样式管理 内联样式/样式常量 样式属性等价映射(backgroundColor/borderRadius 等)
FAB 浮动按钮 Position.Fixed + 圆形组件 固定定位、点击交互、图标切换逻辑一致

随着移动办公需求的提升,此类任务管理组件将成为生产力应用的核心模块。其技术实现的质量和跨端一致性,将直接影响产品的使用效率和用户满意度。


真实演示案例代码:



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

// Base64 图标库
const ICONS_BASE64 = {
  add: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  edit: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  delete: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  check: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  close: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  settings: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  filter: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
  home: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==',
};

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

// 待办事项类型
type TodoItem = {
  id: string;
  title: string;
  description: string;
  completed: boolean;
  priority: 'low' | 'medium' | 'high';
  dueDate: string;
  createdAt: string;
};

const TodoListWithFAB: React.FC = () => {
  const [todos, setTodos] = useState<TodoItem[]>([
    { 
      id: '1', 
      title: '完成项目设计稿', 
      description: '完成新项目的UI设计和交互原型', 
      completed: false, 
      priority: 'high', 
      dueDate: '2023-06-10', 
      createdAt: '2023-06-01' 
    },
    { 
      id: '2', 
      title: '购买生活用品', 
      description: '去超市购买日用品和食物', 
      completed: true, 
      priority: 'medium', 
      dueDate: '2023-06-05', 
      createdAt: '2023-05-30' 
    },
    { 
      id: '3', 
      title: '预约医生', 
      description: '预约年度体检', 
      completed: false, 
      priority: 'high', 
      dueDate: '2023-06-15', 
      createdAt: '2023-06-02' 
    },
    { 
      id: '4', 
      title: '学习React Native', 
      description: '完成教程第三章的学习', 
      completed: false, 
      priority: 'medium', 
      dueDate: '2023-06-20', 
      createdAt: '2023-06-03' 
    },
    { 
      id: '5', 
      title: '整理衣柜', 
      description: '整理春夏衣物,准备收纳', 
      completed: false, 
      priority: 'low', 
      dueDate: '2023-06-25', 
      createdAt: '2023-06-01' 
    },
  ]);

  const [showAddForm, setShowAddForm] = useState<boolean>(false);
  const [newTodoTitle, setNewTodoTitle] = useState<string>('');
  const [newTodoDescription, setNewTodoDescription] = useState<string>('');
  const [newTodoPriority, setNewTodoPriority] = useState<'low' | 'medium' | 'high'>('medium');
  const [newTodoDueDate, setNewTodoDueDate] = useState<string>('2023-06-10');

  // 添加新待办事项
  const addTodo = () => {
    if (!newTodoTitle.trim()) {
      Alert.alert('提示', '请输入待办事项标题');
      return;
    }

    const newTodo: TodoItem = {
      id: `${todos.length + 1}`,
      title: newTodoTitle,
      description: newTodoDescription,
      completed: false,
      priority: newTodoPriority,
      dueDate: newTodoDueDate,
      createdAt: new Date().toISOString().split('T')[0]
    };

    setTodos([newTodo, ...todos]);
    resetForm();
    setShowAddForm(false);
    Alert.alert('成功', '待办事项已添加');
  };

  // 重置表单
  const resetForm = () => {
    setNewTodoTitle('');
    setNewTodoDescription('');
    setNewTodoPriority('medium');
    setNewTodoDueDate('2023-06-10');
  };

  // 切换完成状态
  const toggleComplete = (id: string) => {
    setTodos(todos.map(todo => 
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };

  // 删除待办事项
  const deleteTodo = (id: string) => {
    Alert.alert(
      '确认删除',
      '确定要删除这个待办事项吗?',
      [
        { text: '取消', style: 'cancel' },
        { 
          text: '删除', 
          style: 'destructive',
          onPress: () => setTodos(todos.filter(todo => todo.id !== id))
        }
      ]
    );
  };

  // 获取优先级颜色
  const getPriorityColor = (priority: string): string => {
    switch (priority) {
      case 'high': return '#ef4444'; // 红色
      case 'medium': return '#f59e0b'; // 橙色
      case 'low': return '#10b981'; // 绿色
      default: return '#64748b'; // 灰色
    }
  };

  // 渲染待办事项项
  const renderTodoItem = ({ item }: { item: TodoItem }) => (
    <View style={[
      styles.todoItem,
      { backgroundColor: item.completed ? '#f1f5f9' : '#ffffff' }
    ]}>
      <TouchableOpacity 
        style={styles.todoCheckbox}
        onPress={() => toggleComplete(item.id)}
      >
        <Text style={[
          styles.checkboxIcon,
          { color: item.completed ? '#10b981' : '#94a3b8' }
        ]}>
          {item.completed ? '✓' : '○'}
        </Text>
      </TouchableOpacity>
      
      <View style={styles.todoContent}>
        <View style={styles.todoHeader}>
          <Text style={[
            styles.todoTitle,
            { textDecorationLine: item.completed ? 'line-through' : 'none' }
          ]}>
            {item.title}
          </Text>
          <View style={[
            styles.priorityBadge,
            { backgroundColor: `${getPriorityColor(item.priority)}20`, borderColor: getPriorityColor(item.priority) }
          ]}>
            <Text style={[
              styles.priorityText,
              { color: getPriorityColor(item.priority) }
            ]}>
              {item.priority === 'high' ? '高' : item.priority === 'medium' ? '中' : '低'}
            </Text>
          </View>
        </View>
        
        <Text style={[
          styles.todoDescription,
          { textDecorationLine: item.completed ? 'line-through' : 'none' }
        ]}>
          {item.description}
        </Text>
        
        <View style={styles.todoMeta}>
          <Text style={styles.dueDate}>截止: {item.dueDate}</Text>
          <Text style={styles.createdAt}>{item.createdAt}</Text>
        </View>
      </View>
      
      <TouchableOpacity 
        style={styles.deleteButton}
        onPress={() => deleteTodo(item.id)}
      >
        <Text style={styles.deleteIcon}></Text>
      </TouchableOpacity>
    </View>
  );

  return (
    <SafeAreaView style={styles.container}>
      {/* 头部 */}
      <View style={styles.header}>
        <Text style={styles.title}>我的待办清单</Text>
        <Text style={styles.subtitle}>{todos.filter(t => !t.completed).length} 个待办</Text>
      </View>

      <ScrollView style={styles.content}>
        {/* 统计卡片 */}
        <View style={styles.statsCard}>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{todos.length}</Text>
            <Text style={styles.statLabel}>总计</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{todos.filter(t => t.completed).length}</Text>
            <Text style={styles.statLabel}>已完成</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statNumber}>{todos.filter(t => !t.completed).length}</Text>
            <Text style={styles.statLabel}>待完成</Text>
          </View>
        </View>

        {/* 待办列表 */}
        <View style={styles.todosContainer}>
          <FlatList
            data={todos}
            renderItem={renderTodoItem}
            keyExtractor={item => item.id}
            showsVerticalScrollIndicator={false}
          />
        </View>

        {/* 添加表单 */}
        {showAddForm && (
          <View style={styles.addForm}>
            <Text style={styles.addFormTitle}>添加新待办</Text>
            
            <TextInput
              style={styles.input}
              placeholder="待办事项标题"
              value={newTodoTitle}
              onChangeText={setNewTodoTitle}
            />
            
            <TextInput
              style={[styles.input, styles.textArea]}
              placeholder="描述(可选)"
              value={newTodoDescription}
              onChangeText={setNewTodoDescription}
              multiline
            />
            
            <View style={styles.formRow}>
              <View style={styles.formGroup}>
                <Text style={styles.label}>优先级</Text>
                <View style={styles.priorityOptions}>
                  <TouchableOpacity 
                    style={[
                      styles.priorityOption,
                      newTodoPriority === 'low' && styles.priorityOptionSelected
                    ]}
                    onPress={() => setNewTodoPriority('low')}
                  >
                    <Text style={[
                      styles.priorityOptionText,
                      newTodoPriority === 'low' && styles.priorityOptionTextSelected
                    ]}></Text>
                  </TouchableOpacity>
                  <TouchableOpacity 
                    style={[
                      styles.priorityOption,
                      newTodoPriority === 'medium' && styles.priorityOptionSelected
                    ]}
                    onPress={() => setNewTodoPriority('medium')}
                  >
                    <Text style={[
                      styles.priorityOptionText,
                      newTodoPriority === 'medium' && styles.priorityOptionTextSelected
                    ]}></Text>
                  </TouchableOpacity>
                  <TouchableOpacity 
                    style={[
                      styles.priorityOption,
                      newTodoPriority === 'high' && styles.priorityOptionSelected
                    ]}
                    onPress={() => setNewTodoPriority('high')}
                  >
                    <Text style={[
                      styles.priorityOptionText,
                      newTodoPriority === 'high' && styles.priorityOptionTextSelected
                    ]}></Text>
                  </TouchableOpacity>
                </View>
              </View>
              
              <View style={styles.formGroup}>
                <Text style={styles.label}>截止日期</Text>
                <TextInput
                  style={styles.dateInput}
                  value={newTodoDueDate}
                  onChangeText={setNewTodoDueDate}
                  placeholder="YYYY-MM-DD"
                />
              </View>
            </View>
            
            <View style={styles.formButtons}>
              <TouchableOpacity 
                style={styles.cancelButton}
                onPress={() => setShowAddForm(false)}
              >
                <Text style={styles.cancelButtonText}>取消</Text>
              </TouchableOpacity>
              <TouchableOpacity 
                style={styles.saveButton}
                onPress={addTodo}
              >
                <Text style={styles.saveButtonText}>保存</Text>
              </TouchableOpacity>
            </View>
          </View>
        )}

        {/* 使用说明 */}
        <View style={styles.infoCard}>
          <Text style={styles.infoTitle}>使用说明</Text>
          <Text style={styles.infoText}>• 点击圆圈标记完成/未完成</Text>
          <Text style={styles.infoText}>• 点击右上角×删除待办事项</Text>
          <Text style={styles.infoText}>• 高优先级任务会用红色标识</Text>
          <Text style={styles.infoText}>• 浮动按钮可快速添加新任务</Text>
        </View>
      </ScrollView>

      {/* 浮动添加按钮 */}
      <TouchableOpacity 
        style={styles.fab}
        onPress={() => setShowAddForm(!showAddForm)}
      >
        <Text style={styles.fabIcon}>{showAddForm ? '✕' : '+'}</Text>
      </TouchableOpacity>

      {/* 底部导航 */}
      <View style={styles.bottomNav}>
        <TouchableOpacity 
          style={[styles.navItem, styles.activeNavItem]} 
          onPress={() => Alert.alert('首页')}
        >
          <Text style={styles.navIcon}>🏠</Text>
          <Text style={styles.navText}>首页</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => Alert.alert('待办')}
        >
          <Text style={styles.navIcon}>📋</Text>
          <Text style={styles.navText}>待办</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => Alert.alert('完成')}
        >
          <Text style={styles.navIcon}></Text>
          <Text style={styles.navText}>完成</Text>
        </TouchableOpacity>
        
        <TouchableOpacity 
          style={styles.navItem} 
          onPress={() => Alert.alert('设置')}
        >
          <Text style={styles.navIcon}>⚙️</Text>
          <Text style={styles.navText}>设置</Text>
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f8fafc',
  },
  header: {
    padding: 20,
    backgroundColor: '#ffffff',
    borderBottomWidth: 1,
    borderBottomColor: '#e2e8f0',
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 4,
  },
  subtitle: {
    fontSize: 14,
    color: '#64748b',
  },
  content: {
    flex: 1,
    padding: 16,
  },
  statsCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    flexDirection: 'row',
    justifyContent: 'space-around',
    marginBottom: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  statItem: {
    alignItems: 'center',
  },
  statNumber: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#3b82f6',
  },
  statLabel: {
    fontSize: 12,
    color: '#64748b',
    marginTop: 4,
  },
  todosContainer: {
    marginBottom: 16,
  },
  todoItem: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 16,
    borderRadius: 8,
    marginBottom: 12,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  todoCheckbox: {
    marginRight: 12,
  },
  checkboxIcon: {
    fontSize: 20,
    fontWeight: 'bold',
  },
  todoContent: {
    flex: 1,
  },
  todoHeader: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    marginBottom: 8,
  },
  todoTitle: {
    fontSize: 16,
    fontWeight: '500',
    color: '#1e293b',
    flex: 1,
  },
  priorityBadge: {
    paddingHorizontal: 6,
    paddingVertical: 2,
    borderRadius: 4,
    borderWidth: 1,
    marginLeft: 8,
  },
  priorityText: {
    fontSize: 10,
    fontWeight: '500',
  },
  todoDescription: {
    fontSize: 14,
    color: '#64748b',
    marginBottom: 8,
  },
  todoMeta: {
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  dueDate: {
    fontSize: 12,
    color: '#ef4444',
    fontWeight: '500',
  },
  createdAt: {
    fontSize: 12,
    color: '#94a3b8',
  },
  deleteButton: {
    padding: 4,
  },
  deleteIcon: {
    fontSize: 18,
    color: '#94a3b8',
  },
  addForm: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  addFormTitle: {
    fontSize: 18,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 16,
    textAlign: 'center',
  },
  input: {
    borderWidth: 1,
    borderColor: '#cbd5e1',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
    color: '#1e293b',
    marginBottom: 12,
  },
  textArea: {
    height: 80,
    textAlignVertical: 'top',
  },
  formRow: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    marginBottom: 12,
  },
  formGroup: {
    flex: 1,
    marginRight: 8,
  },
  formGroup:lastChild: {
    marginRight: 0,
  },
  label: {
    fontSize: 14,
    fontWeight: '500',
    color: '#1e293b',
    marginBottom: 6,
  },
  priorityOptions: {
    flexDirection: 'row',
  },
  priorityOption: {
    flex: 1,
    padding: 8,
    borderWidth: 1,
    borderColor: '#cbd5e1',
    alignItems: 'center',
    backgroundColor: '#f8fafc',
  },
  priorityOption:firstChild: {
    borderTopLeftRadius: 8,
    borderBottomLeftRadius: 8,
  },
  priorityOption:lastChild: {
    borderTopRightRadius: 8,
    borderBottomRightRadius: 8,
  },
  priorityOptionSelected: {
    backgroundColor: '#dbeafe',
    borderColor: '#3b82f6',
  },
  priorityOptionText: {
    fontSize: 12,
    color: '#64748b',
    textAlign: 'center',
  },
  priorityOptionTextSelected: {
    color: '#1e40af',
    fontWeight: '500',
  },
  dateInput: {
    borderWidth: 1,
    borderColor: '#cbd5e1',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
    color: '#1e293b',
  },
  formButtons: {
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  cancelButton: {
    flex: 1,
    padding: 12,
    borderRadius: 8,
    borderWidth: 1,
    borderColor: '#cbd5e1',
    alignItems: 'center',
    marginRight: 8,
  },
  cancelButtonText: {
    color: '#64748b',
    fontWeight: '500',
  },
  saveButton: {
    flex: 1,
    padding: 12,
    borderRadius: 8,
    backgroundColor: '#3b82f6',
    alignItems: 'center',
    marginLeft: 8,
  },
  saveButtonText: {
    color: '#ffffff',
    fontWeight: '500',
  },
  infoCard: {
    backgroundColor: '#ffffff',
    borderRadius: 12,
    padding: 16,
    elevation: 1,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.1,
    shadowRadius: 2,
  },
  infoTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    color: '#1e293b',
    marginBottom: 12,
  },
  infoText: {
    fontSize: 14,
    color: '#64748b',
    lineHeight: 22,
    marginBottom: 8,
  },
  fab: {
    position: 'absolute',
    right: 20,
    bottom: 80,
    width: 56,
    height: 56,
    borderRadius: 28,
    backgroundColor: '#3b82f6',
    alignItems: 'center',
    justifyContent: 'center',
    elevation: 8,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 4 },
    shadowOpacity: 0.3,
    shadowRadius: 6,
  },
  fabIcon: {
    fontSize: 24,
    color: '#ffffff',
    fontWeight: 'bold',
  },
  bottomNav: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#ffffff',
    borderTopWidth: 1,
    borderTopColor: '#e2e8f0',
    paddingVertical: 12,
  },
  navItem: {
    alignItems: 'center',
    flex: 1,
  },
  activeNavItem: {
    paddingTop: 4,
    borderTopWidth: 2,
    borderTopColor: '#3b82f6',
  },
  navIcon: {
    fontSize: 20,
    color: '#94a3b8',
    marginBottom: 4,
  },
  activeNavIcon: {
    color: '#3b82f6',
  },
  navText: {
    fontSize: 12,
    color: '#94a3b8',
  },
  activeNavText: {
    color: '#3b82f6',
  },
});

export default TodoListWithFAB;

请添加图片描述


打包

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

在这里插入图片描述

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

在这里插入图片描述

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

请添加图片描述
本文介绍了一个跨平台待办事项列表组件的设计与实现,重点分析了其在React Native和HarmonyOS上的技术适配方案。组件采用模块化设计,包含列表展示、表单交互和状态管理三大核心功能。通过TypeScript强类型定义确保数据一致性,使用useState实现响应式状态管理,并优化列表渲染性能。在UI层面,组件实现了优先级可视化、完成状态反馈等交互细节,采用浮动操作按钮提升移动端操作体验。文章还详细阐述了React Native组件向HarmonyOS ArkUI迁移的技术路径,包括状态管理映射、组件等效替换等关键问题,为跨端开发提供了实践参考。

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐