第 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

这很关键。

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

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

的工程化前端页面。

相关推荐
UXbot2 小时前
如何用 AI 快速生成完整的移动端 UI 界面:从描述到交付的实操教程
前端·ui·交互·ai编程·原型模式
南囝coding2 小时前
零成本打造专业域名邮箱:Cloudflare + Gmail 终极配置保姆级全攻略
前端·后端
jiayong232 小时前
第 12 课:`watch` 和防抖到底该怎么用
前端·javascript·vue.js
想唱rap2 小时前
C++11之包装器
服务器·开发语言·c++·算法·ubuntu
鹏程十八少2 小时前
2.2026金三银四 Android Handler 完全指南:28道高频面试题 + 源码解析 + 图解 (一文通关)
android·前端·面试
zhangjw342 小时前
第3篇:Java流程控制:if-else、switch、循环(for/while/do-while)全解析
java·开发语言
大连好光景2 小时前
Fiddler、Wireshark、Charles三种抓包工具的对比
前端·fiddler·wireshark
gyx_这个杀手不太冷静2 小时前
大人工智能时代下前端界面全新开发模式的思考(五)
前端·架构·ai编程