RN for OpenHarmony 实战 TodoList 项目:按分类筛选
文章摘要 该案例实现了一个支持分类管理的TodoList应用,主要功能包括: 任务分类体系:预设工作、生活、学习和其他4个分类,通过数组存储便于扩展 分类筛选功能:支持按分类快速筛选任务,与其他筛选条件(状态、优先级等)组合使用 分类选择器:添加任务时可选择分类,UI组件复用分类数组保证一致性 分类可视化:任务卡片显示分类标签,使用主题色保持界面统一 分类统计:计算各分类任务数量用于数据展示 技术
案例开源地址:https://gitcode.com/lqjmac/rn_openharmony_todolist
分类的价值
任务可以按很多维度来组织:时间、重要程度、所属领域。分类就是按领域来组织任务。
工作的事情和生活的事情混在一起,会让人感觉混乱。把它们分开,工作时只看工作任务,下班后只看生活任务,心理上会轻松很多。
在我们的 TodoList 应用中,任务有四个分类:工作、生活、学习、其他。分类筛选让用户能快速切换到某个领域的任务视图。
分类列表的定义
首先定义有哪些分类:
const categories = ['工作', '生活', '学习', '其他'];
为什么用数组
用数组而不是硬编码,是因为分类可能会变。如果以后要加一个"健康"分类,只需要在数组里加一项,筛选按钮和添加任务的分类选择器都会自动更新。
分类的选择
四个分类覆盖了大多数人的日常任务:
- 工作:职场相关的任务
- 生活:日常琐事、家务、购物等
- 学习:读书、课程、技能提升
- 其他:不好归类的任务
"其他"的必要性
“其他"是一个兜底选项。有些任务确实不好归类,强行归类反而增加用户的认知负担。有了"其他”,用户可以快速添加任务而不用纠结分类。
筛选状态的管理
const [categoryFilter, setCategoryFilter] = useState('all');
类型推断
这里没有显式定义类型,TypeScript 会推断 categoryFilter 是 string 类型。因为分类是中文字符串,不像优先级那样是固定的英文值,用 string 类型更灵活。
默认值
默认是 'all',显示所有分类的任务。和其他筛选一样,用户打开应用时应该能看到全部任务。
筛选按钮组的实现
分类筛选按钮的实现稍有不同:
<View style={styles.filterContainer}>
<View style={styles.filterGroup}>
<TouchableOpacity style={[styles.filterBtn, categoryFilter === 'all' && {backgroundColor: theme.accent}]} onPress={() => setCategoryFilter('all')}>
<Text style={[styles.filterBtnText, {color: categoryFilter === 'all' ? '#fff' : theme.subText}]}>全部</Text>
</TouchableOpacity>
{categories.map(c => (
<TouchableOpacity key={c} style={[styles.filterBtn, categoryFilter === c && {backgroundColor: theme.accent}]} onPress={() => setCategoryFilter(c)}>
<Text style={[styles.filterBtnText, {color: categoryFilter === c ? '#fff' : theme.subText}]}>{c}</Text>
</TouchableOpacity>
))}
</View>
</View>
"全部"按钮单独处理
"全部"按钮是单独写的,不在 categories 数组里。这是因为 'all' 是一个特殊值,不是真正的分类。把它和分类混在一起会让数据结构变得不清晰。
分类按钮用 map 生成
四个分类按钮用 map 遍历 categories 数组生成。如果以后分类变了,只需要改数组,按钮会自动更新。
key 的选择
key={c} 用分类名称作为 key。因为分类名称是唯一的,可以作为稳定的标识符。
按钮样式的处理
分类筛选按钮的样式和其他筛选按钮一样:
style={[styles.filterBtn, categoryFilter === c && {backgroundColor: theme.accent}]}
统一的视觉风格
所有筛选按钮都用相同的基础样式 styles.filterBtn,选中时都用主题强调色 theme.accent。这种统一让界面看起来协调。
没有特殊颜色
和优先级筛选不同,分类筛选没有特殊的颜色。工作、生活、学习、其他,这些分类没有天然的颜色对应关系。强行赋予颜色反而会让用户困惑。
筛选逻辑的实现
分类筛选的逻辑很简单:
const filteredTasks = tasks.filter(task => {
const matchesSearch = task.title.toLowerCase().includes(searchText.toLowerCase());
const matchesFilter = filter === 'all' || (filter === 'active' && !task.completed) || (filter === 'completed' && task.completed);
const matchesPriority = priorityFilter === 'all' || task.priority === priorityFilter;
const matchesCategory = categoryFilter === 'all' || task.category === categoryFilter;
return matchesSearch && matchesFilter && matchesPriority && matchesCategory;
});
matchesCategory 的逻辑
const matchesCategory = categoryFilter === 'all' || task.category === categoryFilter;
和优先级筛选一样简单:
- 如果
categoryFilter === 'all',所有任务都匹配 - 否则,只有分类等于筛选值的任务才匹配
字符串比较
分类是中文字符串,直接用 === 比较。'工作' === '工作' 返回 true,'工作' === '生活' 返回 false。
四重筛选的组合
分类筛选是第四个筛选条件,和前三个一起工作:
return matchesSearch && matchesFilter && matchesPriority && matchesCategory;
筛选的叠加
四个条件用 && 连接,只有全部满足的任务才会显示。用户可以组合使用:
- 搜索"报告" + 筛选"工作" = 找到工作分类中包含"报告"的任务
- 筛选"待办" + 筛选"高优先级" + 筛选"工作" = 找到工作分类中未完成的高优先级任务
筛选的顺序
四个筛选条件的计算顺序不影响结果,因为 && 是可交换的。但从性能角度,把最可能为 false 的条件放前面可以提前短路。不过对于我们的数据量,这种优化没有意义。
分类在任务卡片中的显示
任务卡片上显示分类标签:
<View style={[styles.categoryTag, {backgroundColor: theme.accent + '30'}]}>
<Text style={[styles.categoryText, {color: theme.accent}]}>{item.category}</Text>
</View>
标签样式
categoryTag: {paddingHorizontal: 8, paddingVertical: 2, borderRadius: 8},
categoryText: {fontSize: 10, fontWeight: '600'},
分类标签是一个小药丸形状,背景是主题强调色的 30% 透明度,文字是主题强调色。
筛选与显示的关联
当用户筛选"工作"分类时,列表里所有任务的分类标签都显示"工作"。这种一致性让用户确认筛选生效了。
添加任务时的分类选择
添加任务弹窗中有分类选择器:
<Text style={[styles.modalLabel, {color: theme.subText}]}>分类</Text>
<View style={styles.categorySelector}>
{categories.map(c => (
<TouchableOpacity key={c} style={[styles.categoryOption, {borderColor: theme.accent}, newTaskCategory === c && {backgroundColor: theme.accent}]} onPress={() => setNewTaskCategory(c)}>
<Text style={{color: newTaskCategory === c ? '#fff' : theme.accent}}>{c}</Text>
</TouchableOpacity>
))}
</View>
复用分类数组
筛选按钮和添加选择器都用同一个 categories 数组。这保证了分类的一致性,不会出现筛选有"工作"但添加没有的情况。
选择器样式
categorySelector: {flexDirection: 'row', flexWrap: 'wrap', gap: 8, marginBottom: 24},
categoryOption: {paddingHorizontal: 16, paddingVertical: 8, borderRadius: 8, borderWidth: 1},
分类选择器的按钮比筛选按钮大一些,因为弹窗里空间更充裕,大按钮更容易点击。
分类统计
统计页面显示各分类的任务数量:
const categoryStats = categories.map(c => ({name: c, count: tasks.filter(t => t.category === c).length}));
统计数据的计算
遍历 categories 数组,对每个分类计算任务数量。结果是一个对象数组,每个对象包含分类名称和任务数量。
统计的展示
{categoryStats.map((cat, idx) => (
<View key={idx} style={styles.categoryStatItem}>
<Text style={[styles.categoryStatName, {color: theme.text}]}>{cat.name}</Text>
<View style={[styles.categoryStatBar, {backgroundColor: theme.border}]}>
<View style={[styles.categoryStatFill, {width: `${totalTasks > 0 ? (cat.count / totalTasks) * 100 : 0}%`, backgroundColor: theme.accent}]} />
</View>
<Text style={[styles.categoryStatCount, {color: theme.subText}]}>{cat.count}</Text>
</View>
))}
用进度条的形式展示各分类的任务占比,直观地显示任务在各分类的分布情况。
分类筛选的使用场景
什么时候用户会用分类筛选?
场景切换
上班时筛选"工作",只看工作任务。下班后筛选"生活",处理生活琐事。这种场景切换能帮助用户保持专注。
批量处理
周末想集中学习,筛选"学习"分类,把学习相关的任务一起处理。
回顾分析
想看看最近工作任务是不是太多了,筛选"工作"看看有多少任务。如果太多,可能需要调整工作节奏。
分类的扩展性
如果要支持用户自定义分类怎么办?
数据结构的变化
categories 从常量变成状态:
const [categories, setCategories] = useState(['工作', '生活', '学习', '其他']);
添加分类的功能
提供一个入口让用户添加新分类:
const addCategory = (name: string) => {
if (!categories.includes(name)) {
setCategories([...categories, name]);
}
};
删除分类的考虑
删除分类比较复杂,需要处理已有任务的分类。可以把被删除分类的任务移到"其他",或者禁止删除有任务的分类。
筛选按钮的换行处理
如果分类很多,一行放不下怎么办?
filterGroup: {flexDirection: 'row', gap: 8},
当前的实现
当前的 filterGroup 没有设置 flexWrap,按钮会挤在一行。如果分类太多,按钮会变得很小或者超出屏幕。
支持换行
可以添加 flexWrap: 'wrap' 让按钮自动换行:
filterGroup: {flexDirection: 'row', flexWrap: 'wrap', gap: 8},
这样分类多的时候会自动换到下一行,不会挤在一起。
筛选状态的重置
有时候用户想清除所有筛选,回到初始状态。可以提供一个重置按钮:
const resetFilters = () => {
setSearchText('');
setFilter('all');
setPriorityFilter('all');
setCategoryFilter('all');
};
重置的时机
可以在筛选区域添加一个"重置"按钮,或者在空状态提示里提供重置链接。当用户筛选后没有结果时,提示"没有匹配的任务,点击重置筛选"会很有帮助。
筛选的持久化
如果想保存用户的筛选偏好:
useEffect(() => {
AsyncStorage.setItem('categoryFilter', categoryFilter);
}, [categoryFilter]);
useEffect(() => {
AsyncStorage.getItem('categoryFilter').then(value => {
if (value) setCategoryFilter(value);
});
}, []);
是否需要持久化
分类筛选的持久化比状态筛选更有意义。用户可能习惯每天早上先看工作任务,保存这个偏好能节省操作。
小结
分类筛选让用户能按领域组织和查看任务。工作、生活、学习、其他四个分类覆盖了大多数场景。
实现要点:
- 分类列表用数组定义,方便扩展
- "全部"按钮单独处理,不混入分类数组
- 筛选逻辑简单,直接比较字符串
- 分类在筛选按钮、任务卡片、添加选择器、统计页面保持一致
分类筛选是四个筛选条件中最"软"的一个。状态是客观的(完成或未完成),优先级是主观但有明确标准的(重要程度),而分类完全取决于用户的组织方式。好的分类筛选应该尊重用户的分类习惯,提供灵活的筛选能力。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)