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

HarmonyKit | 鸿蒙新特性:@Component 与 @Entry 组件的生命周期与编译约束
引言:两个装饰器的分工
@Component 和 @Entry 是 ArkUI 最基础的两个装饰器。前者将一个 struct 标记为可复用的 UI 组件,后者将它标记为可被路由系统导航到的独立页面。一个 struct 可以同时拥有两者——此时它既是一个页面入口,也是一个可被其他页面嵌套的组件。
在 HarmonyKit 中,主页 Index 和 10 个工具页都是 @Entry——它们是独立的路由目标。ToolCard 和 CopyButton 只是 @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 组件语法。 不能声明变量(let、const、var),不能用 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 中。
最佳实践总结
- @Component 保持轻量:一个组件只做一件事,输入输出清晰
- 避免过深的组件嵌套:超过 5 层的嵌套使数据流难以追踪
- @Entry 页面保持精简:页面只做路由入口,业务逻辑委托给子组件和方法
- 合理使用生命周期:aboutToAppear 做初始化,aboutToDisappear 做清理
- 组件优先于继承:用组合而非继承来复用逻辑
更多推荐



所有评论(0)