文章

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 进行授权