在这里插入图片描述

前言

删除任务是一个不可逆动作,但用户的点击可能只是误触。直接从数组移除虽然简单,却会让用户失去恢复机会;弹确认框又会打断连续处理。一个更平衡的方案是:删除后立即从列表移除,在页面上给出明确反馈,并保留最近一次删除记录供撤销。

这篇文章继续沿着当前 Taskflow 待办面板往前走。和只展示 API 名称的教程不同,文章会把用户动作、状态变化、布局结果和验证证据放在同一条链上。读者可以先看“当前边界”判断哪些内容能直接复制,再看“下一步设计”区分哪些能力还需要自己接入。这样做的目的不是降低目标,而是避免把一个尚未运行的方案误写成项目结论。

先把问题拆成输入、变化和结果

删除动作需要先保存被删除对象,再修改源数组;撤销动作需要检查记录是否存在,恢复后清空撤销槽位,避免重复点击把同一任务插入多次。撤销按钮的可见性必须是可观察状态,不能只依赖普通私有字段。

在实现之前,建议把问题写成三列。输入是用户能改变的值,例如筛选项、关键词、标题、复选框和按钮动作;变化是页面内部要更新的源数据或异步状态;结果是用户能看到、能继续操作、能在重启后复查的内容。只要这三列没有对齐,代码就会出现“事件触发了但列表没有变”“数据显示了但重启丢失”这类难以解释的现象。

对于当前工程,源数据仍然是 TodoItem[]。筛选、搜索和编辑输入属于页面状态,任务数组的新增、替换、删除和恢复属于写入动作,Preferences 只负责把任务事实序列化保存。这个边界贯穿本系列后续文章,也解释了为什么不能为了方便给每一个视图再复制一份数组。

当前项目边界

当前 App 已实现单步删除和单步撤销:卡片提供“删除”按钮,删除后显示“任务已删除,可点击撤销恢复”,并出现“撤销删除”按钮;撤销会把最近一项任务放回列表并持久化。当前没有做多步操作历史和二次确认弹窗。

文章里出现“已实现”时,指的是当前工程源码、构建产物或模拟器操作能够核对的内容;出现“设计”“后续”“可以”时,指的是还需要继续编码、构建和验证的方案。两种语气必须分开。尤其是文件选择器、签名发布、动态主题和自动化 UI 测试等能力,不能因为路线图中有对应编号,就默认它们已经存在。

实现或设计步骤

第一步:定义状态边界

先确认哪些状态可以由一个源数据推导。当前页面使用 filterTodos() 生成可见任务,countRemaining() 生成未完成数量;编辑和删除则按稳定 id 定位对象。若本篇能力属于下一阶段设计,也要先说明它将新增什么状态、哪些状态不应该保存,避免功能完成后再次出现重复数据。

第二步:确定动作入口

用户动作应有一个明确入口。输入框提交进入 submitTodo(),复选框进入 toggleTodo(),卡片编辑和删除进入对应方法,持久化统一由 persistTodos() 负责。复杂功能可以继续拆分,但不要让同一类动作从多个组件直接改数组。统一入口能让错误提示、持久化和测试都有稳定落点。

第三步:让 UI 结果从状态产生

ArkUI 页面不需要在每个点击回调里手工刷新列表。只要状态写入正确,build() 中的派生查询、计数、空状态和卡片样式会重新读取数据。对于弹窗、导入、迁移等尚未接入的能力,仍应沿用同一原则:临时草稿先独立存在,校验通过后一次性写入源数据。

第四步:处理异常与边界

正常路径只说明功能能走通,边界路径才说明它是否可靠。要测试空标题、无匹配结果、重复点击、删除后撤销、应用重启、损坏存储、窄窗口、长标题以及任务顺序变化。对于本文暂未实现的部分,应把边界写成验收条件,不能用一段示例代码替代实际结果。

第五步:留存可复核的证据

代码文件说明“怎么写”,构建日志说明“能否打包”,HAP 路径说明“产物在哪里”,设备操作和截图说明“是否真的启动并交互”。这四类证据互相补充但不能互相替代。任何一类没有完成,都应在文章里明确标注待验证。

关键代码或数据协议

private lastDeletedTodo: TodoItem | undefined;
@State private canUndoDelete: boolean = false;

private deleteTodo(item: TodoItem): void {
  this.lastDeletedTodo = item;
  this.canUndoDelete = true;
  this.todos = removeTodo(this.todos, item.id);
  this.persistTodos();
}

private undoDelete(): void {
  if (this.lastDeletedTodo === undefined) {
    return;
  }
  this.todos = [this.lastDeletedTodo, ...this.todos];
  this.lastDeletedTodo = undefined;
  this.canUndoDelete = false;
  this.persistTodos();
}

这段示例的价值不在于直接复制,而在于它把本篇的边界固定下来:数据从哪里来、动作在哪里进入、结果如何产生、失败时保留什么。真正接入工程时,还要根据当前 ArkTS 版本、页面上下文和设备能力重新构建,不能只看代码片段就断言已经运行。

验证路径

删除第一条进行中任务,确认计数减少;点击撤销,确认任务重新出现且 id 不变;连续删除两条,确认只有最近一条可撤销;重启应用,确认已经撤销的任务仍由本地数据恢复。

建议每次验证都记录四个字段:前置数据、用户动作、预期结果、实际证据。例如“有两条进行中任务—点击第一条删除—列表减少一条且出现撤销—布局树出现撤销按钮并截图”。这样的记录比“功能测试通过”更容易在文章、测试用例和发布前清单之间复用。

如果验证发生在模拟器上,还要写清设备和产物来源。当前工程曾通过 hdc 安装 unsigned HAP 并启动 com.huihui.harmony.pctodoEntryAbility;这能证明当前包在该模拟器环境中安装启动,但不能替代用户签名或公开发布。对于未执行的设备动作,继续保留待验证状态。

当前模拟器画面

在这里插入图片描述

这张图只对应本文标注的已验证场景;它不替代签名、发布链接或未实现功能的验证。

常见误区和取舍

只保存被删除任务的标题会导致撤销后优先级和完成状态丢失;不清空撤销槽位会允许重复恢复;把“删除成功”写成“已永久删除”会与撤销行为冲突;删除后没有调用持久化会在重启时恢复旧数据。

另一个常见误区是把“代码看起来合理”当作“项目已经完成”。ArkUI 组件参数、Preferences 上下文、窗口尺寸和设备签名都可能让理论代码在真实链路中出现差异。更稳妥的方式是先做小范围改动,运行静态构建,再在模拟器中按一条路径核验;一旦发现问题,记录首个明确错误,不要同时改动数据、布局和配置。

在性能、架构和功能之间也要做阶段性取舍。当前项目选择单页、纯函数和页面状态,是为了先把闭环跑通;当第二个页面、复杂数据源或多步历史真正出现时,再引入 Store、组件接口和版本迁移。架构不是越早越多越好,而是要在实际复杂度到来时提供清晰边界。

后续工作

如果产品需要连续撤销,应将单个 lastDeletedTodo 升级为带操作类型和时间的历史栈,并限制长度。历史栈属于新的状态模型,应该先补测试再接入页面。

无论下一步选择哪一项,都应先把它拆成最小可验证增量:先增加模型或工具函数,再接入页面,再执行构建,最后补设备操作和文章证据。这样后续文章才会自然形成开发记录,而不是提前写好的功能承诺。

小结

本篇围绕“鸿蒙ArkUI实战:设计删除任务的二次确认与撤销入口”讨论的不是一个孤立控件,而是一条从用户问题到工程证据的完整路径。当前已实现的部分可以直接在待办项目中复查;尚未实现的部分则给出了状态边界、代码入口和验收条件。保持这种区分,文章才真正有助于读者复现,也能让项目后续进度保持可信。

Logo

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

更多推荐