HarmonyOS应用实战-启示散页-08-提问历史别无限长:用去重、截断和 Sheet 入口做轻量记忆
提问历史的价值不是把用户每一次点击都永久保存,而是帮助用户找回最近问过什么、当时抽到了什么。答案之书如果无限追加历史,Preferences 会越来越大,列表越来越慢,重复问题也会淹没有用记录。轻量应用更适合做"最近记忆":去重、截断、可清理,并用 Sheet 承接查看入口。

这篇文章会把问题拆成四个可落地的点:
- 历史记录保存问题、答案、题库和时间,不只保存问题文本。
- 用最近上限控制 Preferences 体积。
- 相同问题可合并或前移,避免列表噪声。
- Sheet 入口只展示轻量历史,完整规则留在 Service。


1. 历史不是日志仓库
日志追求完整,历史追求可用。用户在答案之书里可能连续问同一个问题,也可能只是试动画。把每次抽取都永久保存,会让列表快速失去阅读价值。历史服务应该定义上限,比如只保留最近 50 条。
ts
const MAX_HISTORY_COUNT: number = 50;
interface QuestionHistoryRecord {
id: string;
question: string;
answerText: string;
deckId: string;
answerId: string;
createdAt: number;
}
历史记录仍要保存来源字段,因为它可能被用于收藏、复盘或再次提问。
2. 空问题不要污染历史
答案之书允许用户不输入问题直接抽答案,但这类行为不一定值得进提问历史。可以只记录非空问题,或者把空问题标记为"随便问问"。关键是规则要在 Service 里统一,而不是由页面临时判断。
ts
function normalizeQuestion(question?: string): string {
return (question ?? '').trim().replace(/\s+/g, ' ');
}
if (!normalizeQuestion(result.question)) {
return;
}
输入规范化能合并多余空格,也能避免空字符串成为历史列表里的噪声。
3. 重复问题可以前移
用户重复问同一个问题时,历史列表里出现十条相同问题没有意义。答案之书可以按规范化问题去重:新结果覆盖旧结果,并移动到最前面。这样保留最近答案,也保持列表清爽。
ts
const key: string = normalizeQuestion(result.question).toLocaleLowerCase();
const rest: QuestionHistoryRecord[] = records.filter((item: QuestionHistoryRecord): boolean => {
return normalizeQuestion(item.question).toLocaleLowerCase() !== key;
});
const next: QuestionHistoryRecord[] = [createHistoryRecord(result), ...rest]
.slice(0, MAX_HISTORY_COUNT);
这个策略不是丢数据,而是把历史定位成最近记忆。用户最需要看到的是最后一次问这个问题得到的答案。
4. 追加历史要复用抽取结果
历史来源应该是 DrawAnswerResult,不要让 DrawingPage 再拼一遍字段。只要抽取结果已经包含 deckId、answerId、answerText、question 和 drawnAt,历史服务就能稳定生成记录。
ts
function createHistoryRecord(result: DrawAnswerResult): QuestionHistoryRecord {
return {
id: newId('history'),
question: normalizeQuestion(result.question),
answerText: result.answer.text,
deckId: result.deckId,
answerId: result.answer.id,
createdAt: result.drawnAt
};
}
历史是抽取链路的下游,字段必须从同一个结果对象来,避免收藏和历史对不上。
5. Preferences 里只放必要字段
历史记录可能增长,字段越多越容易拖慢读写。不要把整份 Deck、完整 Answer 对象或 UI 展示字段写进历史。展示需要的题库名、时间文案可以在 Service 读取时组装。
ts
await PreferencesStore.setJson<QuestionHistoryRecord[]>(
PrefStoreName.App,
AppPrefKey.QuestionHistory,
next
);
AppStorage.setOrCreate(AppStorageKey.LastHistoryUpdateAt, Date.now());
持久化字段越稳定,后续迁移越轻。展示字段属于 ViewItem,不属于历史原始记录。
6. Sheet 适合承接轻量历史
提问历史不是主流程页面,放在 Sheet 里更符合轻量工具体验。首页保留抽取入口,Sheet 展示最近问题、答案和时间,并提供再次提问、收藏、清空。
ts
@Builder HistorySheet() {
Column({ space: 12 }) {
ForEach(this.historyItems, (item: HistoryViewItem) => {
this.historyRow(item);
}, (item: HistoryViewItem): string => item.id)
}
}
Sheet 只承载展示和操作入口,不直接读写 Preferences。列表数据仍从 HistoryService 来。
7. 清空历史要明确范围
清空历史不是清空收藏,也不是清空题库。按钮文案和 Service 方法都要表达范围,避免误删。对于本地离线应用,用户数据边界越清楚,隐私和发布说明越好写。
ts
async clearQuestionHistory(): Promise<void> {
await PreferencesStore.setJson<QuestionHistoryRecord[]>(
PrefStoreName.App,
AppPrefKey.QuestionHistory,
[]
);
AppStorage.setOrCreate(AppStorageKey.LastHistoryUpdateAt, Date.now());
}
清空动作只触碰历史 key。收藏、题库、当前题库都不应该被顺手改掉。
8. 历史刷新独立于题库刷新
历史变化不一定意味着题库变化。答案之书用独立的 LastHistoryUpdateAt,Sheet 打开或刷新信号变化时重新读取。这样历史列表不会依赖首页重建,也不会因为收藏变化而误刷新。
ts
@StorageLink('lastHistoryUpdateAt')
@Watch('reloadHistory')
lastHistoryUpdateAt: number = 0;
private async reloadHistory(): Promise<void> {
this.historyItems = await HistoryService.listViewItems();
}
刷新信号按数据域拆分,是让小应用长期可维护的基础习惯。
9. 验证与排障
历史验证要连续抽取、重复提问、清空、重启、删除来源题库。尤其要看 MAX_HISTORY_COUNT 是否生效,以及重复问题是否前移而不是新增一条。
text
验证点:
1. 空问题不进入历史或按约定显示
2. 同一问题重复提问只保留最近一条
3. 超过 50 条后只保留最近 50 条
4. 清空历史不影响收藏和题库
5. 重启后历史仍按时间倒序显示
如果 Sheet 打开很慢,先看历史是否无限增长,再看是否把展示字段或整份题库写进了历史记录。
历史里的"再次提问"也要重新走抽取链路,而不是把旧答案重新展示一遍。历史记录提供的是 question 上下文,新的答案仍由 AnswerDrawService 从当前题库里生成。这样用户能区分"查看过去结果"和"基于同一个问题再抽一次",历史功能才不会变成隐藏的结果缓存。
这个边界对收藏也有影响:从历史里收藏旧答案,应该收藏历史记录里的 answerId 和 answerText;从历史里再次提问后收藏新答案,则使用新的 DrawAnswerResult。两个动作看起来都从 Sheet 发起,但数据来源不同,Service 方法也应该分开。
验证清单
- 清应用数据后从冷启动进入,确认默认数据、页面状态和日志分支符合预期。
- 对本文涉及的写路径准备正常、空值、重复、越界四类输入,确认错误停在 Service 或 Repository。
- 页面返回、重新进入、切换题库、收藏、历史或删除后,确认对应刷新信号触发重新读取。
- 修改资源或模块归属后重新构建,确认 HAP、HAR、HSP 的依赖方向没有反转。
- 涉及真机体验、备份恢复、发布素材的内容,单独记录是否已经在设备或平台侧验证。
常见问题与处理
| 现象 | 先看哪里 | 处理方式 |
|---|---|---|
| 历史列表越来越慢 | MAX_HISTORY_COUNT 是否生效 | 写入时截断最近记录 |
| 重复问题刷屏 | 是否按规范化问题去重 | 重复问题前移并覆盖旧结果 |
| 清空后收藏没了 | 清理范围是否过大 | 只清 QuestionHistory key |
| Sheet 数据不刷新 | LastHistoryUpdateAt 是否变化 | 历史写入和清空后发刷新信号 |
小结
提问历史应该是轻量记忆,不是无限日志。规范化问题、合并重复、限制上限、独立刷新,能让历史功能长期保持有用,也不会拖累本地存储。