上一篇说,接口失败时页面要退到一个还能用的状态。这一篇把退化的三个出口落成具体设计:空状态、错误提示、重试。三个出口各回答一个问题,答错了页面就在异常时把用户带偏。
空状态:先问这个空是怎么来的
空状态不是“没数据就显示 noData”这么简单,要先问这个空是接口成功返回的,还是接口失败留下的。
正常空是业务结果:接口返回零条,列表确实没有内容。这时的空状态可以顺带引导,比如“暂无数据,去创建一条”或“换个条件试试”。
异常空是错误结果:接口没成功,列表数组还停留在初始值。这时如果也渲染成“暂无数据”,用户会误判成业务上没有数据。
区分这两者的做法,是给列表加一个独立的状态位,而不是靠list.length猜:
const listState = ref('loading'); // loading | empty | error | ready const loadList = async (params) => { listState.value = 'loading'; try { const res = await listApi(params); list.value = res.data.records; listState.value = list.value.length ? 'ready' : 'empty'; } catch (e) { listState.value = 'error'; } };loading、empty、error三种状态各自渲染不同内容,才不会把“加载失败”和“没有数据”画成同一张脸。
错误提示:先问用户还能不能继续
提示不是“有错就弹”这么简单,要先分可恢复和不可恢复。
可恢复的错误,是重来一次可能成功的:网络抖动、超时、服务暂时不可用。这类错误除了提示,还要给重试入口,让用户知道下一步是再点一次。
不可恢复的错误,是重来也不会好的:权限不足、参数校验失败、登录失效。这类错误再给重试按钮就是误导,用户点了也白点。它要说清楚问题出在哪,用户该怎么解决。
mess这种瞬时提示适合操作结果反馈,不适合承载“下一步怎么做”。它一闪而过,用户还没看清就没了。真正需要用户做决定的错误,应该落到页面区域里常驻展示,而不是靠一条会消失的 toast。比如翻页失败后,在表格上方常驻一条“本页加载失败,点击重试”,重试动作就挂在这条提示上,用户不需要等提示消失后再回忆该点哪里,这比弹一条三秒消失的提示更实用。
重试:先问重试会不会更糟
重试本身有风险,不是所有失败都适合点重试。
读操作和写操作,重试的代价完全不同。
查询、翻页、详情这类读操作是幂等的,重发一次不会产生副作用,失败后可以放心重试。
提交、保存、删除这类写操作就不是了。用户看到“保存失败”,再点一次,可能上一份其实已经写进去了,只是响应丢了。重试这里就成了重复提交,反而更糟。
所以重试要分类处理。读操作给重试按钮;写操作的重试要依赖幂等键或明确的后端去重,不能无脑再发一份。这一点在防重复提交那两篇里已经展开过,这里不再重复,只强调:重试按钮不是通用配置,要跟着操作性质走。
重试本身还要防连点。用户连点重试,等于制造新的并发。重试按钮和普通提交按钮一样,需要节流,请求期间置灰。
三个出口的设计对照
| 出口 | 要回答的问题 | 关键区分 | 常见错写 |
|---|---|---|---|
| 空状态 | 空是哪来的 | 正常空 / 异常空 | 用 list.length 判断 |
| 错误提示 | 还能不能继续 | 可恢复 / 不可恢复 | 不可恢复也给重试 |
| 重试 | 重试会不会更糟 | 读操作 / 写操作 | 写操作无幂等重试 |
这张表的每一行,都对应一个给 Codex 的判断题。题目清楚了,实现才不容易走样。
验收要分静态和运行
静态能查的:列表有没有独立区分加载中、空、失败的状态;写操作的重试有没有带幂等;不可恢复的错误有没有配了无意义的重试按钮。
要跑页面才能确认的:断网时首屏显示的是“加载失败”还是“暂无数据”;翻页失败后上一页数据有没有保留;写操作失败再重试会不会产生第二条记录。最后一条依赖后端,没有条件就列为未验证。
交给 Codex 的任务模板
请为这张列表整理异常路径,不先改代码: 1. 列出列表所有可能失败的请求:首屏、翻页、查询、刷新、删除、保存。 2. 为每个失败点标注页面退到哪个状态:失败态、保留旧数据、停在原地。 3. 区分正常空和异常空,指出当前实现靠什么判断。 4. 区分可恢复和不可恢复错误,检查不可恢复错误是否误配了重试。 5. 检查每个重试按钮:读操作还是写操作,写操作有没有幂等,重试有没有防连点。 先输出异常路径与退化状态对照表,再给最小修改方案。不要用空 catch 吞掉失败,也不要把失败渲染成空数据。
这条任务的前半段是盘点失败点。异常路径写不好,往往是因为没先数清楚这张列表一共有几个请求会失败,每个失败该退到哪。
写在最后
空状态、错误提示、重试,是列表异常退化的三个出口。空状态分正常空和异常空,错误提示分可恢复和不可恢复,重试分读操作和写操作。
把这三组区分做清楚,页面在异常时才不会把用户领进错误的方向。
下一篇把这些出口整理成一份页面异常路径验证清单,作为列表页验收的固定步骤,让 Codex 交付前能自己过一遍。
本系列持续更新。异常路径验证收束后,会用web-skills约束一张完整列表页,做一次整体验收复盘。