From d49b041a2ce908dd44b888306eefc1ddc2104789 Mon Sep 17 00:00:00 2001 From: laoyang Date: Mon, 2 Nov 2026 17:10:38 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E6=9C=88=E8=A7=86=E5=9B=BE):=20=E9=A6=96?= =?UTF-8?q?=E5=B1=8F=E5=86=9C=E5=8E=86=E4=B8=8D=E5=8F=98=E7=BA=A2=EF=BC=8C?= =?UTF-8?q?=E5=88=87=E8=A7=86=E5=9B=BE=E6=89=8D=E7=94=9F=E6=95=88=20#6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用户反馈:刚打开应用时,月视图里有事件的日期农历没变红; 切到周视图再切回月视图,才变红。 ## 根因:ForEach 的 key 不完整 月视图的 key 是: (cell) => `${cell.dateMs}_${cell.dateMs === this.selectedDate}` **没有包含 hasEvent。** 首屏时序: 1. aboutToAppear → rebuildPages(),此时 events 为空,hasEvent 全 false 2. reloadEvents() 拉回数据 → rebuildPages() 再次重建,hasEvent 变 true 3. 但每个格子的 key 完全没变(dateMs 和选中态都没变) ⇒ ArkUI 判定"这些项没有变化",**复用旧组件、跳过重绘**,颜色停在旧值 切视图能恢复,是因为切回来时整棵子树被重新创建,绕过了 diff。 ## 修法 把影响渲染的**全部**状态纳入 key:hasEvent / isToday / 是否选中。 key 变了 → ArkUI 视为新项 → 重建该格子 → 颜色正确。 ## 顺带加固 中间层"每周"的 key 原来是 `String(idx)`(0..5),跨月翻页时序号永远重复, 会让 ArkUI 误判为同一周而复用旧节点。改为用该周首日的 dateMs 做 key。 ## 为什么不改成 @Observed / @ObjectLink monthPages 是二维数组(MonthCell[][]),buildMonthCells 返回新数组、 chunkCells 又一次切片,改成可观察对象要动整条构建链路, 牵涉面大且容易引入其它 diff 问题。用 key 是最小、最稳的改法。 ⚠️ 通用教训:ArkUI 的 ForEach 用自定义 key 时,**key 必须覆盖所有影响渲染的 字段**。漏一个字段,该字段更新后界面就不刷新 —— 而且这类 bug 只在 "同一项的数据变了、key 没变"时出现,首次渲染永远是正常的,很容易漏测。 --- entry/src/main/ets/pages/Index.ets | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index 11f79c1..ec5e95e 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -1706,7 +1706,8 @@ struct Index { .onClick(() => { this.selectedDate = cell.dateMs; }) - }, (cell: MonthCell) => `${cell.dateMs}_${cell.dateMs === this.selectedDate}_${cell.hasEvent}`) + }, (cell: MonthCell) => `${cell.dateMs}_${cell.dateMs === this.selectedDate}` + + `_he${cell.hasEvent ? 1 : 0}_td${cell.isToday ? 1 : 0}`) } .width('100%') .padding({ left: 12, right: 12 }) @@ -1808,13 +1809,20 @@ struct Index { Column({ space: 2 }) { ForEach(this.chunkCells(cells), (week: MonthCell[], idx: number) => { Row({ space: 2 }) { + // ⭐ key 必须包含影响渲染的**全部**状态:hasEvent / isToday / + // 是否选中。只放 dateMs+选中态时,数据加载完成后 hasEvent 从 + // false 变 true 但 key 没变 ⇒ ArkUI 复用旧组件、跳过重绘, + // 表现为"首屏农历不变红,切一次视图才正常"。 ForEach(week, (cell: MonthCell) => { this.dayCell(cell) - }, (cell: MonthCell) => `${cell.dateMs}_${cell.dateMs === this.selectedDate}`) + }, (cell: MonthCell) => `${cell.dateMs}_${cell.dateMs === this.selectedDate}` + + `_he${cell.hasEvent ? 1 : 0}_td${cell.isToday ? 1 : 0}`) } .width('100%') .layoutWeight(this.wideLayout ? 1 : 0) - }, (week: MonthCell[], idx: number) => String(idx)) + // 用"这一周首日的 dateMs"当 key,而不是序号:跨月翻页时序号会重复, + // 会让 ArkUI 误以为是同一周而复用旧节点。 + }, (week: MonthCell[], idx: number) => `wk${week.length > 0 ? week[0].dateMs : idx}`) } .width('100%') // 注意:不要给页面设百分比高度——竖屏时 Swiper 是自然高度,