
前言
在 ArkUI 中,装饰器 是声明式 UI 的核心语法。@Entry 和 @Component 是最基础也是最重要的两个装饰器,它们分别用于声明页面入口和自定义组件。
"海风日记"共有 37 个 .ets 源文件 ,每个文件都使用了 @Component 或 @Entry 装饰器。本文将从源码出发,深入讲解这两个装饰器的语法、机制、生命周期以及最佳实践,帮助你全面掌握 ArkUI 的组件化开发。
一、装饰器概述
1.1 什么是装饰器
装饰器是 ArkTS 语言提供的一种声明式语法,用于为类或类成员添加元数据和行为。在 ArkUI 中,装饰器主要用于:
- 声明组件 :
@Component将一个类标记为 UI 组件 - 声明页面入口 :
@Entry将一个组件标记为独立页面 - 状态管理 :
@State、@Prop、@Link等管理组件状态 - 构建函数 :
@Builder封装 UI 构建逻辑
1.2 @Entry 与 @Component 的关系
| 装饰器 | 作用 | 是否必须 | 使用场景 |
|---|---|---|---|
@Component |
声明这是一个 UI 组件 | 是 | 所有组件必须使用 |
@Entry |
声明这是一个页面入口 | 否(仅页面需要) | 注册在 main_pages.json 中的页面 |
typescript
// 页面组件:同时使用 @Entry 和 @Component
@Entry
@Component
struct SplashPage { ... }
// 普通组件:只使用 @Component
@Component
export struct DiaryCard { ... }
1.3 海风日记中的组件统计
| 类型 | 装饰器 | 数量 | 示例 |
|---|---|---|---|
| 页面组件 | @Entry @Component |
26 个 | SplashPage, Index.ets |
| 通用组件 | @Component + export |
4 个 | DiaryCard, MoodSelector |
| 内部组件 | @Component(不导出) |
7 个 | ReviewTab 内的统计卡片 |
二、@Component 装饰器详解
2.1 基本语法
typescript
@Component
struct ComponentName {
// 状态变量
@State message: string = 'Hello'
// 构建方法
build() {
// 返回 UI 描述
Column() {
Text(this.message)
}
}
}
2.2 @Component 的规则
- 必须使用 struct :
@Component只能装饰struct类型 - 必须包含 build() 方法 :
build()返回组件的 UI 描述 - build() 必须有且只有一个根节点:根节点可以是 Column、Row、Stack 等容器
- 不支持继承 :
@Component修饰的 struct 不能继承其他类
2.3 可导出与不可导出
typescript
// 可导出的组件(供其他文件 import 使用)
@Component
export struct DiaryCard {
// 属性声明
moodColor: MoodColor = 'default'
content: string = ''
build() {
Row() { ... }
}
}
// 不导出的组件(仅在本文件内使用)
@Component
struct InnerComponent {
build() {
Column() { ... }
}
}
2.4 组件属性
@Component 中的非 @State 属性可以通过构造函数参数传递:
typescript
@Component
export struct DiaryCard {
// 这些属性可以通过父组件传入
moodColor: MoodColor = 'default'
timeStr: string = ''
content: string = ''
tags: string[] = []
isPinned: boolean = false
hasImage: boolean = false
moodEmoji: string = ''
onTap: () => void = () => {} // 函数类型属性
build() {
Row() {
// 使用属性构建 UI
Text(this.content)
}
.onClick(() => { this.onTap() })
}
}
// 在父组件中使用
DiaryCard({
moodColor: getMoodColor(item.mood),
timeStr: '今天 · 06:12',
content: item.content,
tags: item.tags,
isPinned: item.isPinned,
hasImage: item.imageUrls.length > 0,
moodEmoji: getMoodEmoji(item.mood),
onTap: () => { router.pushUrl({ url: 'pages/diary/DiaryDetailPage' }) }
})
三、@Entry 装饰器详解
3.1 基本语法
typescript
@Entry
@Component
struct SplashPage {
build() {
Stack() {
// 页面内容
}
.width('100%')
.height('100%')
}
}
3.2 @Entry 的作用
@Entry 做了以下事情:
- 注册页面:将该组件注册为可被路由跳转的页面
- 提供生命周期 :添加
aboutToAppear()、aboutToDisappear()、onPageShow()、onPageHide()等生命周期回调 - 绑定路由参数 :可以通过
router.getParams()获取路由参数
3.3 @Entry 的生命周期
typescript
@Entry
@Component
struct DiaryDetailPage {
aboutToAppear() {
// 1. 页面即将显示时调用
// 适合:获取路由参数、初始化数据
const params = router.getParams() as Record<string, string>
if (params?.diaryId) {
this.diaryId = params.diaryId
}
}
onPageShow() {
// 2. 页面显示时调用(每次回到该页面都会触发)
// 适合:刷新数据、恢复状态
this.refreshData()
}
onPageHide() {
// 3. 页面隐藏时调用
// 适合:暂停动画、保存草稿
}
aboutToDisappear() {
// 4. 页面即将销毁时调用
// 适合:清理资源、取消订阅
this.cleanup()
}
}
3.4 生命周期执行顺序
页面启动时的生命周期顺序:
1. aboutToAppear() ← 页面即将显示
2. build() ← 构建 UI
3. onPageShow() ← 页面已显示
页面跳转到其他页面的生命周期顺序:
1. onPageHide() ← 当前页面隐藏
2. aboutToAppear() ← 新页面即将显示
3. build() ← 新页面构建
4. onPageShow() ← 新页面已显示
四、组件导出与导入
4.1 export 导出组件
typescript
// DiaryCard.ets
@Component
export struct DiaryCard {
// 组件定义
}
// MoodSelector.ets
@Component
export struct MoodSelector {
// 组件定义
}
// WaveHeaderBand.ets
@Component
export struct WaveHeaderBand {
// 组件定义
}
4.2 import 导入组件
typescript
// HomeTab.ets --- 导入组件
import { DiaryCard } from '../../common/components/DiaryCard'
import { WaveHeaderBand } from '../../common/components/WaveHeaderBand'
// CalendarTab.ets --- 导入数据和模型
import { MOCK_DIARIES } from '../../common/model/DiaryModel'
import { COLOR_PRIMARY } from '../../common/constants/Colors'
// Index.ets --- 导入子页面组件
import { HomeTab } from './home/HomeTab'
import { CalendarTab } from './calendar/CalendarTab'
import { ReviewTab } from './review/ReviewTab'
import { MineTab } from './mine/MineTab'
4.3 导入路径的规则
当前文件: pages/home/HomeTab.ets
导入组件: ../../common/components/DiaryCard.ets
↑↑ 从当前目录上溯两级到 common
| 路径 | 说明 |
|---|---|
./ |
当前目录 |
../ |
上一级目录 |
../../ |
上两级目录 |
common/components/ |
通用组件目录 |
common/model/ |
数据模型目录 |
common/constants/ |
常量目录 |
五、@Component 中的状态管理
5.1 @State 装饰器
@State 标记的变量发生变化时,组件会自动重新渲染:
typescript
@Component
export struct HomeTab {
@State isGridView: boolean = true
@State diaryList: DiaryEntry[] = MOCK_DIARIES
build() {
Column() {
// 切换视图
Button()
.onClick(() => { this.isGridView = !this.isGridView })
// 根据 isGridView 状态渲染不同布局
if (this.isGridView) {
Grid() { ... } // 双栏网格
} else {
List() { ... } // 单栏列表
}
}
}
}
5.2 @Link 装饰器
@Link 实现父子组件之间的双向绑定:
typescript
// 父组件
@Component
export struct WriteDiaryPage {
@State selectedMood: number = 3
@State selectedWeather: number = 0
build() {
MoodSelector({
selectedMood: this.selectedMood, // 传入 @State
selectedWeather: this.selectedWeather // 传入 @State
})
}
}
// 子组件
@Component
export struct MoodSelector {
@Link selectedMood: number // @Link 双向绑定
@Link selectedWeather: number // @Link 双向绑定
build() {
Column() {
// 修改 @Link 变量会同步到父组件的 @State
Text('😊')
.onClick(() => { this.selectedMood = 4 })
}
}
}
5.3 @State 与 @Link 的对比
| 装饰器 | 数据流向 | 声明位置 | 修改权限 |
|---|---|---|---|
@State |
内部状态 | 组件内部 | 仅本组件可修改 |
@Prop |
父→子(单向) | 子组件 | 子组件可修改,但不会同步回父组件 |
@Link |
双向绑定 | 子组件 | 子组件修改会同步回父组件 |
5.4 状态管理的最佳实践
typescript
// 1. 状态尽量定义在需要修改的组件中
@State currentIndex: number = 0 // Index 中定义,Index 修改
// 2. 子组件通过 @Link 获取修改能力
@Link selectedMood: number // MoodSelector 中修改,同步到父组件
// 3. 纯展示数据通过普通属性传递
content: string = '' // DiaryCard 中仅展示,不修改
六、组件设计模式
6.1 容器组件模式
容器组件负责布局和状态管理,子组件负责具体展示:
typescript
// Index.ets --- 容器组件
@Entry
@Component
struct Index {
@State currentIndex: number = 0
build() {
Tabs() {
TabContent() { HomeTab() } // 子组件
TabContent() { CalendarTab() } // 子组件
TabContent() { ReviewTab() } // 子组件
TabContent() { MineTab() } // 子组件
}
}
}
6.2 展示组件模式
展示组件专注于 UI 呈现,不管理业务状态:
typescript
// DiaryCard.ets --- 纯展示组件
@Component
export struct DiaryCard {
// 所有属性由父组件传入
moodColor: MoodColor = 'default'
timeStr: string = ''
content: string = ''
tags: string[] = []
isPinned: boolean = false
hasImage: boolean = false
moodEmoji: string = ''
onTap: () => void = () => {}
build() {
// 仅 UI 展示,不包含业务逻辑
}
}
6.3 组件粒度控制
| 粒度 | 特点 | 适用场景 | 示例 |
|---|---|---|---|
| 粗粒度 | 代码集中,耦合度高 | 简单页面 | FaqPage |
| 中粒度 | 适度拆分,可复用 | 通用功能 | DiaryCard |
| 细粒度 | 高度复用,代码分散 | 多处使用 | MoodSelector |
七、组件生命周期详解
7.1 页面组件的完整生命周期
typescript
@Entry
@Component
struct ExamplePage {
// 1. 构造函数
constructor() {
console.info('constructor')
}
// 2. 状态初始化
@State count: number = 0
// 3. aboutToAppear(页面即将显示)
aboutToAppear() {
console.info('aboutToAppear')
}
// 4. build(构建 UI)
build() {
Column() {
Text(`${this.count}`)
}
.onClick(() => { this.count++ })
}
// 5. onPageShow(页面已显示)
onPageShow() {
console.info('onPageShow')
}
// 6. onPageHide(页面隐藏)
onPageHide() {
console.info('onPageHide')
}
// 7. aboutToDisappear(页面即将销毁)
aboutToDisappear() {
console.info('aboutToDisappear')
}
}
7.2 非页面组件的生命周期
非 @Entry 组件只有 aboutToAppear 和 aboutToDisappear:
typescript
@Component
export struct DiaryCard {
aboutToAppear() {
// 组件即将显示
}
aboutToDisappear() {
// 组件即将销毁
}
}
7.3 生命周期使用场景
| 生命周期回调 | 典型用途 |
|---|---|
aboutToAppear |
获取路由参数、初始化数据、发起网络请求 |
onPageShow |
每次页面显示时刷新数据 |
onPageHide |
暂停动画、保存草稿、释放临时资源 |
aboutToDisappear |
清理定时器、取消订阅、释放内存 |
八、常见问题与最佳实践
8.1 组件未注册
问题:使用 @Component 装饰的组件在页面中不显示。
原因 :组件未使用 export 导出,或在父组件中未正确 import。
解决方案:
typescript
// 1. 导出组件
@Component
export struct MyComponent { ... }
// 2. 导入组件
import { MyComponent } from './path/to/MyComponent'
8.2 build() 方法缺少根节点
问题:编译报错,提示 build() 必须包含一个根节点。
解决方案:使用容器组件包裹多个子元素:
typescript
build() {
// 错误:多个子组件没有根节点
// Text('Hello')
// Text('World')
// 正确:使用 Column 作为根节点
Column() {
Text('Hello')
Text('World')
}
}
8.3 @State 更新不触发 UI 刷新
问题:修改了 @State 变量,但 UI 没有更新。
原因:直接修改了数组或对象的内部属性,未触发引用变化。
解决方案:创建新的数组或对象引用:
typescript
// 错误:直接修改数组内部
this.tags.splice(idx, 1) // UI 不会刷新
// 正确:创建新数组
this.tags = [...this.tags] // 触发 UI 刷新
总结
本文详细讲解了 ArkUI 中 @Entry 和 @Component 两个核心装饰器的完整用法:
- @Component 基础:组件声明、build() 方法、导出规则
- @Entry 装饰器:页面入口注册、生命周期回调
- 组件导入导出:export 和 import 的使用方法
- 状态管理:@State、@Link、@Prop 的对比和使用
- 组件设计模式:容器组件、展示组件、粒度控制
- 生命周期:页面组件和普通组件的生命周期差异
- 最佳实践:常见问题的排查和解决方案
至此,第一系列"应用架构与导航"的 10 篇文章全部完成 !从下一篇文章开始,我们将进入 第二系列:闪屏与引导页,深入讲解 Canvas 2D 绘制、LinearGradient 渐变、Swiper 轮播等内容,敬请期待。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源: