news 2026/4/16 14:44:05

第 10 课:列表页的异步状态怎么设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第 10 课:列表页的异步状态怎么设计

第 10 课:列表页的异步状态怎么设计

这一课非常实战。

因为真实项目里的列表页,几乎都不是一打开就“天然有数据”的。

它们通常都要经历这样一个过程:

  1. 页面先挂载
  2. 发起请求
  3. 等待返回
  4. 再决定显示表格、空状态还是错误提示

所以这节课要解决的核心问题是:

当任务页开始从“同步假数据页面”走向“异步请求页面”时,状态该怎么设计?


先讲结论

你先记住一句最重要的话:

异步列表页不是只有“有数据”和“没数据”两种状态,它至少要区分 loading、success、error

更进一步地说,一个成熟一点的列表页,通常还要考虑:

  • 首屏加载
  • 刷新中
  • 空数据
  • 失败重试
  • 旧数据是否保留

也就是说:

真正难的不是“把数据拿回来”,而是“请求过程中页面应该怎么表现”


这次我们做了什么

这一轮我们不只是写文档,而是真的把任务页改成了“异步列表页”。

新增了:

  • src/services/taskService.ts

更新了:

  • src/composables/useTasksPage.ts
  • src/views/TasksView.vue
  • src/components/tasks/TaskPageHeader.vue
  • src/components/tasks/TaskFilterBar.vue
  • src/components/tasks/TaskTable.vue
  • src/composables/__tests__/useTasksPage.spec.ts

这说明你现在看到的已经不是“理论上的异步状态”,
而是一个真实跑起来的 Vue 页面。


为什么之前那种写法不够真实

之前的任务页是这样工作的:

  • 直接从src/mock/tasks.ts读同步数据
  • 页面一创建,tasks里就已经有内容
  • 不存在等待请求的过程

这对入门很友好,但它和真实项目还有一个很大的差距:

没有请求过程,就没有请求状态

于是你也就看不到这些关键问题:

  • 加载中时页面显示什么
  • 请求失败时页面显示什么
  • 刷新时要不要清空旧数据
  • 没有任务和筛选后为空,是不是同一种空状态

而这些,恰恰是列表页里最常见的实战问题。


为什么这次先引入 service 层

这次我们新增了:

  • taskService.ts

你可以把它先理解成一个很简单的“模拟后端接口层”。

它做了两件事:

  1. 提供fetchTaskList(),模拟异步获取任务列表
  2. 提供createTaskRecord(),把新任务写入内存数据库

这样做的好处是:

页面不再直接依赖假数据文件,而是开始依赖“取数动作”

这一步非常重要。

因为真实项目里,页面通常不会自己知道数据从哪来,
页面只会说:

请帮我把任务列表拿来

而 service 层负责:

  • 请求 API
  • 返回数据
  • 以后替换成真实后端实现

当前任务页的异步状态有哪些

现在的 useTasksPage.ts 里多了几个很关键的状态:

  • loadState
  • loadErrorMessage
  • isLoading
  • hasSourceTasks
  • isInitialLoading

你先不要急着背名字,先理解它们各自解决什么问题。


1.loadState

它是这次最核心的状态:

'idle'|'loading'|'success'|'error'

它的意义是:

  • idle:还没开始请求
  • loading:请求中
  • success:请求成功
  • error:请求失败

这比只写一个isLoading更完整。

因为如果你只有:

constisLoading=ref(false)

你没法准确表达:

  • 现在是还没请求,还是请求失败了
  • 失败后页面应该渲染什么
  • 成功后又该切回什么状态

所以这节课最值得你学的一点就是:

异步流程最好用“状态枚举”来描述,而不是只靠一个布尔值硬撑


2.loadErrorMessage

这个状态专门保存错误提示文案。

它的作用不是代替loadState
而是配合loadState === 'error'时显示更具体的信息。

也就是说:

  • loadState负责告诉页面“现在失败了”
  • loadErrorMessage负责告诉页面“为什么失败”

这是一种很常见的组合。


3.hasSourceTasks

这个状态非常容易被初学者忽略。

它表示:

当前原始任务源里到底有没有数据

为什么这个信息这么重要?

因为“没有数据”其实可能对应两种完全不同的场景:

场景 A

真的没有任何任务数据。

场景 B

原始任务是有的,只是你筛选完之后没有匹配结果。

这两个场景页面文案不应该一样。

所以我们用了:

  • filteredTasks
  • hasSourceTasks
  • emptyDescription

一起判断当前到底该显示哪种空状态提示。


4.isInitialLoading

这个状态也很实战。

它表示:

当前是不是首屏第一次加载,而且还没有任何旧数据可展示

为什么不能只看isLoading

因为加载中也分两种:

首屏加载中

页面还什么都没有,这时更适合显示骨架屏。

刷新加载中

页面已经有旧数据了,这时通常不应该把表格直接清空,
而是应该保留旧数据,再加一个“刷新中”的提示。

这就是为什么我们要把:

  • 首屏加载
  • 刷新加载

分开看。


为什么TaskTable.vue现在更像真实项目

现在的 TaskTable.vue 不再只是“渲染一个表格”。

它开始承接列表区域最常见的 4 种显示状态:

1. 首屏加载

显示el-skeleton


2. 阻塞式错误

如果请求失败,而且手里没有任何旧数据,
显示el-result+ “重新加载”按钮。

这是一种“整块区域都被错误态接管”的设计。


3. 非阻塞式刷新

如果页面已经有旧数据,这时再次请求:

  • 成功前显示“刷新中”提示
  • 表格继续保留旧数据

这就是为什么我们没有在重新加载时先把tasks清空。

因为一旦清空,用户就会看到表格闪掉,体验会变差。


4. 非阻塞式错误

如果刷新失败,但旧数据还在,
页面会显示一个错误alert,同时保留旧表格。

这比“一失败就整页全空”更符合真实业务。


这次非常值得你学的一点:失败时不一定要清空旧数据

很多新手一写异步列表页,失败时会直接这样做:

tasks.value=[]

这并不总是对。

如果是首屏第一次请求失败,
确实可能只能显示错误页。

但如果是“已经有旧数据,再次刷新失败”,
直接清空旧数据通常不是好体验。

因为用户至少还能先看旧内容。

所以这次我们的处理是:

  • 成功时写入新任务列表
  • 失败时保留旧列表
  • 同时写入错误消息

这是一个非常典型、也非常实用的列表页思路。


为什么新增任务现在不会被“重新加载”冲掉

这次 service 层除了fetchTaskList(),还加了:

  • createTaskRecord()

它的作用是把新增任务同步写入模拟服务的内存数据库。

这样一来:

  1. 你在页面里新增一条任务
  2. 本地列表立刻更新
  3. 之后你再点“重新加载”
  4. 服务层返回的数据里仍然包含这条新任务

这一步很重要,因为它帮你避开了一个常见坑:

本地新增成功了,但一刷新又被原始假数据覆盖掉


页面层和 composable 层这次是怎么分工的

这次分工非常值得你学。

useTasksPage.ts负责

  • 维护请求状态
  • 调用服务层
  • 保存任务列表
  • 计算空状态文案
  • 组织筛选、统计、新增逻辑

关键词:

页面业务逻辑


TasksView.vue负责

  • onMounted里触发首次加载
  • 响应“重新加载”按钮点击
  • 在页面层显示成功提示或等待提示

关键词:

页面组装 + 页面反馈


TaskTable.vue负责

  • 根据不同状态决定渲染什么界面
  • 区分骨架屏、错误态、表格、空状态
  • 在错误态里抛出重试事件

关键词:

列表区域展示逻辑


为什么这次要在页面里用onMounted

现在 TasksView.vue 里有一段很关键的代码:

onMounted(()=>{voidloadTasks()})

它表达的是:

页面真正挂载到界面上之后,再开始发起请求

这也是很多真实页面最常见的启动方式。

所以你现在要开始建立一个直觉:

  • setup里定义状态和函数
  • onMounted里启动首次副作用

这是 Vue 页面里非常高频的一组搭配。


为什么这次还继续保留了 composable 测试

现在的 useTasksPage.spec.ts 新增了异步状态相关测试。

它测试了几件很关键的事情:

  1. 加载开始时是否进入loading
  2. 加载成功后是否进入success
  3. 刷新失败后是否保留旧数据
  4. 创建任务后是否正确插入列表

这很有教学价值,因为你会越来越清楚:

复杂页面逻辑只要边界清楚,就可以不依赖组件渲染,直接测逻辑本身


真实项目里最常见的异步列表页陷阱

你现在就可以先记住下面这 6 个坑:

1. 只有isLoading,没有完整状态机

会导致你分不清:

  • 还没请求
  • 请求中
  • 请求失败
  • 请求成功

2. 加载失败后没有错误文案

用户只会看到“出错了”,但不知道下一步该做什么。


3. 刷新时先清空旧数据

会让页面频繁闪烁,看起来像“抖一下再回来”。


4. 把“真实空数据”和“筛选后为空”混成一种状态

这会让文案不准确,也会让用户迷惑。


5. 请求失败后没有重试入口

用户只能刷新整个页面,交互很笨重。


6. 本地新增和重新加载互相覆盖

这正是我们这次专门通过 service 层内存数据库避开的坑。


你现在应该能回答的 10 个问题

  1. 为什么异步列表页不能只用一个isLoading来描述全部状态?
  2. loadState里的idle / loading / success / error各自表示什么?
  3. 为什么首屏加载和刷新加载要分开看?
  4. 为什么失败时不一定要把旧数据清空?
  5. hasSourceTasks解决了什么判断问题?
  6. 为什么“没有任何任务”和“筛选后没有结果”不是同一种空状态?
  7. 为什么这次要引入taskService.ts
  8. 为什么新增任务要同步写入模拟服务?
  9. TasksView.vueTaskTable.vue这次分别承担了什么职责?
  10. 为什么 composable 抽出来之后,异步状态也更容易测试?

这节课的动手练习

练习 1

打开 useTasksPage.ts,把里面和异步请求相关的代码只按这 3 类重新整理一遍:

  • 请求状态
  • 请求动作
  • 请求结果衍生判断

目的:

训练你从“页面逻辑角度”读 composable,而不是只盯着模板看。


练习 2

打开 TaskTable.vue,自己口述一遍:

  • 什么时候显示骨架屏
  • 什么时候显示整块错误态
  • 什么时候显示错误提示但保留旧表格
  • 什么时候显示空状态

目的:

训练你把“状态判断”翻译成“界面表现”。


练习 3

自己尝试再加一个小功能:

给任务页增加一个“只看高优先级任务”的快捷按钮。

要求你先回答:

  • 这个按钮状态适合放哪里
  • 它会影响哪个computed
  • 它属于组件逻辑、composable 逻辑还是 store 逻辑

目的:

让你把“异步状态设计”和“页面业务状态设计”开始放在一起思考。


这节课的复习结论

把这一课压缩成 8 句话:

  1. 真实列表页一定会经历异步请求过程,所以必须设计请求状态。
  2. loadState比单独一个isLoading更能完整表达页面当前所处阶段。
  3. 首屏加载和刷新加载虽然都叫 loading,但页面表现通常不一样。
  4. 请求失败时不一定要清空旧数据,很多时候保留旧数据体验更好。
  5. “任务真的为空”和“筛选结果为空”是两种不同的空状态。
  6. service 层的作用是把“取数据动作”和“页面本身”分开。
  7. composable 很适合承接异步列表页的请求状态和页面级业务逻辑。
  8. 好的异步状态设计,本质上是在回答“请求过程中每一刻页面该长什么样”。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 14:35:25

Android蓝牙状态监听实战:从广播接收器到Handler的完整实现

Android蓝牙状态监听实战:从广播接收器到Handler的完整实现 在移动应用开发中,蓝牙功能的状态管理一直是个既基础又关键的环节。想象一下这样的场景:用户打开健身APP准备连接智能手环,却发现界面始终显示"设备未连接"&a…

作者头像 李华
网站建设 2026/4/16 14:33:31

实验室信息化管理系统设计与实现(有完整资料)

资料查找方式:特纳斯电子(电子校园网):搜索下面编号即可编号:T0922309M设计简介:本设计是基于单片机的实验室信息化管理系统设计,主要实现以下功能:通过温湿度传感器检测温湿度 通过…

作者头像 李华
网站建设 2026/4/16 14:33:15

室内无人机也能稳如老狗:用Livox Mid360雷达+MTF-01光流,搞定PX4飞控的无GPS定位(附ROS源码解析)

室内无人机高精度定位实战:Livox Mid360雷达与光流融合的PX4飞控解决方案 在仓库巡检、隧道勘探或地下空间测绘等场景中,无人机常面临GPS信号缺失的挑战。传统光流方案在低纹理环境下容易失效,而纯激光雷达方案又存在计算资源消耗大的问题。…

作者头像 李华