在这里插入图片描述

HarmonyKit | 鸿蒙新特性:@Component 与 @Entry 组件的生命周期与编译约束

引言:两个装饰器的分工

@Component@Entry 是 ArkUI 最基础的两个装饰器。前者将一个 struct 标记为可复用的 UI 组件,后者将它标记为可被路由系统导航到的独立页面。一个 struct 可以同时拥有两者——此时它既是一个页面入口,也是一个可被其他页面嵌套的组件。

在 HarmonyKit 中,主页 Index 和 10 个工具页都是 @Entry——它们是独立的路由目标。ToolCardCopyButton 只是 @Component——它们被嵌入到页面中使用。

项目仓库:https://atomgit.com/VON-/harmony-kit

@Entry 和 @Component 的区别

从使用角度来看,两者的核心区别在于:@Entry 是路由的目标@Component 是 UI 的构造单元。
在这里插入图片描述

// @Entry — 独立页面,可以被路由导航到
@Entry
@Component
struct JsonFormatterPage {
  // 页面级状态
  @State input: string = '';
  
  build() {
    // 完整的页面布局
    Column() {
      // 使用 @Component 子组件
      CopyButton({ content: this.formattedText })
    }
  }
}

// @Component — 可复用 UI 单元
@Component
export struct CopyButton {
  @Prop content: string;
  
  build() {
    Button('复制')
      .onClick(() => {
        // 复制逻辑
      })
  }
}

两者的编译约束也略有不同。@Entry 组件可以访问页面级别的路由上下文(onPageShow/onPageHide),而 @Component 不能。同时,@Entry 组件的样式和布局通常是完整的页面布局,而 @Component 只关注它负责的 UI 片段。

build() 方法的严格语法限制

ArkTS 的 build() 方法有一个核心限制:只能包含 UI 组件语法。 不能声明变量(letconstvar),不能用 if/else/switch/for 包裹非 UI 逻辑。以下代码在 ArkTS 中不合法:
在这里插入图片描述

// 错误!
build() {
  let result = this.calculate();
  if (result > 0) {
    this.doSomething();
  }
  Text(String(result))
}

正确做法是将逻辑迁移到 build() 外部:

// 正确
getResult(): string {
  let result = this.calculate();
  return String(result);
}

build() {
  Text(this.getResult())
}

这个限制在初学阶段让人沮丧——习惯了 React JSX 中能穿插任意 JS 表达式的人会觉得 ArkTS"用着别扭"。但这种严格性是有意为之——强制开发者将逻辑与 UI 分离。build() 只负责渲染,计算逻辑放在外部方法中。

更正式的说法是:build() 方法内部是一个 UI DSL,不是 TypeScript/JavaScript。UI DSL 只接受组件构造函数、装饰器调用和有限的表达式(三元运算符、函数调用)。一切变量声明、循环语句、条件语句都需要放在 build() 之外。

// build() 中可以使用的有限表达式示例
build() {
  Column() {
    // ✅ 三元运算符
    Text(this.isValid ? '有效' : '无效')
    
    // ✅ 函数调用
    Text(this.getFormattedResult())
    
    // ✅ 数组展开(ForEach)
    ForEach(this.items, (item: ItemType) => {
      Text(item.label)
    })
    
    // ❌ 不能声明变量
    // ❌ 不能写 for/while 循环
    // ❌ 不能写 try/catch
    // ❌ 不能写 switch
  }
}

生命周期方法详解

ArkUI 组件的生命周期方法按调用顺序排列如下:

aboutToAppear()

组件即将显示时回调。这是初始化数据的最佳时机——在 build() 执行之前调用。

aboutToAppear() {
  this.tsToDate();  // 页面打开即刻展示当前时间戳
}

在 HarmonyKit 中,aboutToAppear 被广泛用于"预填充"场景:

  • 时间戳转换:打开页面时,自动显示当前时间戳和对应的日期
  • UUID 生成器:打开页面时,自动生成一个 UUID
  • 进制转换:打开页面时,默认输入 255 并展示多种进制结果
  • 文本统计:打开页面时,展示一段示例文本的统计数据

这样做的目的是"零操作体验"——用户打开工具页,不需要先输入点什么才能看到效果,页面本身就在展示有意义的内容。

aboutToDisappear()

组件即将销毁时回调。用于清理资源:
在这里插入图片描述

aboutToDisappear() {
  clearInterval(this.timer);  // 清理定时器
}

HarmonyKit 中这个回调使用较少,因为大多数工具不涉及定时器、订阅等需要手动清理的资源。如果未来加入实时相关工具(如秒表、计时器),aboutToDisappear 将是资源清理的关键入口。

onPageShow() — 仅 @Entry 可用

页面每次显示时触发。注意与 aboutToAppear 的区别——aboutToAppear 只在组件首次创建时触发一次,onPageShow 在每次页面出现在前台时触发(包括从后台切回、从其他页面返回)。

onPageHide() — 仅 @Entry 可用

页面不再显示时触发(切到后台、导航到其他页面)。通常与 onPageShow 成对使用。

onPageShow() {
  // 页面显示时刷新数据
}

onPageHide() {
  // 页面隐藏时暂停耗时操作
}

onBackPress() — 仅 @Entry 可用

用户点击系统返回键时触发。返回 true 表示拦截返回操作,false 表示执行默认返回行为:

onBackPress(): boolean {
  if (this.hasUnsavedChanges) {
    this.showDiscardDialog();  // 弹窗确认是否丢弃更改
    return true;  // 拦截返回
  }
  return false;  // 允许返回
}

HarmonyKit 中的工具大多是无状态的(每次打开都是新的输入),所以没有使用 onBackPress。但对于表单类的工具(例如 JWT 调试器中的编辑区域),onBackPress 提供了防止数据丢失的机会。

生命周期完整示例

@Entry
@Component
struct TimestampConverter {
  @State timestamp: number = 0;

  aboutToAppear() {
    // 1. 组件创建时:设置默认值
    this.timestamp = Date.now();
  }

  onPageShow() {
    // 2. 页面显示时:刷新数据(可能从后台切回)
  }

  build() {
    // 3. 渲染 UI
    Column() {
      Text(this.timestamp.toString())
    }
  }

  onPageHide() {
    // 4. 页面隐藏时:暂停处理
  }

  aboutToDisappear() {
    // 5. 组件销毁时:清理资源
  }

  onBackPress(): boolean {
    // 6. 返回键按下时
    return false;
  }
}

页面级生命周期 vs 组件级生命周期

需要特别区分的是:@Component 只有 aboutToAppear 和 aboutToDisappear(组件级生命周期);@Entry 在上面两个之外还有 onPageShow、onPageHide、onBackPress(页面级生命周期)。

区分的原因是:一个页面可能由多个 @Component 嵌套组成,当页面在前台/后台切换时,所有子组件的 aboutToAppear/aboutToDisappear 不会重复触发——触发的是页面的 onPageShow/onPageHide。只有页面真正创建和销毁时,组件的 aboutToAppear 和 aboutToDisappear 才会触发。

struct vs class

ArkUI 组件必须是 struct 而不能是 class。struct 不支持继承——组件复用通过组合(@Prop/@Consume)而非继承实现。熟悉的 OOP 模式(BasePage → JsonPage extends BasePage)在 ArkUI 中不适用。

// ArkUI 组件必须是 struct
@Component
export struct ToolCard {
  @Prop tool: ToolItem;
  build() {
    // ...
  }
}

// 以下代码不合法:
// @Component
// export class ToolCard { ... }  // 错误!

如果确实需要共享逻辑(如多个工具页都需要 CopyButton + 输出区域),方案是抽出独立的 @Component(如 CopyButton)而非抽取基类 struct。组合优于继承——听起来和 React 社区的口号一致。

结构体与类在实践中还有另一个关键区别:struct 不能定义自定义构造函数,只能通过组件参数传递属性:

// ✅ 正确:通过组件参数传递
ToolCard({ tool: item })

// ❌ 错误:struct 不能定义构造函数
// new ToolCard(item)

这个限制看似约束,实际上让组件接口变得更加显式——所有输入都通过 @Prop/@Consume/@State 声明,一眼就能看出组件的依赖项。

@Builder 的限制

@Builder 方法不能拥有 @State。导航映射的 NavMap 是 @Builder——它只是路由表(name → Page),不管理状态。它只是从 name 参数查到对应的页面组件并调它的构建函数。

一个常见错误:HdsNavDestination 与 @Entry 的关系

HarmonyKit 早期尝试让工具页同时是 @Entry 和 HdsNavDestination。但 HdsNavDestination 是为 HdsNavigation + NavPathStack 体系设计的——它的 titleBar 配置类型和 @Entry 页面的上下文不兼容。最终 HarmonyKit 选择:工具页保持 @Entry(router 导航),HdsTabs 用于主页底部导航栏。

这个矛盾的根源在于:@Entry 是页面路由的旧体系,HdsNavDestination 是新体系。 两者不能共存于同一个页面,因为它们在生命周期管理和导航上下文上有根本差异。如果后续迁移到 NavPathStack,所有工具页需要从 @Entry 改为普通的 @Component,嵌入到 HdsNavigation 的 navDestination 中。

最佳实践总结

  1. @Component 保持轻量:一个组件只做一件事,输入输出清晰
  2. 避免过深的组件嵌套:超过 5 层的嵌套使数据流难以追踪
  3. @Entry 页面保持精简:页面只做路由入口,业务逻辑委托给子组件和方法
  4. 合理使用生命周期:aboutToAppear 做初始化,aboutToDisappear 做清理
  5. 组件优先于继承:用组合而非继承来复用逻辑

项目仓库:https://atomgit.com/VON-/harmony-kit

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐