fix(月视图): 翻月闪跳 —— 改为显式传参重建,不依赖 @State 即时可读 #6
用户反馈:11 月滑到 12 月时,先闪回 11 月,然后才跳到 12 月。
## 根因(两层)
### 第一层:异步时序
`reloadEvents()` 是 async(内部 await 读库),调用只是**启动**。
原代码:
this.reloadEvents(); // 只启动
this.swiperController.changeIndex(1); // 立刻回弹
回弹那一刻 monthPages 还是旧数据,中间页仍是上个月 ⇒ 闪回。
修:把 `rebuildPages()` 提到 changeIndex **之前**同步执行。
### 第二层:@State 赋值在当前 tick 内不可读
改完第一层后仍会闪。日志证据:
onChange index=2 当前=2026-11
同步重建完成 中间页=2026-11 ← 明明先赋了 displayMonth=12
说明 `this.displayMonth = v` 之后、同一个同步调用栈内读取它,
**不一定拿到新值**(@State 走可观察代理,写入与读取之间有调度)。
修:新增 `rebuildPagesFor(year, month)`,把月份**作为参数显式传入**,
内部不再读 `this.displayXxx`。这样无论 @State 何时生效都算得对。
## 附带修正:诊断日志本身有 bug
之前用 `monthPages[1][0].dateMs` 当作"中间页月份",
但月历第一格常属于**上个月**(12月的第一格是 11/30),
日志就写成"中间页=11月",让我误判成"月份没变",白查了一轮。
改用记录**传入参数**后真相立刻清楚:入参全程都是 2026-12,逻辑一直是对的。
## 真机验证
11月 → 左滑 → 2026年12月,选中圆存在,无闪回。
日志确认:
TRACE_PARAM 重建入参=2026-12(同步重建)
onChange index=1 guard=true → 被 guard 正确拦下
TRACE_PARAM 重建入参=2026-12(数据重建)
诊断日志已在提交前移除(grep 0 处)。
## 教训
1. @State 赋值后**不要在同一同步栈内立刻读它**做计算,要用局部变量/参数传递。
2. 写诊断日志时,验证"我关心的那个值"必须用可靠来源。
用 `数组[0]` 代表整体属性是常见陷阱 —— 这次就因此浪费了一轮排查。
This commit is contained in:
@@ -482,7 +482,7 @@ struct Index {
|
|||||||
this.events = await CalendarDataService.loadEvents(context, rangeStart, rangeEnd, this.sources);
|
this.events = await CalendarDataService.loadEvents(context, rangeStart, rangeEnd, this.sources);
|
||||||
this.todos = await CalendarDataService.loadTodos(context, this.sources);
|
this.todos = await CalendarDataService.loadTodos(context, this.sources);
|
||||||
LogUtil.write(`界面刷新完成:日程 ${this.events.length} 条(显示区间内),待办 ${this.todos.length} 条`);
|
LogUtil.write(`界面刷新完成:日程 ${this.events.length} 条(显示区间内),待办 ${this.todos.length} 条`);
|
||||||
this.rebuildPages();
|
this.rebuildPagesFor(this.displayYear, this.displayMonth);
|
||||||
// 当前正处于列表视图时立即重载,避免同步后列表空白
|
// 当前正处于列表视图时立即重载,避免同步后列表空白
|
||||||
if (this.mode === 'agenda') {
|
if (this.mode === 'agenda') {
|
||||||
await this.ensureAgendaData();
|
await this.ensureAgendaData();
|
||||||
@@ -555,11 +555,25 @@ struct Index {
|
|||||||
|
|
||||||
/** 重建三页(上月/本月/下月) */
|
/** 重建三页(上月/本月/下月) */
|
||||||
private rebuildPages(): void {
|
private rebuildPages(): void {
|
||||||
const prev = this.addMonths(this.displayYear, this.displayMonth, -1);
|
this.rebuildPagesFor(this.displayYear, this.displayMonth);
|
||||||
const next = this.addMonths(this.displayYear, this.displayMonth, 1);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按**显式传入的年月**重建三页。
|
||||||
|
*
|
||||||
|
* ⭐ 为什么需要这个版本:ArkUI 的 @State 赋值在同一个同步调用栈内
|
||||||
|
* **不一定立即反映到后续读取**(实测:先 `this.displayMonth = 12`
|
||||||
|
* 再 `rebuildPages()`,里面读到的仍是 11)。
|
||||||
|
* 翻月这种"改状态 + 立刻据此重建"的场景,必须把值用参数传下去,
|
||||||
|
* 不能依赖 `this.displayMonth` 的可读时机 —— 否则中间页会停留在旧月份,
|
||||||
|
* 表现为"闪回上个月再跳回来"。
|
||||||
|
*/
|
||||||
|
private rebuildPagesFor(year: number, month: number): void {
|
||||||
|
const prev = this.addMonths(year, month, -1);
|
||||||
|
const next = this.addMonths(year, month, 1);
|
||||||
this.monthPages = [
|
this.monthPages = [
|
||||||
this.buildMonthCells(prev[0], prev[1]),
|
this.buildMonthCells(prev[0], prev[1]),
|
||||||
this.buildMonthCells(this.displayYear, this.displayMonth),
|
this.buildMonthCells(year, month),
|
||||||
this.buildMonthCells(next[0], next[1])
|
this.buildMonthCells(next[0], next[1])
|
||||||
];
|
];
|
||||||
// 自增版本号,强制外层 ForEach 重建(见字段注释)
|
// 自增版本号,强制外层 ForEach 重建(见字段注释)
|
||||||
@@ -645,9 +659,16 @@ struct Index {
|
|||||||
// 否则 1/31 → 2/28 之后,再翻月就一直是 28 号,回不到 31。
|
// 否则 1/31 → 2/28 之后,再翻月就一直是 28 号,回不到 31。
|
||||||
const lastDay: number = new Date(nm[0], nm[1] + 1, 0).getDate();
|
const lastDay: number = new Date(nm[0], nm[1] + 1, 0).getDate();
|
||||||
this.selectedDate = new Date(nm[0], nm[1], Math.min(this.preferredDay, lastDay)).getTime();
|
this.selectedDate = new Date(nm[0], nm[1], Math.min(this.preferredDay, lastDay)).getTime();
|
||||||
this.reloadEvents();
|
// ⭐ SYNC_REBUILD_FIRST 必须先**同步**重建,再回弹。
|
||||||
|
// 原来 reloadEvents() 在前,但它是 async(内部 await 读库),
|
||||||
|
// 调用只是启动;紧接着 changeIndex(1) 回弹时 monthPages 还是旧数据,
|
||||||
|
// 中间页仍是上个月 ⇒ 界面闪回旧月份,等数据到了才跳到新月份。
|
||||||
|
// 先同步 rebuildPages,回弹时中间页已经是新月份,就没有闪跳了。
|
||||||
|
// ⭐ 用 rebuildPagesFor 显式传 nm,不依赖 this.displayMonth 的即时可读性
|
||||||
|
this.rebuildPagesFor(nm[0], nm[1]);
|
||||||
this.swiperGuard = true;
|
this.swiperGuard = true;
|
||||||
this.swiperController.changeIndex(1, false);
|
this.swiperController.changeIndex(1, false);
|
||||||
|
this.reloadEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- 同步 ----------
|
// ---------- 同步 ----------
|
||||||
|
|||||||
Reference in New Issue
Block a user