HarmonyOS APP<玩转React>开源教程二十三:面试题库功能

第23次:面试题库功能

本文讲解题库系统的主干实现。它对应的不是单个文件,而是一条完整链路:InterviewQuizService.etsQuizBankPage.etsQuizPracticePage.ets,以及与错题本的联动。阅读时请把它当作一个完整的刷题系统来理解。


题库系统在项目里的真实定位

课程内测验强调"跟课学完顺手测一下",而面试题库强调的是"体系化刷题"。因此它的设计比每日一题和普通课程测验都更完整:

  • 有模块分类
  • 有统计卡片
  • 有模块练习
  • 有错题本入口
  • 有练习总结页

先看整条链路:
QuizBankPage
InterviewQuizService.init
WrongAnswerService.init
加载所有题目与统计
显示模块列表与正确率
进入 QuizPracticePage
recordAnswer 更新统计
addWrongAnswer / markAsMastered
WrongAnswerBookPage


一、为什么题库首页要先显示统计再显示模块

QuizBankPage 的内容顺序非常有讲究:

  1. 顶部导航
  2. 我的统计卡片
  3. 错题本入口
  4. 模块题库列表

这个顺序不是为了好看,而是为了建立学习反馈感。用户进入题库首页时,先看到的是:

  • 我一共答了多少题
  • 我答对了多少题
  • 我的正确率是多少

然后再看到模块列表,才更容易知道下一步去哪里练。这个设计非常符合学习产品的逻辑,因为用户不是来"浏览信息"的,而是来"持续进步"的。


二、InterviewQuizService 的职责比你想象的大

当前题库服务做的事情非常完整:

  • 初始化全部题目
  • 读取整体统计数据
  • 记录每题答题结果
  • 维护模块级统计
  • 维护整体正确率
  • 记录每道题最后一次答题状态
  • 支持重置统计

这里最值得学习的是它不仅存"总答题数",还存"每个模块的统计"。这意味着用户后续可以看到自己是 Hooks 模块弱,还是组件模块弱,而不是只知道一个笼统的全局正确率。

这类"全局统计 + 局部统计"并存的设计,在题库类应用中非常常见,也非常实用。


三、答题记录为什么不是简单累加

recordAnswer(questionId, answer, isCorrect) 是这套系统最关键的方法之一。它的设计并不是"每答一次就无脑加一",而是更细致地处理了同一道题的重复作答:

  • 第一次作答:计入总答题数和正确/错误数。
  • 如果之前答错,后来答对:要修正统计。
  • 如果之前答过且结果没变:不重复累计。

这说明当前统计系统更接近"题目掌握情况",而不是"机械刷题次数"。这种设计对学习产品更友好,因为它鼓励用户修正错误,而不是把统计做得越来越失真。


四、题库首页为什么还要初始化错题本服务

QuizBankPage 在加载时不仅调用 InterviewQuizService.init(),还调用 WrongAnswerService.init()。这是因为题库首页需要同时展示:

  • 我的答题统计
  • 错题本入口
  • 当前未掌握错题数量

也就是说,题库首页本质上已经是"练习中心",而不是单纯的目录页。它既展示当前能力,也展示后续复习入口。

这一层入口在 QuizBankPage.ets 里对应的是下面这段真实加载逻辑:

ts 复制代码
private async loadData(): Promise<void> {
  this.isLoading = true;

  await InterviewQuizService.init();
  await WrongAnswerService.init();

  this.statistics = InterviewQuizService.getOverallStatistics();
  this.wrongAnswerCount = WrongAnswerService.getUnmasteredCount();

  const moduleList = InterviewQuizService.getAllModules();
  const tempModules: ModuleInfo[] = [];
  for (const m of moduleList) {
    const questions = InterviewQuizService.getQuestionsByModule(m.moduleId);
    const moduleInfo: ModuleInfo = {
      moduleId: m.moduleId,
      moduleName: m.moduleName,
      icon: m.icon,
      questionCount: questions.length,
      stats: InterviewQuizService.getModuleStatistics(m.moduleId)
    };
    tempModules.push(moduleInfo);
  }
  this.modules = tempModules;

  this.isLoading = false;
}

User WrongAnswerService InterviewQuizService QuizBankPage User WrongAnswerService InterviewQuizService QuizBankPage init() init() getOverallStatistics() getUnmasteredCount() 展示统计卡片、错题入口、模块列表


五、模块列表为什么要显示每个模块的正确率

当前 ModuleItem 中显示了:

  • 模块图标
  • 模块名称
  • 题目总数
  • 当前模块正确率

这不是简单的信息堆砌,而是给用户非常明确的练习反馈:

  • 题量决定刷题成本
  • 正确率反映掌握程度

当用户看到某个模块题目不多但正确率很低时,就知道那里是弱项;如果某个模块正确率已经很高,可能就可以暂时放一放。这就是"数据驱动学习路径"的基本思路。


六、QuizPracticePage 为什么支持两种模式

这一页非常值得学,因为它没有被写成"只能练模块题"。

当前它支持两种进入方式:

  • 通过 moduleId 进入模块练习模式
  • 通过 isFromWrongBook 进入错题练习模式

这种设计很聪明,因为:

  • 页面 UI 基本一样
  • 差异主要体现在题目来源
  • 复用一个页面就能覆盖两种练习场景

也就是说,页面层的复用不是靠硬拷贝,而是靠"参数化驱动不同模式"。


七、练习页里的答题闭环是怎样形成的

QuizPracticePage 的答题流程非常完整:

  1. 加载题目并随机打乱顺序。
  2. 展示当前题目和选项。
  3. 用户选择答案。
  4. 提交后立即判断对错。
  5. 调用 InterviewQuizService.recordAnswer() 更新统计。
  6. 如果答错,调用 WrongAnswerService.addWrongAnswer()
  7. 如果来自错题本且答对,调用 WrongAnswerService.markAsMastered()
  8. 最后一题做完后显示总结页。

这说明题库系统真正形成了:

  • 练习
  • 统计
  • 错题
  • 复习

四段闭环,而不是只有"答题"这一个动作。

把它放回真实源码里看,会更容易理解这条链路是如何闭合的:

ts 复制代码
private async handleSubmit(): Promise<void> {
  const question = this.getCurrentQuestion();
  if (!question || this.selectedAnswer < 0) return;

  this.isCorrect = InterviewQuizService.validateAnswer(question.id, this.selectedAnswer);
  this.isSubmitted = true;

  this.answers.push(this.selectedAnswer);
  this.results.push(this.isCorrect);

  await InterviewQuizService.recordAnswer(question.id, this.selectedAnswer, this.isCorrect);

  if (!this.isCorrect) {
    await WrongAnswerService.addWrongAnswer(question, this.selectedAnswer);
  } else if (this.isFromWrongBook) {
    await WrongAnswerService.markAsMastered(question.id);
  }
}

注意这里除了更新统计,还把用户本次选择和结果顺手记录进了 answersresults,这样页面后续才能继续切题并生成总结页。


八、总结页为什么非常重要

练习页最后的 SummaryView() 展示了:

  • 总题数
  • 正确题数
  • 正确率
  • 用时

这个总结页的意义在于,它给用户一次完整练习画上了一个可感知的句号。没有总结页,用户只会觉得"题做完了";有总结页,用户会觉得"我完成了一次训练"。

对于学习产品来说,这种仪式感其实很重要。它能帮助用户形成持续刷题的动力。


九、自己实操时最推荐的顺序

  1. 打开题库首页,先看统计卡片和模块列表。
  2. 进入任意模块做几道题。
  3. 故意答错一两道,观察结果卡片和解析。
  4. 完成全部题目后看总结页。
  5. 回到题库首页,观察统计是否更新。
  6. 进入错题本,再从错题本发起练习,看答对后是否会被标记为已掌握。

这一套走完后,你对题库链路的理解会非常扎实。


十、本篇常见坑

1. 只做题目列表,不做统计

没有统计的题库很难形成成长反馈。

2. 同一道题重复作答时直接无脑累加

这样会让统计越来越失真。

3. 错题本和练习页分家过度

当前项目的优点恰恰在于两者通过参数模式复用了同一个练习页。

4. 练习完成后不做总结页

会大大削弱一次练习的完成感。


本篇小结

面试题库功能并不是一个单独页面,而是一整套学习闭环:

  • QuizBankPage 负责入口和总览
  • InterviewQuizService 负责统计与题库规则
  • QuizPracticePage 负责实际练习
  • WrongAnswerService 负责错题沉淀与复习

如果你把这一套结构真正理解透了,后面看错题本功能就会非常自然,因为它本来就是题库系统的一部分。


课后练习

  1. 自己总结 QuizServiceInterviewQuizService 在业务目标上的区别。
  2. 画出"模块练习模式"和"错题练习模式"在 QuizPracticePage 中的分流图。
  3. 思考如果你要增加"只练未做过题目"模式,最适合把过滤逻辑写在服务层还是页面层,为什么。
相关推荐
nashane5 小时前
HarmonyOS 6学习:CapsLock键失效诊断与长截图完整实现指南
学习·华为·harmonyos
richard_yuu7 小时前
鸿蒙心理测评模块实战|PHQ-9/GAD7双量表答题、实时计分与结果本地化存储
华为·harmonyos
不爱吃糖的程序媛10 小时前
2026年Electron 鸿蒙PC环境搭建指南
人工智能·华为·harmonyos
nashane10 小时前
HarmonyOS 6学习:长截图功能开发中的滚动拼接与权限处理实战
人工智能·华为·harmonyos
大师兄666812 小时前
从零开发一个 HarmonyOS 输入法——KikaInputMethod 完整拆解
harmonyos·服务卡片·harmonyos6·formkit
Python私教17 小时前
鸿蒙 NEXT 也能接 MCP?用 ArkTS 跑通 AI Agent 工具链
人工智能·华为·harmonyos
Swift社区19 小时前
分布式能力在鸿蒙 PC 上到底怎么用?
分布式·华为·harmonyos
nashane1 天前
HarmonyOS 6学习:外接键盘CapsLock与长截图功能的实战调试与完整解决方案
学习·华为·计算机外设·harmonyos
aqi002 天前
一文理清 HarmonyOS 6.0.2 涵盖的十个升级点
android·华为·harmonyos·鸿蒙·harmony