Taro 详情页请求状态机:失败不能永远像加载中
本文目录
本文基于 2026-10-15 获取的一份个人跨端消费应用默认分支源码做静态审阅,只分析一个详情页的数据加载状态。正文不保留产品名、仓库名、服务地址、接口路径、配置值或真实业务数据,也没有修改源项目。没有安装依赖、运行构建、启动小程序开发工具、调用线上接口或使用真机;结论限于代码明确呈现的状态转换,路由复用和请求竞态仍需目标平台验证。
加载态为什么退不出去
页面把 moto 初始值设为 null。副作用从路由读取 id:没有 id 就直接返回;有 id 时请求详情,成功后把响应数据写入 moto。异常分支只显示一条 toast,没有更新页面状态。渲染阶段只判断 moto 是否为空:为空就返回“加载中”,否则才显示详情。
这把“尚未完成”“没有这条记录”和“请求失败”压缩成了一个值。按当前实现可以静态确认三条路径:
| 输入或结果 | 当前路径 | 可见结果 |
|---|---|---|
| 路由没有 id | 请求前提前返回,moto 仍为 null | 一直是加载中 |
| 请求成功但数据为空 | 把空结果规范成 null | 一直是加载中 |
| Promise reject | 只弹出“加载失败”提示,没有更新 moto | toast 消失后仍是加载中 |
因此“加载失败”的提示并不等于页面进入错误状态;用户没有重试按钮,也不知道是无效链接、已删除记录还是暂时的网络问题。空白占位虽然避免了空对象访问,却把错误信息藏掉了。
把数据和请求状态分开
详情页至少需要四种互斥状态:加载中、已找到、未找到、请求失败。失败要有可重复的重试动作;未找到应提供返回目录的出口。不要用 item === null 同时推断这些状态,也不要把 toast 当作页面状态的唯一反馈。
下面是一个状态建模示意。实际项目应由 API adapter 按接口契约把响应规范化为实体或空结果,并按 Taro 页面组件支持的事件方式连接重试与返回操作:
type DetailState<T> =
| { status: 'loading' }
| { status: 'ready'; data: T }
| { status: 'not-found' }
| { status: 'error' }
const id = router.params.id
const [attempt, setAttempt] = useState(0)
const [state, setState] = useState<DetailState<Item>>({ status: 'loading' })
useEffect(() => {
let current = true
if (!id) {
setState({ status: 'not-found' })
return () => { current = false }
}
setState({ status: 'loading' })
getDetail(id)
.then((item) => {
if (current) setState(item ? { status: 'ready', data: item } : { status: 'not-found' })
})
.catch(() => {
if (current) setState({ status: 'error' })
})
return () => { current = false }
}, [id, attempt])
渲染时用穷尽分支分别显示进度、详情、未找到和失败视图;失败视图中的“重试”只需递增 attempt,由同一个 Effect 重新请求。网络请求返回的错误细节不要直接显示给访客;可根据明确的错误码区分需要重新登录、无权限和可重试的服务异常,同时把诊断信息留在受控日志里。
路由切换和旧响应
源码中的 Effect 使用空依赖数组。如果页面实例的路由参数可能在实例存活期间变化,id 改变不会触发这段 Effect;如果 Taro 在目标页面栈中总是创建新实例,这一风险则未必出现,必须用目标端导航流程确认,不能仅凭 H5 结论推断小程序行为。把规范化后的 id 放入依赖,并在清理函数中阻止旧请求更新状态;若请求层支持取消,还可以同时中止已无用的网络任务。
只忽略卸载后的结果还不够:连续从记录 A 切到记录 B 时,A 的慢响应不能覆盖 B。可以由 Effect 清理标记隔离每次请求,也可以使用递增 requestId / query key;更稳妥的是让状态携带对应的 id,只有当前 id 匹配的成功结果才允许渲染。Taro 的网络请求能力与各端支持情况应以项目实际 Taro 版本和目标平台为准,不能假定浏览器里的 AbortController 可以原样移植到每个小程序端。
详情数据与店铺配置是两条独立请求。配置请求失败不应把详情页误判成失败,详情请求失败也不应阻止可独立展示的页面骨架;每个请求只更新自己拥有的状态。导航栏标题只应在当前 id 对应的详情成功后更新,切换记录时避免继续显示上一条记录的标题。
回归测试矩阵
- 不传 id:不调用详情 API,显示“链接无效/记录不可用”状态和返回入口,不出现无限加载。
- API 返回空实体:显示未找到,不把空结果当成网络错误,也不保留 spinner。
- API reject:显示操作级错误和可访问的重试按钮;点击后只发起一轮新请求,失败不会清空当前输入或停留在加载态。
- A、B 两个 id 的请求按相反顺序完成:只展示 B;离开页面后,迟到响应不再更新已卸载页面。
- 配置请求独立失败:详情仍按自身结果显示;详情请求独立失败:配置成功也不能伪装成详情加载完成。
- 分别在 H5 和实际目标小程序中验证页面栈返回、重复进入、参数缺失、弱网重试、接口 404/401/5xx 及快速切换;覆盖加载、空数据、错误、成功四态的文案和触控区域。
审阅边界
本次确认的是客户端源码里的控制流:空 id 提前结束、空响应归一为 null、catch 只 toast、渲染只按 moto 判加载。没有审阅服务端的详情接口,因此不推断记录为何为空;没有运行态复现,也没有把“同一实例参数变化”或乱序覆盖写成已发生的线上故障。修复需要同步补页面状态测试和目标平台验收,而不只是替换一段加载文案。
Live Notes
留言提交后会先进入审核,审核通过后对其他读者公开。