【OpenHarmony/HarmonyOs 】ArkUI 实时时钟组件:状态驱动、日期格式化与定时器生命周期

🕐 首页时钟看起来只是几行代码,却同时涉及声明式 UI、定时任务、日期格式化、组件生命周期和资源释放。本文结合 LinkOS 链界首页,完整拆解一个可长期运行的实时时钟。

一、声明式 UI 中的时钟如何刷新

ArkUI 页面不会因为 new Date() 自动重新构建。要让时间持续变化,需要把当前时间放进可观察状态:

@State currentTime: Date = new Date();
private timerId: number = -1;

组件显示 currentTime,定时器只负责更新状态。状态变化后,ArkUI 会重新计算依赖它的文本,而不需要开发者手动查找控件并修改内容。

Text(this.formatTime(this.currentTime))
  .fontSize(42)
  .fontWeight(FontWeight.Bold)

Text(this.formatDate(this.currentTime))
  .fontSize(14)

这就是声明式 UI 的核心:页面描述状态应该呈现成什么样,而不是逐个操作控件。

二、页面出现时启动定时器

项目在 aboutToAppear() 中启动每秒更新:

async aboutToAppear(): Promise<void> {
  this.timerId = setInterval(() => {
    this.currentTime = new Date();
  }, 1000);

  // 同时恢复身份、兴趣、视图模式等页面数据
}

更新间隔选 1000ms 是因为 UI 只展示到分钟或秒。若只显示小时和分钟,没有必要整秒重绘,可以计算距离下一分钟的时间,再按分钟更新,从而减少无意义刷新。

三、格式化时间和日期

不依赖额外库时,可以用基础日期 API:

private formatTime(date: Date): string {
  const hour = `${date.getHours()}`.padStart(2, '0');
  const minute = `${date.getMinutes()}`.padStart(2, '0');
  return `${hour}:${minute}`;
}

private formatDate(date: Date): string {
  const month = date.getMonth() + 1;
  const day = date.getDate();
  const week = ['日', '一', '二', '三', '四', '五', '六'][date.getDay()];
  return `${month}${day}日 星期${week}`;
}

getMonth() 从 0 开始,因此必须加 1。小时和分钟使用 padStart() 补零,否则上午 8:05 会显示成 8:5

四、必须在离开页面时清理

只创建定时器却不清理,会造成重复任务和内存泄漏。用户多次进入首页后,可能同时存在多个计时器,每秒反复更新状态。

aboutToDisappear(): void {
  if (this.timerId !== -1) {
    clearInterval(this.timerId);
    this.timerId = -1;
  }
}

如果页面只是被缓存而不是销毁,还应根据实际页面生命周期选择 onPageShow/onPageHide 或组件回调。原则是:开始任务和结束任务必须成对出现。

五、防止重复创建

某些生命周期可能多次触发。启动前先清理旧任务更稳妥:

private startClock(): void {
  this.stopClock();
  this.currentTime = new Date();
  this.timerId = setInterval(() => {
    this.currentTime = new Date();
  }, 1000);
}

private stopClock(): void {
  if (this.timerId !== -1) {
    clearInterval(this.timerId);
    this.timerId = -1;
  }
}

封装后,页面生命周期只调用 startClock()stopClock(),可读性更好,也不容易遗漏。

六、时间跳变与前后台切换

不能通过“当前时间加一秒”实现时钟,因为应用进入后台后定时器可能被暂停,设备也可能校时或切换时区。每次触发都重新 new Date(),才能读取系统真实时间。

应用从后台返回时应立即刷新一次,而不是等待下一个 tick:

onPageShow(): void {
  this.currentTime = new Date();
  this.startClock();
}

七、国际化与 12/24 小时制

中文固定拼接适合原型,但正式应用还需要:

  • 根据地区显示月/日或日/月;
  • 支持星期本地化;
  • 尊重系统 12/24 小时设置;
  • 切换时区后即时刷新;
  • 将“晴朗”等天气文本与真实天气时间分开。

如果平台提供本地化日期格式化能力,应优先使用系统格式,而不是自己维护多套语言数组。

八、性能优化

时钟状态放在首页顶层时,理论上可能使较大的构建函数重新求值。可以把时钟提取为独立 @Component,缩小状态变化影响范围:

@Component
struct ClockWidget {
  @State now: Date = new Date();
  private timerId: number = -1;
  // 独立管理计时与显示
}

此外,不要在每次刷新时执行网络请求、重新生成大数组或写入 Preferences。时钟更新只应改变时间显示。

九、测试清单 ✅

  1. 08:05 是否正确补零;
  2. 月初、月末和跨年显示是否正确;
  3. 页面反复进入退出后是否只有一个定时器;
  4. 后台停留后返回是否立刻显示当前时间;
  5. 横屏、平板和字体放大时是否溢出;
  6. 系统语言、时区或时间制式变化后是否同步。

十、总结

一个可靠的 ArkUI 时钟需要形成闭环:@State 驱动显示、生命周期启动任务、离开页面清理、每次读取系统真实时间,并把格式化与 UI 分离。把这些细节处理好,时钟组件才能长期运行而不会积累隐藏问题。⏱️

img

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐