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

张开发
2026/4/16 14:44:06 15 分钟阅读

分享文章

第 10 课:列表页的异步状态怎么设计
第 10 课列表页的异步状态怎么设计这一课非常实战。因为真实项目里的列表页几乎都不是一打开就“天然有数据”的。它们通常都要经历这样一个过程页面先挂载发起请求等待返回再决定显示表格、空状态还是错误提示所以这节课要解决的核心问题是当任务页开始从“同步假数据页面”走向“异步请求页面”时状态该怎么设计先讲结论你先记住一句最重要的话异步列表页不是只有“有数据”和“没数据”两种状态它至少要区分 loading、success、error更进一步地说一个成熟一点的列表页通常还要考虑首屏加载刷新中空数据失败重试旧数据是否保留也就是说真正难的不是“把数据拿回来”而是“请求过程中页面应该怎么表现”这次我们做了什么这一轮我们不只是写文档而是真的把任务页改成了“异步列表页”。新增了src/services/taskService.ts更新了src/composables/useTasksPage.tssrc/views/TasksView.vuesrc/components/tasks/TaskPageHeader.vuesrc/components/tasks/TaskFilterBar.vuesrc/components/tasks/TaskTable.vuesrc/composables/__tests__/useTasksPage.spec.ts这说明你现在看到的已经不是“理论上的异步状态”而是一个真实跑起来的 Vue 页面。为什么之前那种写法不够真实之前的任务页是这样工作的直接从src/mock/tasks.ts读同步数据页面一创建tasks里就已经有内容不存在等待请求的过程这对入门很友好但它和真实项目还有一个很大的差距没有请求过程就没有请求状态于是你也就看不到这些关键问题加载中时页面显示什么请求失败时页面显示什么刷新时要不要清空旧数据没有任务和筛选后为空是不是同一种空状态而这些恰恰是列表页里最常见的实战问题。为什么这次先引入 service 层这次我们新增了taskService.ts你可以把它先理解成一个很简单的“模拟后端接口层”。它做了两件事提供fetchTaskList()模拟异步获取任务列表提供createTaskRecord()把新任务写入内存数据库这样做的好处是页面不再直接依赖假数据文件而是开始依赖“取数动作”这一步非常重要。因为真实项目里页面通常不会自己知道数据从哪来页面只会说请帮我把任务列表拿来而 service 层负责请求 API返回数据以后替换成真实后端实现当前任务页的异步状态有哪些现在的 useTasksPage.ts 里多了几个很关键的状态loadStateloadErrorMessageisLoadinghasSourceTasksisInitialLoading你先不要急着背名字先理解它们各自解决什么问题。1.loadState它是这次最核心的状态idle|loading|success|error它的意义是idle还没开始请求loading请求中success请求成功error请求失败这比只写一个isLoading更完整。因为如果你只有constisLoadingref(false)你没法准确表达现在是还没请求还是请求失败了失败后页面应该渲染什么成功后又该切回什么状态所以这节课最值得你学的一点就是异步流程最好用“状态枚举”来描述而不是只靠一个布尔值硬撑2.loadErrorMessage这个状态专门保存错误提示文案。它的作用不是代替loadState而是配合loadState error时显示更具体的信息。也就是说loadState负责告诉页面“现在失败了”loadErrorMessage负责告诉页面“为什么失败”这是一种很常见的组合。3.hasSourceTasks这个状态非常容易被初学者忽略。它表示当前原始任务源里到底有没有数据为什么这个信息这么重要因为“没有数据”其实可能对应两种完全不同的场景场景 A真的没有任何任务数据。场景 B原始任务是有的只是你筛选完之后没有匹配结果。这两个场景页面文案不应该一样。所以我们用了filteredTaskshasSourceTasksemptyDescription一起判断当前到底该显示哪种空状态提示。4.isInitialLoading这个状态也很实战。它表示当前是不是首屏第一次加载而且还没有任何旧数据可展示为什么不能只看isLoading因为加载中也分两种首屏加载中页面还什么都没有这时更适合显示骨架屏。刷新加载中页面已经有旧数据了这时通常不应该把表格直接清空而是应该保留旧数据再加一个“刷新中”的提示。这就是为什么我们要把首屏加载刷新加载分开看。为什么TaskTable.vue现在更像真实项目现在的 TaskTable.vue 不再只是“渲染一个表格”。它开始承接列表区域最常见的 4 种显示状态1. 首屏加载显示el-skeleton2. 阻塞式错误如果请求失败而且手里没有任何旧数据显示el-result “重新加载”按钮。这是一种“整块区域都被错误态接管”的设计。3. 非阻塞式刷新如果页面已经有旧数据这时再次请求成功前显示“刷新中”提示表格继续保留旧数据这就是为什么我们没有在重新加载时先把tasks清空。因为一旦清空用户就会看到表格闪掉体验会变差。4. 非阻塞式错误如果刷新失败但旧数据还在页面会显示一个错误alert同时保留旧表格。这比“一失败就整页全空”更符合真实业务。这次非常值得你学的一点失败时不一定要清空旧数据很多新手一写异步列表页失败时会直接这样做tasks.value[]这并不总是对。如果是首屏第一次请求失败确实可能只能显示错误页。但如果是“已经有旧数据再次刷新失败”直接清空旧数据通常不是好体验。因为用户至少还能先看旧内容。所以这次我们的处理是成功时写入新任务列表失败时保留旧列表同时写入错误消息这是一个非常典型、也非常实用的列表页思路。为什么新增任务现在不会被“重新加载”冲掉这次 service 层除了fetchTaskList()还加了createTaskRecord()它的作用是把新增任务同步写入模拟服务的内存数据库。这样一来你在页面里新增一条任务本地列表立刻更新之后你再点“重新加载”服务层返回的数据里仍然包含这条新任务这一步很重要因为它帮你避开了一个常见坑本地新增成功了但一刷新又被原始假数据覆盖掉页面层和 composable 层这次是怎么分工的这次分工非常值得你学。useTasksPage.ts负责维护请求状态调用服务层保存任务列表计算空状态文案组织筛选、统计、新增逻辑关键词页面业务逻辑TasksView.vue负责在onMounted里触发首次加载响应“重新加载”按钮点击在页面层显示成功提示或等待提示关键词页面组装 页面反馈TaskTable.vue负责根据不同状态决定渲染什么界面区分骨架屏、错误态、表格、空状态在错误态里抛出重试事件关键词列表区域展示逻辑为什么这次要在页面里用onMounted现在 TasksView.vue 里有一段很关键的代码onMounted((){voidloadTasks()})它表达的是页面真正挂载到界面上之后再开始发起请求这也是很多真实页面最常见的启动方式。所以你现在要开始建立一个直觉setup里定义状态和函数onMounted里启动首次副作用这是 Vue 页面里非常高频的一组搭配。为什么这次还继续保留了 composable 测试现在的 useTasksPage.spec.ts 新增了异步状态相关测试。它测试了几件很关键的事情加载开始时是否进入loading加载成功后是否进入success刷新失败后是否保留旧数据创建任务后是否正确插入列表这很有教学价值因为你会越来越清楚复杂页面逻辑只要边界清楚就可以不依赖组件渲染直接测逻辑本身真实项目里最常见的异步列表页陷阱你现在就可以先记住下面这 6 个坑1. 只有isLoading没有完整状态机会导致你分不清还没请求请求中请求失败请求成功2. 加载失败后没有错误文案用户只会看到“出错了”但不知道下一步该做什么。3. 刷新时先清空旧数据会让页面频繁闪烁看起来像“抖一下再回来”。4. 把“真实空数据”和“筛选后为空”混成一种状态这会让文案不准确也会让用户迷惑。5. 请求失败后没有重试入口用户只能刷新整个页面交互很笨重。6. 本地新增和重新加载互相覆盖这正是我们这次专门通过 service 层内存数据库避开的坑。你现在应该能回答的 10 个问题为什么异步列表页不能只用一个isLoading来描述全部状态loadState里的idle / loading / success / error各自表示什么为什么首屏加载和刷新加载要分开看为什么失败时不一定要把旧数据清空hasSourceTasks解决了什么判断问题为什么“没有任何任务”和“筛选后没有结果”不是同一种空状态为什么这次要引入taskService.ts为什么新增任务要同步写入模拟服务TasksView.vue和TaskTable.vue这次分别承担了什么职责为什么 composable 抽出来之后异步状态也更容易测试这节课的动手练习练习 1打开 useTasksPage.ts把里面和异步请求相关的代码只按这 3 类重新整理一遍请求状态请求动作请求结果衍生判断目的训练你从“页面逻辑角度”读 composable而不是只盯着模板看。练习 2打开 TaskTable.vue自己口述一遍什么时候显示骨架屏什么时候显示整块错误态什么时候显示错误提示但保留旧表格什么时候显示空状态目的训练你把“状态判断”翻译成“界面表现”。练习 3自己尝试再加一个小功能给任务页增加一个“只看高优先级任务”的快捷按钮。要求你先回答这个按钮状态适合放哪里它会影响哪个computed它属于组件逻辑、composable 逻辑还是 store 逻辑目的让你把“异步状态设计”和“页面业务状态设计”开始放在一起思考。这节课的复习结论把这一课压缩成 8 句话真实列表页一定会经历异步请求过程所以必须设计请求状态。loadState比单独一个isLoading更能完整表达页面当前所处阶段。首屏加载和刷新加载虽然都叫 loading但页面表现通常不一样。请求失败时不一定要清空旧数据很多时候保留旧数据体验更好。“任务真的为空”和“筛选结果为空”是两种不同的空状态。service 层的作用是把“取数据动作”和“页面本身”分开。composable 很适合承接异步列表页的请求状态和页面级业务逻辑。好的异步状态设计本质上是在回答“请求过程中每一刻页面该长什么样”。

更多文章