fix(月视图): 首屏不变色 —— 真正根因是外层 ForEach key 不变 #6
上一版只改内层 key 没修好,因为问题不在内层。
## 真正根因
外层 ForEach 的 key 是:
(cells) => String(cells[0].dateMs) // "这页第一天"
重建前后这个值**完全相同**(还是那个月的 1 号)。
⇒ ArkUI 判定"这一项没变" ⇒ **不重新执行 itemGenerator**
⇒ 里面的 chunkCells(cells) 不重算
⇒ 内层 ForEach 收到的还是**旧的 cells 数组**,旧数组里 hasEvent 仍是 false
内层 key 再怎么改都没用 —— 内层压根没被重新调用。
## 修法
新增 `@State monthDataVersion`,每次 rebuildPages 后自增,拼进外层 key:
(cells) => `${cells[0].dateMs}_v${this.monthDataVersion}`
key 一变 → 外层重建 → 内层重新执行 → 颜色正确。
为什么不用"把整页 hasEvent 拼成 key":一页 42 格,key 会长很多,
且会参与 diff 比较,有性能代价。版本号是 O(1),语义也更清楚。
## 同时补了诊断日志
buildMonthCells 里输出:事件条数 / 有事件日期数 / 本页命中格数 / 当前版本号。
用来判断问题卡在哪一层:
· "有事件的日期 0 天" → 数据层没算对
· "命中 X 格" 但界面没变色 → 渲染/key 问题
## ⚠️ 教训(两层 ForEach 嵌套时)
这类 bug 我第一轮判断错了:只看到最内层 key 缺 hasEvent 就改了,
没意识到**外层 key 不变会让整棵子树被复用**,内层改什么都没用。
排查嵌套 ForEach 的刷新问题时,要从**最外层**往里查,
每一层的 key 都必须随数据变化。
This commit is contained in:
@@ -83,6 +83,12 @@ struct Index {
|
|||||||
@State agendaEvents: DisplayEvent[] = []; // 列表视图全量数据(今天 ~ 未来2年)
|
@State agendaEvents: DisplayEvent[] = []; // 列表视图全量数据(今天 ~ 未来2年)
|
||||||
private agendaStale: boolean = true; // 全量数据是否需要重新加载
|
private agendaStale: boolean = true; // 全量数据是否需要重新加载
|
||||||
@State monthPages: MonthCell[][] = [[], [], []]; // 上月/本月/下月
|
@State monthPages: MonthCell[][] = [[], [], []]; // 上月/本月/下月
|
||||||
|
/** ⭐ 月视图数据版本号:每次 rebuildPages 后自增,拼进外层 ForEach 的 key。
|
||||||
|
* 为什么需要它:外层 key 原本用"这页第一天"(cells[0].dateMs),
|
||||||
|
* 数据重载前后这个值完全相同,ArkUI 会判定"同一项"而**复用整棵子树**,
|
||||||
|
* 连 itemGenerator 都不重新执行 ⇒ 内层永远拿到旧的 cells(hasEvent 全 false)。
|
||||||
|
* 只改内层 key 是没用的,必须让外层 key 变。 */
|
||||||
|
@State monthDataVersion: number = 0;
|
||||||
@State syncing: boolean = false;
|
@State syncing: boolean = false;
|
||||||
@State loading: boolean = true;
|
@State loading: boolean = true;
|
||||||
@State menuOpen: boolean = false; // 顶部 ≡ 下拉菜单
|
@State menuOpen: boolean = false; // 顶部 ≡ 下拉菜单
|
||||||
@@ -517,10 +523,8 @@ struct Index {
|
|||||||
const gridStart: number = first - offset * 86400000;
|
const gridStart: number = first - offset * 86400000;
|
||||||
const todayStart: number = this.startOfDay(Date.now());
|
const todayStart: number = this.startOfDay(Date.now());
|
||||||
const eventDays: Set<number> = this.buildEventDaySet();
|
const eventDays: Set<number> = this.buildEventDaySet();
|
||||||
// DOT_DEBUG 临时:确认集合算出来了没有
|
// 诊断:本次构建里 hasEvent=true 的格子数(用于排查"首屏不变色")
|
||||||
if (month >= 0) {
|
let flagTrue: number = 0;
|
||||||
LogUtil.write(`月格点标记 ${year}-${month + 1}:事件 ${this.events.length} 条,有事件的日期 ${eventDays.size} 天`);
|
|
||||||
}
|
|
||||||
const cells: MonthCell[] = [];
|
const cells: MonthCell[] = [];
|
||||||
for (let i = 0; i < 42; i++) {
|
for (let i = 0; i < 42; i++) {
|
||||||
const dateMs: number = gridStart + i * 86400000;
|
const dateMs: number = gridStart + i * 86400000;
|
||||||
@@ -532,8 +536,16 @@ struct Index {
|
|||||||
cell.isToday = dateMs === todayStart;
|
cell.isToday = dateMs === todayStart;
|
||||||
cell.lunar = LunarUtil.lunarDayText(dateMs);
|
cell.lunar = LunarUtil.lunarDayText(dateMs);
|
||||||
cell.hasEvent = eventDays.has(this.startOfDay(dateMs));
|
cell.hasEvent = eventDays.has(this.startOfDay(dateMs));
|
||||||
|
if (cell.hasEvent) {
|
||||||
|
flagTrue++;
|
||||||
|
}
|
||||||
cells.push(cell);
|
cells.push(cell);
|
||||||
}
|
}
|
||||||
|
if (month >= 0) {
|
||||||
|
LogUtil.write(`月视图构建 ${year}-${month + 1}:事件 ${this.events.length} 条,`
|
||||||
|
+ `有事件的日期 ${eventDays.size} 天,本页命中 ${flagTrue} 格,`
|
||||||
|
+ `版本 v${this.monthDataVersion}`);
|
||||||
|
}
|
||||||
return cells;
|
return cells;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,6 +558,8 @@ struct Index {
|
|||||||
this.buildMonthCells(this.displayYear, this.displayMonth),
|
this.buildMonthCells(this.displayYear, this.displayMonth),
|
||||||
this.buildMonthCells(next[0], next[1])
|
this.buildMonthCells(next[0], next[1])
|
||||||
];
|
];
|
||||||
|
// 自增版本号,强制外层 ForEach 重建(见字段注释)
|
||||||
|
this.monthDataVersion = this.monthDataVersion + 1;
|
||||||
this.rebuildDayTimeline();
|
this.rebuildDayTimeline();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1827,7 +1841,9 @@ struct Index {
|
|||||||
.width('100%')
|
.width('100%')
|
||||||
// 注意:不要给页面设百分比高度——竖屏时 Swiper 是自然高度,
|
// 注意:不要给页面设百分比高度——竖屏时 Swiper 是自然高度,
|
||||||
// 子组件 height('100%') 对 auto 父容器解析为 0,整月网格会塌陷
|
// 子组件 height('100%') 对 auto 父容器解析为 0,整月网格会塌陷
|
||||||
}, (cells: MonthCell[]) => String(cells[0].dateMs))
|
// ⚠️ 必须带版本号:只按"这页第一天"做 key 时,数据重载前后 key 不变,
|
||||||
|
// ArkUI 会复用整棵子树、不重跑 itemGenerator,内层就拿不到新数据。
|
||||||
|
}, (cells: MonthCell[]) => `${cells.length > 0 ? cells[0].dateMs : 0}_v${this.monthDataVersion}`)
|
||||||
}
|
}
|
||||||
.index(1)
|
.index(1)
|
||||||
.loop(false)
|
.loop(false)
|
||||||
|
|||||||
Reference in New Issue
Block a user