ArkTS 组件编写实战练习深度解析
ArkTS 实战:声明式组件编写、状态驱动 UI 与常用布局练习。 偏手写,掌握后能现场写出规范的鸿蒙声明式组件,应对语法与工程落地考察。
ArkTS 组件编写实战练习深度解析
一句话概括
掌握 ArkTS 组件化的 6 大核心模式:状态封装、数据流设计、插槽机制、条件渲染、列表优化、手势交互——通过评分组件、下拉刷新、轮播图、标签组、折叠面板、拖拽排序逐个击破。
核心知识点
1. 组件封装——状态隔离 + 接口清晰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// StarRating:内聚评分逻辑,对外只暴露 rating 和回调
@Component
struct StarRating {
@Link rating: number;
@Prop total: number = 5;
@Prop size: number = 28;
build() {
Row() {
ForEach(Array.from({ length: this.total }), (_: undefined, i: number) => {
Image(i < this.rating ? $r('app.media.star_filled') : $r('app.media.star_empty'))
.width(this.size).height(this.size)
.onClick(() => this.rating = i + 1) // 点击第 i 颗星 → 评分 = i+1
})
}
}
}
// 调用:StarRating({ rating: $score, total: 5 })
2. 下拉刷新——@State + Refresh 组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Component
struct RefreshableList {
@State items: string[] = [];
@State isRefreshing: boolean = false;
aboutToAppear() { this.fetchData(); }
async fetchData() {
// 模拟网络请求
setTimeout(() => this.items = ['项目A', '项目B', '项目C'], 1000);
}
build() {
Refresh({ refreshing: $$this.isRefreshing }) { // $$ 双向绑定
List() {
ForEach(this.items, (item: string) => {
ListItem() { Text(item).padding(12) }
})
}
}
.onRefreshing(() => {
setTimeout(() => { this.fetchData(); this.isRefreshing = false; }, 1500);
})
}
}
3. 图片轮播——Swiper + @State 索引
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Component
struct ImageCarousel {
@Prop images: string[] = [];
@State currentIndex: number = 0;
build() {
Column() {
Swiper() {
ForEach(this.images, (img: string) => {
Image(img).width('100%').height(200).objectFit(ImageFit.Cover)
})
}
.indicator(Indicator.dot().selectedColor('#007AFF'))
.onChange((index: number) => this.currentIndex = index)
.autoPlay(true).loop(true)
}
}
}
4. 多选标签组——Set + 事件回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
struct TagGroup {
@Link selectedIds: Set<number>;
@Prop tags: TagItem[] = [];
build() {
Flex({ wrap: FlexWrap.Wrap }) {
ForEach(this.tags, (tag: TagItem) => {
Text(tag.label)
.fontColor(this.selectedIds.has(tag.id) ? Color.White : '#333')
.backgroundColor(this.selectedIds.has(tag.id) ? '#007AFF' : '#F0F0F0')
.borderRadius(16).padding({ left: 12, right: 12, top: 6, bottom: 6 })
.onClick(() => {
if (this.selectedIds.has(tag.id)) this.selectedIds.delete(tag.id);
else this.selectedIds.add(tag.id);
})
})
}
}
}
5. 折叠面板——if/else 条件渲染
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Component
struct CollapsePanel {
@State expanded: boolean = false;
@Prop title: string = '';
build() {
Column() {
Row() {
Text(this.title).fontSize(16).layoutWeight(1)
Image(this.expanded ? $r('app.media.arrow_up') : $r('app.media.arrow_down'))
.width(16).height(16)
}
.padding(12).onClick(() => this.expanded = !this.expanded)
if (this.expanded) { // 条件渲染保证隐藏时不占布局空间
this.content()
}
}
}
@Builder content() { // @Builder 复用 UI 片段
Column() { Text('折叠内容区域...').padding(12) }
}
}
其实你每天都在用
- 评分组件:商品详情页、用户反馈、应用评价——几乎每个 toC 应用必备
- 下拉刷新:任何列表页——Feed 流、消息列表、搜索结果
- 标签组:商品筛选、兴趣选择、过滤器——电商和社交的标配
- 折叠面板:FAQ 页面、设置页、文档目录——信息密度控制利器
- @Builder:页面中多处出现的相同 UI 片段,抽取为 @Builder 函数避免重复
常见误解(FAQ)
❌ 误区:「组件拆得越细越好」 过度拆分导致通信成本飙升。一个表单页拆出 20 个组件,每个都需要传参和回调——不如适度内聚。原则:有独立状态逻辑的才独立成组件。
❌ 误区:「if/else 和 visibility 效果一样」
if/else是真创建/销毁(走生命周期),visibility: Hidden只是不可见(组件还在树里)。需要清理资源的场景(如定时器、socket 连接)必须用 if/else。❌ 误区:「ForEach 和 List 搭配时不需要 key」 ForEach 的第三个参数就是 key 函数。不传 key 在列表增删时会导致组件错位(状态串到别的 item 上)。key 必须是稳定唯一的值(如 id),绝不能用 index。
❌ 误区:「所有数据都用 @State 就行」 @State 只能观测到第一层属性的赋值。嵌套对象内部变化需要 @Observed + @ObjectLink,这是面试中必问的”@State 观测深度”陷阱。
一句话总结
组件化的精髓不是”分得细”,而是”分得对”——每个组件只做一件事,数据从哪来、往哪去一清二楚。
本文由作者按照 CC BY 4.0 进行授权