大家好,我是展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
- 前言
- ArkTS 与 TS 的差异
- 声明式 UI 的基本写法
- 组件结构
- 常用布局与属性
- 状态管理:@State 与 @Prop
- @State:组件内部状态
- @Prop:父到子的单向传递
- @Link:父子双向同步
- @Watch:监听状态变化
- 总结:何时用哪种装饰器
- 总结
前言
HarmonyOS 应用 UI 使用 ArkTS 编写,语法上类似 TypeScript,但增加了声明式 UI 和状态管理能力。从传统命令式「先取控件再 set 属性」转向「描述 UI 与数据关系、由框架驱动更新」,是很多开发者需要适应的变化。
本文只讲 ArkTS 中与声明式 UI、状态管理相关的核心概念和关键代码,不展开完整页面示例,便于快速建立心智模型。
ArkTS 与 TS 的差异
ArkTS 是 TypeScript 的扩展,在 TS 基础上做了约束和增强:
- 支持:类型标注、接口、类、模块化
- 限制:不支持
any、不支持动态改变对象原型等,以保证运行时可预测性 - 扩展:
@Component、@State、@Link等装饰器,以及声明式 UI 组件
因此,写 ArkTS 时仍然是在写「强类型的 TS」,只是多了一套 UI 与状态绑定的规则。
声明式 UI 的基本写法
在 ArkTS 中,页面由自定义组件组成,每个组件是一个用@Component装饰的 struct,其build()方法返回一棵 UI 树。
组件结构
@Componentstruct HelloPage{build(){Column(){Text('Hello ArkTS').fontSize(24).fontColor('#333333')}.width('100%').height('100%').justifyContent(FlexAlign.Center)}}要点:
@Component表示这是一个可复用的 UI 组件build()必须返回一个根节点(这里是Column),且只能有一个根节点- 子组件通过链式调用配置属性,如
.fontSize(24)、.fontColor('#333333')
这种写法是「声明式」:我们只描述「有什么组件、长什么样」,不写「先找到哪个 Text 再 setText」。
常用布局与属性
布局常用Column(纵向)、Row(横向)、Stack(堆叠)、Flex等,用法与 Flutter/SwiftUI 类似。例如 Row 水平排列:
Row(){Text('左')Text('右')}.width('100%').justifyContent(FlexAlign.SpaceBetween)样式类属性(宽高、边距、对齐等)一般写在容器上,字体、颜色等写在具体组件上,保持「布局归布局、外观归外观」即可。
状态管理:@State 与 @Prop
声明式 UI 的另一个核心是「数据驱动」:数据变了,依赖它的 UI 自动更新。ArkTS 用多种装饰器区分「数据归属」和「更新范围」。
@State:组件内部状态
@State表示状态归当前组件所有,当其值变化时,会触发该组件build()重新执行,从而更新 UI。
@Entry@Componentstruct CounterPage{@Statecount:number=0build(){Column(){Text(`点击次数:${this.count}`).fontSize(20)Button('+1').onClick(()=>{this.count++})}}}要点:
@State只能用在当前组件内,子组件不能直接改父组件的@State,只能通过参数或回调- 修改
this.count后,框架会重新执行build(),Text会显示新的count - 若在
@State上做复杂对象修改,应保证「引用不变、内部字段变化」或「整体替换」,以便框架正确检测变更
@Prop:父到子的单向传递
@Prop表示该字段由父组件传入,在子组件内是只读的;父组件对应数据变化时,子组件会收到新值并刷新。
@Componentstruct ChildCount{@Propnum:numberbuild(){Text(`子组件收到的值:${this.num}`)}}@Entry@Componentstruct ParentPage{@Statevalue:number=0build(){Column(){ChildCount({num:this.value})Button('父组件 +1').onClick(()=>{this.value++})}}}父组件里this.value变化 → 传给ChildCount的num更新 → 子组件用@Prop num接收并重新渲染。子组件内部不能写this.num = xxx,否则违反单向数据流。
@Link:父子双向同步
@Link表示「和父组件某个状态双向绑定」:父改子能看到,子改父也能看到。实现上一般是父传一个「引用类型」或配合$语法。
@Componentstruct DoubleBinding{@Linkvalue:numberbuild(){Row(){Text(`${this.value}`)Button('子 -1').onClick(()=>{this.value--})}}}@Entry@Componentstruct ParentPage{@Statevalue:number=10build(){Column(){DoubleBinding({value:$value})// $ 表示双向绑定Button('父 +1').onClick(()=>{this.value++})}}}父组件用$value把@State value以引用形式传给子组件,子组件通过@Link value修改的也是同一份数据,因此两边始终一致。
@Watch:监听状态变化
若需要在某个状态变化时执行逻辑(例如打点、校验),可用@Watch在装饰器上声明回调:
@Entry@Componentstruct WatchDemo{@State@Watch('onCountChange')count:number=0onCountChange(){console.info(`count 变为:${this.count}`)}build(){Column(){Text(`${this.count}`)Button('+1').onClick(()=>this.count++)}}}@Watch('onCountChange')表示每当count变化就会调用onCountChange()。注意回调里拿到的已经是更新后的值,适合做副作用,不要在这里再改同一个状态导致循环更新。
总结:何时用哪种装饰器
| 装饰器 | 归属 | 方向 | 典型用途 |
|---|---|---|---|
| @State | 当前组件 | - | 页面内部计数、表单输入、开关等 |
| @Prop | 父 → 子 | 单向 | 展示父组件下发的数据,只读 |
| @Link | 父 ↔ 子 | 双向 | 需要子组件直接改父组件状态的场景 |
| @Watch | - | - | 状态变化时执行副作用(日志、校验等) |
实际开发中,优先用@State+@Prop把数据从父传到子,只在确实需要「子改父」时再用@Link,结构会更清晰。
总结
ArkTS 的声明式 UI 围绕「组件 + 状态」:@Component的build()描述界面,@State/@Prop/@Link描述数据从哪里来、谁可以改。掌握「数据驱动视图」「单向数据流」和「双向绑定的使用场景」,就能写出结构清晰、易于维护的 HarmonyOS 页面。后续可以在此基础上再学@ObjectLink、@Observed等进阶状态能力。