第 25 课:给学习笔记页加上搜索、标签筛选和 URL 同步

第 25 课:给学习笔记页加上搜索、标签筛选和 URL 同步

这一课,我们不再只做"测试结构重构",而是回到一个非常真实的前端页面需求:

让笔记页从静态展示升级成可搜索、可按标签筛选、可同步到地址栏的页面。

这一步很重要。

因为真正的后台页面,几乎很少只是把数据"摆出来"。

它们更常见的形态是:

  • 有输入框
  • 有筛选条件
  • 有空状态
  • 有结果摘要
  • 有 URL query 同步

所以这一课,其实是在把笔记页推进到更接近真实业务页面的状态。


这一课一句话在做什么

这一课我们完成了 4 件事:

  1. 给笔记页加了关键字搜索
  2. 给笔记页加了标签筛选
  3. keywordtag 同步到了 URL
  4. 为这套逻辑补了单元测试和 E2E 测试

也就是说,这一课不是只改 UI,而是把:

  • 页面交互
  • 页面状态
  • 路由状态
  • 自动化验证

一起补齐了。


这一课新增了什么文件

1. 新增笔记页组合式函数

文件:

  • src/composables/useNotesPage.ts

这是这一课最核心的新增文件。

它把下面这几类逻辑从 NotesView.vue 里迁了出去:

  • 关键字状态
  • 标签状态
  • 筛选结果
  • 结果摘要
  • 空状态文案
  • 地址栏 query 同步
  • 清空筛选行为

这说明一个很重要的工程思路:

当页面开始出现"多状态 + 多派生结果 + 多个行为函数"时,就很适合抽成 composable。


2. 新增组合式函数单元测试

文件:

  • src/composables/__tests__/useNotesPage.spec.ts

这一课我们不只改功能,还专门给它补了单元测试。

测试覆盖了这些核心点:

  • 首次从 URL 回填筛选状态
  • 非法 tag 参数自动清理
  • 关键字同步走防抖
  • 标签筛选立即同步
  • 清空筛选恢复默认状态

这能让你第一次更完整地感受到:

  • 页面功能不是"写完就算"
  • 组合式函数也应该能被独立验证

3. 扩展笔记页 Page Object

文件:

  • e2e/pages/NotesPage.ts

这一课我们继续扩展了 NotesPageObject,新增了:

  • 输入关键字
  • 选择标签
  • 清空筛选
  • 断言筛选摘要
  • 断言 URL query
  • 断言空状态

这说明 Page Object 不是写完一次就不动了。

当页面能力变强时,页面对象也应该一起成长。


这节课对 NotesView.vue 做了什么

文件:

  • src/views/NotesView.vue

这次最大的变化是:

NotesView.vue 不再自己手写全部筛选逻辑,而是专注于"渲染 UI + 绑定事件"。

它现在主要做的是:

  1. 调用 useNotesPage()
  2. 取出页面需要的状态
  3. 绑定关键字输入框
  4. 绑定标签下拉框
  5. 渲染筛选结果摘要
  6. 在无结果时渲染空状态

这就是典型的:

  • 视图层负责展示
  • composable 负责页面逻辑

为什么这节课要把逻辑抽进 useNotesPage

你完全可以把这些逻辑直接写进 NotesView.vue

比如:

  • const keyword = ref('')
  • const activeTag = ref('全部')
  • const filteredNotes = computed(...)
  • watch(route.query, ...)

这些都能直接写在页面里。

但问题是,一旦都塞进页面,文件很快就会变成:

  • 状态一堆
  • computed 一堆
  • watch 一堆
  • handler 一堆

然后页面会越来越难读。

所以这节课我们把它抽出来,是为了让你体会:

页面复杂度上来以后,最先该拆出去的,通常不是模板,而是页面逻辑。


useNotesPage 里到底封装了哪些东西

文件:

  • src/composables/useNotesPage.ts

1. 页面状态

  • keyword
  • activeTag

这两个是用户直接操作出来的原始状态。


2. 派生状态

  • availableTags
  • noteCount
  • uniqueTagCount
  • filteredNotes
  • filteredNoteCount
  • hasActiveFilters
  • emptyDescription
  • filterSummary

这些都不是用户直接输入的,而是:

  • 根据原始状态推导出来的结果

这正是 computed 最适合做的事情。


3. 行为函数

  • handleKeywordChange
  • handleTagChange
  • clearFilters

这些函数负责:

  • 改状态
  • 触发 URL 同步

它们是页面逻辑层的"动作接口"。


4. 地址栏同步状态

  • isKeywordQuerySyncPending
  • keywordQuerySyncDelay

这两个值是为了让用户能看到:

  • 当前关键字已经变了
  • 但系统还在等待防抖结束后再写入地址栏

这是一种很典型的"同步中可视化"设计。


为什么关键字同步要防抖,标签同步却立即执行

这是这一课一个非常值得记住的设计点。

关键字为什么要防抖

因为关键字输入是高频动作。

用户可能会连续输入:

  • P
  • Pi
  • Pin
  • Pinia

如果每次都立刻改 URL,就会产生大量无意义的路由 replace。

所以关键字更适合:

  • 先更新页面状态
  • 再延迟一点同步到地址栏

标签为什么适合立刻同步

因为标签选择通常是低频动作。

用户不会像打字那样一秒改很多次。

所以标签变化直接:

  • 立刻更新状态
  • 立刻写回 URL

更自然,也更简单。

这说明一个很重要的前端设计思路:

不同交互频率的输入,不一定要使用同一种同步策略。


为什么 URL 同步这么重要

很多初学者会觉得:

  • 页面里筛选一下就好了,为什么还要同步地址栏?

真实项目里,URL 同步至少有 4 个实际价值。

1. 刷新页面后能恢复状态

如果你当前筛选的是:

  • 关键字:Pinia
  • 标签:store

刷新后还能恢复,就说明这个页面状态是可持久的。


2. 可以分享给别人

别人只要打开同一个链接,就能看到相同筛选结果。


3. 浏览器前进后退更自然

筛选变化也会成为浏览历史的一部分。


4. 更像真实后台系统

很多中后台页面都依赖 query 参数保存筛选状态。

所以你越早练这个模式,越接近真实项目开发。


这一课新增了哪些界面能力

文件:

  • src/views/NotesView.vue

1. 关键字输入框

它现在支持搜索:

  • 标题
  • 摘要
  • 标签

这比只搜标题更接近真实使用场景。


2. 标签下拉筛选

可选标签不是手写死的,而是根据当前笔记数据自动生成。

这说明页面没有把标签选项写死在模板里,而是通过数据推导出来。


3. 筛选结果摘要

例如:

  • 当前共 4 篇笔记,正在展示全部内容。
  • 当前共 4 篇笔记,已匹配 1 篇。

这个摘要非常重要。

因为筛选页面如果没有结果反馈,用户会不知道:

  • 到底有没有生效
  • 是 0 条结果,还是还没加载出来

4. 空状态提示

当你输入一个不存在的关键字,比如:

  • Nuxt

页面会明确告诉你:

  • 没有找到符合当前筛选条件的学习笔记。

这比空白一片强得多。


5. 清空筛选

当页面存在筛选条件时,会显示:

  • 清空筛选

这是非常常见且实用的交互细节。


这一课对自动化测试带来了什么变化

文件:

  • e2e/app.spec.ts

我们新增了 3 条笔记页相关用例:

  1. 关键字搜索并同步 query
  2. 标签筛选并清空筛选
  3. 无结果时显示空状态

这 3 条测试让笔记页不再只有"静态展示是否成功"的验证,而是开始覆盖:

  • 页面真实交互
  • 路由同步结果
  • 边界状态

NotesPageObject 这次为什么继续扩展

文件:

  • e2e/pages/NotesPage.ts

上一课 NotesPageObject 只覆盖了:

  • 页面标题
  • 统计卡片
  • 笔记卡片
  • 建议区块

这一课它继续长出了:

  • fillKeyword
  • selectTag
  • clearFilters
  • expectFilterSummary
  • expectQueryValue
  • expectQueryMissing
  • expectNoteHidden
  • expectEmptyState

这说明 Page Object 的成长方式应该是:

  • 跟着页面能力一起增长

而不是一开始就预设成一个巨大的对象。


这节课最值得你理解的工程边界

到这里,你要开始能区分 4 层东西:

1. mock 数据

文件:

  • src/mock/notes.ts

负责:

  • 提供笔记原始数据

2. composable

文件:

  • src/composables/useNotesPage.ts

负责:

  • 页面状态
  • 页面派生结果
  • 页面行为
  • 页面与 URL 的同步

3. view

文件:

  • src/views/NotesView.vue

负责:

  • 把状态渲染成界面
  • 把交互事件绑定回 composable

4. tests

文件:

  • src/composables/__tests__/useNotesPage.spec.ts
  • e2e/app.spec.ts

负责:

  • 单元测试验证逻辑边界
  • E2E 测试验证真实页面行为

这节课你应该真正学会什么

如果你只记住"给笔记页加了个搜索框",那就太浅了。

你真正应该学会的是下面这 6 点:

  1. 页面功能一复杂,优先考虑把页面逻辑抽成 composable
  2. computed 适合表达筛选结果、摘要、空状态这类派生数据
  3. watch(route.query) 适合处理地址栏回填页面状态
  4. 高频输入和低频筛选,不一定使用同一种 query 同步策略
  5. Page Object 要跟着页面能力一起演进
  6. 新功能上线时,单元测试和 E2E 测试都应该跟上

这节课改了哪些文件

  • src/composables/useNotesPage.ts
  • src/composables/__tests__/useNotesPage.spec.ts
  • src/views/NotesView.vue
  • e2e/pages/NotesPage.ts
  • e2e/app.spec.ts
  • docs/25-notes-search-filter-and-query-sync.md
  • docs/README.md

这一课的验证结果

这一课相关改动已经通过:

  • npm run test:unit -- --run
  • npm run test:e2e -- --project=chromium
  • npm run lint
  • npm run type-check

这很关键。

因为你现在做的已经不是"小玩具页面",而是:

  • 有页面逻辑
  • 有测试
  • 有路由同步
  • 有类型约束

的工程化前端页面。

相关推荐
2501_906467631 天前
html5网页中如何实现内网大文件的加密下载?
前端·html·html5·vue上传解决方案·vue断点续传·vue分片上传下载·vue分块上传下载
极客小俊1 天前
【从零到一】用HTML5+CSS+JavaScript实现一个属于自己的mp3免费音乐播放器 (4) JS交互功能(音乐进度条)
javascript·css·html5·前端开发·免费教程·代码案例·手搓音乐播放器
@小柯555m1 天前
Java八股刷题
java·开发语言·八股
182******20831 天前
2026新手必看:C语言学到什么程度可以出去找工作
c语言·开发语言
何何____1 天前
css变换语法介绍及案例展示
前端·css
南境十里·墨染春水1 天前
linux学习进展 mysql数据库
linux·数据库·学习
IT猿手1 天前
光伏模型参数估计:山羊优化算法(Goat Optimization Algorithm, GOA)求解光伏模型参数辨识问题,免费提供完整MATLAB代码链接
开发语言·算法·matlab·智能优化算法·光伏模型参数估计·光伏模型参数辨识·最新群智能算法
冴羽yayujs1 天前
GitHub 前端热榜项目 - 日榜(2026-05-07)
前端·github
kels88991 天前
2026 年黄金实时价格数据 API 接口实测推荐
开发语言·笔记·python·金融·区块链
深蓝海拓1 天前
用HSL颜色系统改造qdarkstyle样式表库
前端·笔记·python·qt·学习