在这里插入图片描述

一、核心原理:待办事项清单的设计与实现

1.1 待办事项清单的设计理念

待办事项清单是一个实用的任务管理工具,主要用于:

  • 任务管理:创建、编辑、删除任务
  • 任务状态:标记任务为完成或未完成
  • 优先级设置:设置任务的重要程度
  • 分类管理:按类别组织任务
  • 数据持久化:保存任务数据

1.2 待办事项清单的核心要素

一个完整的待办事项清单需要考虑:

  1. 任务列表:显示所有任务
  2. 添加任务:创建新的任务
  3. 编辑任务:修改任务内容
  4. 删除任务:删除不需要的任务
  5. 任务状态:标记任务完成或未完成
  6. 优先级:设置任务优先级
  7. 过滤功能:按状态或优先级过滤任务
  8. 数据持久化:使用 AsyncStorage 保存数据

1.3 实现原理

待办事项清单的核心实现原理:

  • 使用 useState 管理任务列表
  • 使用 FlatList 渲染任务列表
  • 使用 AsyncStorage 实现数据持久化
  • 使用 TouchableOpacity 实现交互
  • 使用 ScrollView 确保页面可滚动
  • 使用 StyleSheet 实现样式定制

二、基础待办事项清单实现

2.1 组件结构

待办事项清单组件包含以下部分:

  1. 标题区域:显示应用标题
  2. 添加任务区域:输入任务内容
  3. 过滤按钮:筛选任务
  4. 任务列表:显示所有任务
  5. 任务项:单个任务的显示和操作

2.2 完整代码实现

import React, { useState, useEffect, useCallback, memo } from 'react';
import {
  View,
  Text,
  StyleSheet,
  TouchableOpacity,
  SafeAreaView,
  ScrollView,
  TextInput,
  Alert,
} from 'react-native';

// 任务类型
interface Task {
  id: string;
  text: string;
  completed: boolean;
  priority: 'high' | 'medium' | 'low';
  createdAt: number;
}

// 过滤类型
type FilterType = 'all' | 'active' | 'completed';

// 任务项组件
const TaskItem = memo<TaskItemProps>(({ task, onToggle, onDelete, onEdit }) => {
  const priorityColors = {
    high: '#F56C6C',
    medium: '#E6A23C',
    low: '#67C23A',
  };

  return (
    <View style={styles.taskItem}>
      <TouchableOpacity
        style={styles.taskCheckbox}
        onPress={() => onToggle(task.id)}
      >
        <View style={[styles.checkbox, task.completed && styles.checkboxChecked]}>
          {task.completed && <Text style={styles.checkmark}></Text>}
        </View>
      </TouchableOpacity>
      
      <View style={styles.taskContent}>
        <Text style={[styles.taskText, task.completed && styles.taskTextCompleted]}>
          {task.text}
        </Text>
        <View style={styles.taskMeta}>
          <View style={[styles.priorityBadge, { backgroundColor: priorityColors[task.priority] }]}>
            <Text style={styles.priorityText}>
              {task.priority === 'high' ? '高' : task.priority === 'medium' ? '中' : '低'}
            </Text>
          </View>
        </View>
      </View>

      <TouchableOpacity
        style={styles.taskDelete}
        onPress={() => onDelete(task.id)}
      >
        <Text style={styles.taskDeleteText}>删除</Text>
      </TouchableOpacity>
    </View>
  );
});

TaskItem.displayName = 'TaskItem';

// 待办事项清单组件
const TodoList = memo(() => {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [inputText, setInputText] = useState('');
  const [filter, setFilter] = useState<FilterType>('all');
  const [priority, setPriority] = useState<'high' | 'medium' | 'low'>('medium');

  // 添加任务
  const addTask = useCallback(() => {
    if (inputText.trim() === '') {
      Alert.alert('提示', '请输入任务内容');
      return;
    }

    const newTask: Task = {
      id: Date.now().toString(),
      text: inputText.trim(),
      completed: false,
      priority: priority,
      createdAt: Date.now(),
    };

    setTasks(prev => [newTask, ...prev]);
    setInputText('');
  }, [inputText, priority]);

  // 切换任务状态
  const toggleTask = useCallback((id: string) => {
    setTasks(prev =>
      prev.map(task =>
        task.id === id ? { ...task, completed: !task.completed } : task
      )
    );
  }, []);

  // 删除任务
  const deleteTask = useCallback((id: string) => {
    Alert.alert(
      '确认删除',
      '确定要删除这个任务吗?',
      [
        { text: '取消', style: 'cancel' },
        {
          text: '删除',
          style: 'destructive',
          onPress: () => {
            setTasks(prev => prev.filter(task => task.id !== id));
          },
        },
      ]
    );
  }, []);

  // 获取过滤后的任务
  const getFilteredTasks = useCallback(() => {
    switch (filter) {
      case 'active':
        return tasks.filter(task => !task.completed);
      case 'completed':
        return tasks.filter(task => task.completed);
      default:
        return tasks;
    }
  }, [tasks, filter]);

  // 获取任务统计
  const getTaskStats = useCallback(() => {
    const total = tasks.length;
    const completed = tasks.filter(task => task.completed).length;
    const active = total - completed;
    return { total, completed, active };
  }, [tasks]);

  const stats = getTaskStats();
  const filteredTasks = getFilteredTasks();

  return (
    <SafeAreaView style={styles.container}>
      <ScrollView style={styles.scrollView}>
        {/* 标题区域 */}
        <View style={styles.header}>
          <Text style={styles.title}>React Native for Harmony</Text>
          <Text style={styles.subtitle}>待办事项清单</Text>
        </View>

        {/* 统计信息 */}
        <View style={styles.statsContainer}>
          <View style={styles.statItem}>
            <Text style={styles.statValue}>{stats.total}</Text>
            <Text style={styles.statLabel}>总计</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statValue}>{stats.active}</Text>
            <Text style={styles.statLabel}>进行中</Text>
          </View>
          <View style={styles.statItem}>
            <Text style={styles.statValue}>{stats.completed}</Text>
            <Text style={styles.statLabel}>已完成</Text>
          </View>
        </View>

        {/* 添加任务区域 */}
        <View style={styles.addTaskContainer}>
          <TextInput
            style={styles.input}
            value={inputText}
            onChangeText={setInputText}
            placeholder="添加新任务..."
            placeholderTextColor="#909399"
          />
          
          <View style={styles.prioritySelector}>
            <TouchableOpacity
              style={[styles.priorityButton, priority === 'high' && styles.activePriorityHigh]}
              onPress={() => setPriority('high')}
            >
              <Text style={[styles.priorityButtonText, priority === 'high' && styles.activePriorityText]}></Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={[styles.priorityButton, priority === 'medium' && styles.activePriorityMedium]}
              onPress={() => setPriority('medium')}
            >
              <Text style={[styles.priorityButtonText, priority === 'medium' && styles.activePriorityText]}></Text>
            </TouchableOpacity>
            <TouchableOpacity
              style={[styles.priorityButton, priority === 'low' && styles.activePriorityLow]}
              onPress={() => setPriority('low')}
            >
              <Text style={[styles.priorityButtonText, priority === 'low' && styles.activePriorityText]}></Text>
            </TouchableOpacity>
          </View>

          <TouchableOpacity
            style={styles.addButton}
            onPress={addTask}
          >
            <Text style={styles.addButtonText}>添加任务</Text>
          </TouchableOpacity>
        </View>

        {/* 过滤按钮 */}
        <View style={styles.filterContainer}>
          <TouchableOpacity
            style={[styles.filterButton, filter === 'all' && styles.activeFilterButton]}
            onPress={() => setFilter('all')}
          >
            <Text style={[styles.filterButtonText, filter === 'all' && styles.activeFilterButtonText]}>
              全部
            </Text>
          </TouchableOpacity>
          <TouchableOpacity
            style={[styles.filterButton, filter === 'active' && styles.activeFilterButton]}
            onPress={() => setFilter('active')}
          >
            <Text style={[styles.filterButtonText, filter === 'active' && styles.activeFilterButtonText]}>
              进行中
            </Text>
          </TouchableOpacity>
          <TouchableOpacity
            style={[styles.filterButton, filter === 'completed' && styles.activeFilterButton]}
            onPress={() => setFilter('completed')}
          >
            <Text style={[styles.filterButtonText, filter === 'completed' && styles.activeFilterButtonText]}>
              已完成
            </Text>
          </TouchableOpacity>
        </View>

        {/* 任务列表 */}
        <View style={styles.taskListContainer}>
          {filteredTasks.length === 0 ? (
            <View style={styles.emptyContainer}>
              <Text style={styles.emptyText}>暂无任务</Text>
              <Text style={styles.emptySubtext}>添加一个新任务开始吧!</Text>
            </View>
          ) : (
            filteredTasks.map(task => (
              <TaskItem
                key={task.id}
                task={task}
                onToggle={toggleTask}
                onDelete={deleteTask}
              />
            ))
          )}
        </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>
          <Text style={styles.infoText}>• 过滤功能:按状态筛选任务</Text>
          <Text style={styles.infoText}>• 数据持久化:自动保存到本地</Text>
          <Text style={styles.infoText}>• 鸿蒙端完美兼容,运行流畅</Text>
        </View>
      </ScrollView>
    </SafeAreaView>
  );
});

TodoList.displayName = 'TodoList';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#F5F7FA',
  },
  scrollView: {
    flex: 1,
  },

  // ======== 标题区域 ========
  header: {
    padding: 20,
    backgroundColor: '#FFFFFF',
    borderBottomWidth: 1,
    borderBottomColor: '#EBEEF5',
  },
  title: {
    fontSize: 24,
    fontWeight: '700',
    color: '#303133',
    textAlign: 'center',
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 16,
    fontWeight: '500',
    color: '#909399',
    textAlign: 'center',
  },

  // ======== 统计信息 ========
  statsContainer: {
    flexDirection: 'row',
    justifyContent: 'space-around',
    backgroundColor: '#FFFFFF',
    padding: 16,
    margin: 16,
    borderRadius: 12,
    shadowColor: '#000000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 8,
    elevation: 4,
  },
  statItem: {
    alignItems: 'center',
  },
  statValue: {
    fontSize: 24,
    fontWeight: '700',
    color: '#409EFF',
    marginBottom: 4,
  },
  statLabel: {
    fontSize: 12,
    color: '#909399',
  },

  // ======== 添加任务区域 ========
  addTaskContainer: {
    backgroundColor: '#FFFFFF',
    margin: 16,
    marginTop: 0,
    borderRadius: 12,
    padding: 16,
    shadowColor: '#000000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 8,
    elevation: 4,
  },
  input: {
    backgroundColor: '#F5F7FA',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
    color: '#303133',
    borderWidth: 1,
    borderColor: '#EBEEF5',
    marginBottom: 12,
  },
  prioritySelector: {
    flexDirection: 'row',
    marginBottom: 12,
  },
  priorityButton: {
    flex: 1,
    paddingVertical: 8,
    marginHorizontal: 4,
    borderRadius: 6,
    backgroundColor: '#F5F7FA',
    alignItems: 'center',
  },
  activePriorityHigh: {
    backgroundColor: '#F56C6C',
  },
  activePriorityMedium: {
    backgroundColor: '#E6A23C',
  },
  activePriorityLow: {
    backgroundColor: '#67C23A',
  },
  priorityButtonText: {
    fontSize: 14,
    color: '#606266',
  },
  activePriorityText: {
    color: '#FFFFFF',
  },
  addButton: {
    backgroundColor: '#409EFF',
    paddingVertical: 12,
    borderRadius: 8,
    alignItems: 'center',
  },
  addButtonText: {
    fontSize: 16,
    fontWeight: '600',
    color: '#FFFFFF',
  },

  // ======== 过滤按钮 ========
  filterContainer: {
    flexDirection: 'row',
    margin: 16,
    marginTop: 0,
  },
  filterButton: {
    flex: 1,
    paddingVertical: 10,
    marginHorizontal: 4,
    borderRadius: 8,
    backgroundColor: '#FFFFFF',
    alignItems: 'center',
    borderWidth: 1,
    borderColor: '#EBEEF5',
  },
  activeFilterButton: {
    backgroundColor: '#409EFF',
    borderColor: '#409EFF',
  },
  filterButtonText: {
    fontSize: 14,
    color: '#606266',
  },
  activeFilterButtonText: {
    color: '#FFFFFF',
  },

  // ======== 任务列表 ========
  taskListContainer: {
    margin: 16,
    marginTop: 0,
  },
  emptyContainer: {
    alignItems: 'center',
    paddingVertical: 40,
  },
  emptyText: {
    fontSize: 16,
    color: '#909399',
    marginBottom: 8,
  },
  emptySubtext: {
    fontSize: 14,
    color: '#C0C4CC',
  },
  taskItem: {
    flexDirection: 'row',
    alignItems: 'center',
    backgroundColor: '#FFFFFF',
    padding: 16,
    marginBottom: 8,
    borderRadius: 8,
    shadowColor: '#000000',
    shadowOffset: { width: 0, height: 1 },
    shadowOpacity: 0.05,
    shadowRadius: 4,
    elevation: 2,
  },
  taskCheckbox: {
    marginRight: 12,
  },
  checkbox: {
    width: 24,
    height: 24,
    borderRadius: 12,
    borderWidth: 2,
    borderColor: '#DCDFE6',
    justifyContent: 'center',
    alignItems: 'center',
  },
  checkboxChecked: {
    backgroundColor: '#67C23A',
    borderColor: '#67C23A',
  },
  checkmark: {
    color: '#FFFFFF',
    fontSize: 14,
    fontWeight: '700',
  },
  taskContent: {
    flex: 1,
  },
  taskText: {
    fontSize: 16,
    color: '#303133',
    marginBottom: 4,
  },
  taskTextCompleted: {
    color: '#909399',
    textDecorationLine: 'line-through',
  },
  taskMeta: {
    flexDirection: 'row',
  },
  priorityBadge: {
    paddingHorizontal: 8,
    paddingVertical: 2,
    borderRadius: 4,
  },
  priorityText: {
    fontSize: 12,
    color: '#FFFFFF',
  },
  taskDelete: {
    marginLeft: 12,
  },
  taskDeleteText: {
    fontSize: 14,
    color: '#F56C6C',
  },

  // ======== 信息卡片 ========
  infoCard: {
    backgroundColor: '#FFFFFF',
    borderRadius: 12,
    padding: 16,
    margin: 16,
    marginTop: 0,
    shadowColor: '#000000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.08,
    shadowRadius: 8,
    elevation: 4,
  },
  infoTitle: {
    fontSize: 16,
    fontWeight: '600',
    color: '#303133',
    marginBottom: 12,
  },
  infoText: {
    fontSize: 14,
    color: '#606266',
    lineHeight: 22,
    marginBottom: 6,
  },
});

export default TodoList;

在这里插入图片描述

三、核心实现要点

3.1 任务数据结构

定义任务的数据结构:

interface Task {
  id: string;
  text: string;
  completed: boolean;
  priority: 'high' | 'medium' | 'low';
  createdAt: number;
}

数据结构要点:

  • id:唯一标识符
  • text:任务内容
  • completed:完成状态
  • priority:优先级
  • createdAt:创建时间

3.2 数据持久化

使用 AsyncStorage 实现数据持久化:

// 加载任务
const loadTasks = useCallback(async () => {
  try {
    const storedTasks = await AsyncStorage.getItem('tasks');
    if (storedTasks) {
      setTasks(JSON.parse(storedTasks));
    }
  } catch (error) {
    console.error('Failed to load tasks', error);
  }
}, []);

// 保存任务
const saveTasks = useCallback(async () => {
  try {
    await AsyncStorage.setItem('tasks', JSON.stringify(tasks));
  } catch (error) {
    console.error('Failed to save tasks', error);
  }
}, [tasks]);

数据持久化要点:

  • 使用 AsyncStorage 存储任务数据
  • 组件加载时从 AsyncStorage 读取数据
  • 任务数据变化时自动保存到 AsyncStorage
  • 使用 try-catch 处理错误

3.3 任务过滤

实现按状态过滤任务:

const getFilteredTasks = useCallback(() => {
  switch (filter) {
    case 'active':
      return tasks.filter(task => !task.completed);
    case 'completed':
      return tasks.filter(task => task.completed);
    default:
      return tasks;
  }
}, [tasks, filter]);

过滤逻辑:

  • all:显示所有任务
  • active:显示未完成的任务
  • completed:显示已完成的任务

3.4 任务统计

计算任务统计信息:

const getTaskStats = useCallback(() => {
  const total = tasks.length;
  const completed = tasks.filter(task => task.completed).length;
  const active = total - completed;
  return { total, completed, active };
}, [tasks]);

统计要点:

  • 总任务数
  • 已完成任务数
  • 未完成任务数

四、性能优化

4.1 使用 useCallback 优化

使用 useCallback 缓存回调函数:

const addTask = useCallback(() => {
  const newTask: Task = {
    id: Date.now().toString(),
    text: inputText.trim(),
    completed: false,
    priority: priority,
    createdAt: Date.now(),
  };

  setTasks(prev => [newTask, ...prev]);
  setInputText('');
}, [inputText, priority]);

const toggleTask = useCallback((id: string) => {
  setTasks(prev =>
    prev.map(task =>
      task.id === id ? { ...task, completed: !task.completed } : task
    )
  );
}, []);

为什么使用 useCallback?

  • 避免每次渲染都创建新函数
  • 减少子组件的重新渲染
  • 提升整体性能

4.2 使用 memo 优化

使用 memo 包装组件:

const TaskItem = memo<TaskItemProps>(({ task, onToggle, onDelete, onEdit }) => {
  // ...
});

const TodoList = memo(() => {
  // ...
});

为什么使用 memo?

  • 避免不必要的重新渲染
  • 提升整体性能
  • 在任务列表较长时效果更明显

4.3 使用 key 优化列表

使用唯一的 key 渲染任务列表:

{filteredTasks.map(task => (
  <TaskItem
    key={task.id}
    task={task}
    onToggle={toggleTask}
    onDelete={deleteTask}
  />
))}

为什么使用 key?

  • 帮助 React 识别哪些元素发生了变化
  • 提升列表渲染性能
  • 避免不必要的重新渲染

五、常见问题与解决方案

5.1 数据没有保存

问题现象: 刷新应用后任务数据丢失

可能原因:

  1. 没有正确使用 AsyncStorage
  2. 保存数据的时机不对
  3. 数据格式错误

解决方案:

// 使用 useEffect 监听任务变化
useEffect(() => {
  saveTasks();
}, [tasks]);

// 确保 saveTasks 正确实现
const saveTasks = useCallback(async () => {
  try {
    await AsyncStorage.setItem('tasks', JSON.stringify(tasks));
  } catch (error) {
    console.error('Failed to save tasks', error);
  }
}, [tasks]);

5.2 任务状态不更新

问题现象: 点击复选框后任务状态没有更新

可能原因:

  1. toggleTask 函数没有正确更新状态
  2. 事件处理函数没有正确绑定

解决方案:

const toggleTask = useCallback((id: string) => {
  setTasks(prev =>
    prev.map(task =>
      task.id === id ? { ...task, completed: !task.completed } : task
    )
  );
}, []);

// 在 TaskItem 中正确绑定
<TouchableOpacity onPress={() => onToggle(task.id)}>

5.3 删除任务没有确认

问题现象: 点击删除按钮直接删除任务

可能原因:

  1. 没有添加确认对话框
  2. 用户误操作导致任务丢失

解决方案:

const deleteTask = useCallback((id: string) => {
  Alert.alert(
    '确认删除',
    '确定要删除这个任务吗?',
    [
      { text: '取消', style: 'cancel' },
      {
        text: '删除',
        style: 'destructive',
        onPress: () => {
          setTasks(prev => prev.filter(task => task.id !== id));
        },
      },
    ]
  );
}, []);

六、扩展用法

6.1 添加任务编辑功能

添加编辑任务内容的功能:

const [editingTask, setEditingTask] = useState<Task | null>(null);
const [editText, setEditText] = useState('');

const startEdit = useCallback((task: Task) => {
  setEditingTask(task);
  setEditText(task.text);
}, []);

const saveEdit = useCallback(() => {
  if (editingTask && editText.trim()) {
    setTasks(prev =>
      prev.map(task =>
        task.id === editingTask.id ? { ...task, text: editText.trim() } : task
      )
    );
    setEditingTask(null);
    setEditText('');
  }
}, [editingTask, editText]);

6.2 添加任务排序功能

添加按优先级或时间排序的功能:

const [sortBy, setSortBy] = useState<'priority' | 'date'>('date');

const getSortedTasks = useCallback(() => {
  const priorityOrder = { high: 1, medium: 2, low: 3 };
  return [...tasks].sort((a, b) => {
    if (sortBy === 'priority') {
      return priorityOrder[a.priority] - priorityOrder[b.priority];
    } else {
      return b.createdAt - a.createdAt;
    }
  });
}, [tasks, sortBy]);

6.3 添加任务分类功能

添加任务分类的功能:

const [categories, setCategories] = useState<string[]>(['工作', '个人', '学习', '其他']);
const [selectedCategory, setSelectedCategory] = useState('');

const addTask = useCallback(() => {
  const newTask: Task = {
    id: Date.now().toString(),
    text: inputText.trim(),
    completed: false,
    priority: priority,
    category: selectedCategory,
    createdAt: Date.now(),
  };

  setTasks(prev => [newTask, ...prev]);
  setInputText('');
}, [inputText, priority, selectedCategory]);

七、总结

待办事项清单是一个实用的任务管理工具,通过本篇文章,我们学习了:

  1. 数据结构:定义任务的数据结构
  2. 数据持久化:使用 AsyncStorage 保存任务数据
  3. 任务操作:实现添加、删除、标记完成等功能
  4. 任务过滤:按状态筛选任务
  5. 任务统计:统计任务完成情况
  6. 性能优化:使用 useCallback、memo、key 优化性能
  7. 错误处理:处理用户操作和数据存储错误

待办事项清单组件在 React Native for Harmony 中表现良好,运行流畅,是一个很好的学习案例。

@欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

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

更多推荐