第 27 课:任务页分页大小记忆与用户偏好
第 27 课:任务页分页大小记忆与用户偏好
这一课,我们继续沿着任务管理主线往下走,补上另一个非常真实的后台系统能力:
让用户自己决定“每页显示多少条数据”,并把这份偏好记住。
很多初学者会觉得:
- 分页不是已经做完了吗?
其实还没有。
因为真实项目里,分页通常不只包含:
- 当前第几页
还常常包含:
- 每页显示多少条
而且这两个状态虽然都和分页有关,但它们不一定应该用同一种存储策略。
这一课一句话在做什么
这一课我们完成了 5 件事:
- 给任务页分页栏加了“每页显示多少条”的切换能力
- 让任务页分页大小只能在一组合法选项中切换
- 把分页大小保存到
localStorage - 刷新后自动恢复这份分页偏好
- 补上单元测试、E2E、类型和文档
这节课最重要的设计问题
这一课最值得你认真想清楚的问题是:
页码和分页大小,到底该不该都写进 URL?
答案是:
页码更适合进 URL分页大小更适合做本地偏好
为什么?
页码为什么适合写进 URL
因为页码更像“当前场景状态”。
例如你现在正在看:
- 第 3 页
这往往是一个:
- 可以刷新恢复
- 可以分享给别人
- 可以前进后退回溯
的状态。
所以它很适合写进 URL。
分页大小为什么更适合写进本地存储
因为分页大小更像“个人使用习惯”。
例如你喜欢:
- 每页 2 条
- 每页 4 条
- 每页 6 条
这通常不是业务筛选条件,而是:
- 你自己的阅读偏好
所以它更适合:
- 记在本机
- 刷新保留
- 不一定需要分享给别人
这就是为什么这一课我们特意把分页大小做成:
localStorage持久化
而不是:
- URL query 同步
这节课新增了什么类型
文件:
src/types/task.ts
这一课新增了两个类型:
TaskPageSizeOptionTaskPageSizeOptionItem
这里的意义非常重要。
我们没有把分页大小当成一个“随便来个数字就行”的状态,而是明确限制为:
246
这样做的好处是:
- 页面状态更稳定
- 测试边界更清晰
- 本地存储恢复时更容易做合法性清理
这节课在useTasksPage里做了什么
文件:
src/composables/useTasksPage.ts
这一课最核心的改动仍然在任务页 composable 里。
因为“分页大小偏好”属于:
- 页面级状态
它不是:
- 单个分页组件自己的局部临时状态
新增了分页大小相关常量
例如:
DEFAULT_TASK_PAGE_SIZETASK_TABLE_PAGE_SIZE_STORAGE_KEYTASK_PAGE_SIZE_OPTIONS
这些常量的作用是:
- 统一默认值
- 统一本地存储 key
- 统一合法选项
这样后面:
- 组件层
- 页面层
- 测试层
都能复用同一份定义。
新增了本地存储读写函数
例如:
readPersistedTaskPageSize()persistTaskPageSize()
它们负责:
- 从本地存储里读取旧偏好
- 如果值非法就回退默认值
- 把最新合法分页大小写回本地存储
这和上一课的“列配置持久化”是同一个工程思路。
也就是说,你现在已经开始在项目里重复练同一种“用户偏好状态”的设计模式了。
新增了标准化函数
例如:
isTaskPageSizeOptionnormalizeTaskPageSize
这类函数的意义是:
不要相信本地存储永远是干净的。
本地存储里的值可能来自:
- 老版本数据
- 手动修改
- 异常写入
所以你每次读出来,都应该先做合法性清理。
这就是为什么我们这次依然坚持:
- 先读
- 再校验
- 再使用
这节课对分页逻辑带来了什么变化
原来任务页是:
- 固定每页 4 条
现在变成了:
pageSize是一个响应式ref
这会带来 3 个直接影响。
1. 总页数会跟着变化
例如 6 条任务时:
- 每页 4 条 => 共 2 页
- 每页 2 条 => 共 3 页
2. 当前页切片结果会跟着变化
也就是说:
paginatedTasks
不再只依赖:
currentPage
还依赖:
pageSize
3. 页码合法性也会受到影响
如果你原来在:
- 第 3 页
然后把分页大小改大,可能总页数会立刻变少。
所以这一课里我们也把:
filteredTasks监听
升级成了同时关注:
filteredTaskspageSize
这样一来,只要分页大小变化导致总页数不合法,页码就会自动钳制回合法范围。
为什么这节课切换分页大小后要回到第一页
这一课里我们特意在:
handlePageSizeChange
里做了一个策略:
- 切换分页大小后,主动回到第一页
为什么这样做?
因为分页大小变化后,旧页码的语义很容易变得不稳定。
例如你原来在:
- 第 2 页,每页 4 条
现在改成:
- 每页 2 条
这时“第 2 页”已经不再表示原来那一段数据了。
为了让体验更清楚、更稳定,我们直接回到第一页。
这是一个很常见、也很合理的后台系统设计。
TaskPaginationBar.vue这次发生了什么变化
文件:
src/components/tasks/TaskPaginationBar.vue
这一课里,分页栏不再只有:
- 分页摘要
- 页码切换器
现在还新增了:
- 分页大小下拉框
结构也更完整了:
- 左侧展示摘要
- 右侧展示分页大小控制
- 结果超过一页时再展示页码控件
新增了哪些 props
pageSizeOptions
这说明分页组件不自己定义“有哪些大小可选”,而是由页面层传入。
这样组件仍然保持:
- 展示层
而不是:
- 状态定义中心
新增了哪些事件
page-size-change
这表示分页栏只负责把用户意图抛出去,不自己决定如何持久化。
这仍然符合我们一直在练的分层原则:
- 组件负责 UI 与事件
- composable 负责状态与行为
为什么这节课还要补 E2E
文件:
e2e/app.spec.tse2e/pages/TasksPage.ts
因为“分页大小记忆”这种功能,光做单元测试还不够。
它还涉及:
- Element Plus 下拉框真实交互
- 分页器页码是否真的变化
- 刷新后是否真的恢复
这些都属于:
- 真实页面行为
所以很适合用 E2E 验证。
这次新增的任务页 E2E 大致验证了:
- 把分页大小切到
2 - 看摘要是否更新
- 看第
3页是否出现 - 刷新页面
- 再次确认摘要和第
3页仍然存在
为什么这节课也要补单元测试
文件:
src/composables/__tests__/useTasksPage.spec.ts
这次新增的单元测试主要覆盖了:
- 从
localStorage恢复分页大小 - 非法分页大小自动回退默认值
- 切换分页大小后重置页码
- 切换分页大小后重新计算分页结果
- 切换分页大小后写回本地存储
你会发现:
- 单元测试更适合验证逻辑边界
- E2E 更适合验证真实交互闭环
这两层各有价值,不是互相替代关系。
这一课最值得你真正学会什么
如果你只记住“多了一个每页显示条数下拉框”,那还不够。
你更应该学会下面这 6 点:
- 页码和分页大小虽然都属于分页,但不一定适合用同一种存储策略
- 业务场景状态更适合进 URL,个人使用偏好更适合进本地存储
- 本地持久化状态读取后一定要先做标准化
- 分页大小一旦变成响应式状态,就会影响总页数、切片结果和页码合法性
- 组件层负责交互入口,组合式函数负责偏好状态与持久化
- 这种用户偏好能力也应该同时补单元测试和 E2E
这节课改了哪些文件
src/types/task.tssrc/composables/useTasksPage.tssrc/composables/__tests__/useTasksPage.spec.tssrc/components/tasks/TaskPaginationBar.vuesrc/views/TasksView.vuee2e/pages/TasksPage.tse2e/app.spec.tsdocs/27-task-page-size-persistence.mddocs/README.md
这一课的验证结果
这一课相关改动已经通过:
npm run test:unit -- --runnpm run test:e2e -- --project=chromiumnpm run type-checknpm run lint
下一步最适合继续什么
沿着“任务表格更像真实后台系统”的主线,后面最自然的方向有三个:
- 记住排序偏好
- 记住筛选偏好
- 做列顺序拖拽或列宽记忆
如果继续按当前节奏推进,我更推荐下一课做:
排序偏好与默认工作视图
因为它会让你进一步比较:
- 哪些状态适合进 URL
- 哪些状态适合做本地偏好
