
推荐大家体验用AI编程,人工智能学习小站如下,已整理好相应高质量资源:
前言 -- 人工智能学习网
适用版本:HarmonyOS 5.0.0+ / API 12+
难度:中级
预计阅读时间:30 分钟
写在前面
ArkUI 的声明式 UI 开发范式里,UI 是程序状态的运行结果------状态变了,UI 自动刷新。这套机制的核心就是装饰器。
ArkUI 的装饰器体系分为三大类:
- V1 状态管理装饰器 (15 个):经典的
@Component/@State/@Prop/@Link等 - V2 状态管理装饰器 (14 个):新一代的
@ComponentV2/@Local/@Param/@Computed等 - 通用 UI 装饰器 (9 个):与状态管理无关的 UI 辅助装饰器,如
@Entry/@Builder/@Extend等
这篇笔记会把三类装饰器逐一拆解,每个装饰器配一段最小可运行代码,让你看完就知道怎么用。
一、V1 状态管理装饰器
V1 是 ArkUI 最早的状态管理体系,至今仍是大多数项目的主力。它的核心思路是:通过装饰器声明变量的"同步方向"(单向/双向/跨层级),框架自动处理 UI 刷新。


1.1 @Component:创建自定义组件
typescript
@Component
struct MyComponent {
build() {
Text('我是一个自定义组件')
}
}
@Component 装饰的 struct 就是一个可复用的自定义组件。它必须实现 build() 方法,返回 UI 描述。
1.2 @State:基础状态变量
typescript
@Component
struct Counter {
@State count: number = 0;
build() {
Column() {
Text(`计数:${this.count}`)
Button('+1').onClick(() => { this.count++; })
}
}
}
@State 装饰的变量变化时,当前组件中依赖该变量的 UI 会自动刷新。这是最基础的状态装饰器。
1.3 @Prop:父→子单向同步
typescript
@Component
struct Child {
@Prop message: string; // 接收父组件传入,子组件修改不影响父组件
build() {
Column() {
Text(`子组件:${this.message}`)
Button('修改').onClick(() => { this.message = '子组件改的'; })
}
}
}
@Entry
@Component
struct Parent {
@State msg: string = '来自父组件';
build() {
Column() {
Text(`父组件:${this.msg}`)
Child({ message: this.msg })
}
}
}
@Prop 建立父到子的单向 数据流。父组件 @State 变化时,子组件的 @Prop 会同步更新;但子组件修改 @Prop 不会回传给父组件。
1.4 @Link:父↔子双向同步
typescript
@Component
struct Child {
@Link value: number; // 双向同步,子组件修改会同步回父组件
build() {
Column() {
Text(`子组件:${this.value}`)
Button('+10').onClick(() => { this.value += 10; })
}
}
}
@Entry
@Component
struct Parent {
@State val: number = 100;
build() {
Column() {
Text(`父组件:${this.val}`)
Child({ value: $val }) // 注意:@Link 用 $ 语法传递引用
}
}
}
@Link 建立父到子的双向 数据流。父组件用 $变量名 语法传递引用。子组件修改 @Link 变量,父组件的 @State 同步变化。
1.5 @Observed + @ObjectLink:嵌套类对象观察
typescript
@Observed
class UserInfo {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
@Component
struct UserCard {
@ObjectLink user: UserInfo;
build() {
Column() {
Text(`姓名:${this.user.name}`)
Text(`年龄:${this.user.age}`)
Button('年龄+1').onClick(() => { this.user.age++; })
}
}
}
@Observed 标记一个类为"可观察的",@ObjectLink 在子组件中接收该类的实例。当实例的属性变化时,子组件的 UI 会自动刷新。这解决了 @State 只能观察第一层赋值、不能观察嵌套属性变化的问题。
1.6 @Provide + @Consume:跨层级双向同步
typescript
@Entry
@Component
struct GrandParent {
@Provide themeColor: string = '#007DFF';
build() {
Column() {
Text('祖先组件')
MiddleComponent() // 中间组件不需要传递 themeColor
}
}
}
@Component
struct MiddleComponent {
build() {
Column() {
ChildComponent()
}
}
}
@Component
struct ChildComponent {
@Consume themeColor: string; // 直接消费,不需要逐层传递
build() {
Text('后代组件').fontColor(this.themeColor)
}
}
@Provide 在祖先组件中声明数据,@Consume 在任意后代组件中直接消费。不需要通过中间组件逐层传递 @Prop / @Link。修改任意一端,另一端同步变化。
1.7 @Watch:状态变量变化监听
typescript
@Component
struct Counter {
@State count: number = 0;
@Watch('onCountChanged')
trigger: number = 0;
onCountChanged(): void {
console.info(`count 变化为:${this.count}`);
}
build() {
Button('+1').onClick(() => {
this.count++;
this.trigger++; // 触发 @Watch 回调
})
}
}
@Watch 装饰一个变量,当该变量变化时,调用指定的回调函数。适合在状态变化时执行日志、统计等副作用逻辑。
1.8 @StorageLink / @StorageProp:AppStorage 同步
typescript
// 全局存储
AppStorage.setOrCreate('token', 'abc123');
@Entry
@Component
struct MyPage {
@StorageLink('token') token: string = ''; // 双向同步
@StorageProp('appName') appName: string = ''; // 单向同步(只读)
build() {
Column() {
Text(`Token:${this.token}`)
Button('修改 Token').onClick(() => {
this.token = 'newToken456'; // 同步回 AppStorage
})
}
}
}
@StorageLink 与 AppStorage 中的数据建立双向同步,@StorageProp 建立单向同步(只读)。适合管理全局共享的状态,如登录信息、应用配置。
1.9 @LocalStorageLink / @LocalStorageProp:LocalStorage 同步
与 @StorageLink / @StorageProp 用法类似,区别在于 LocalStorage 是页面级或 Ability 级的局部存储,作用域比 AppStorage 小。
1.10 @Track:类对象属性级更新
typescript
@Observed
class UserModel {
@Track name: string; // 标记为精确追踪
@Track age: number;
untracked: string; // 未标记,变化时不触发 UI 刷新
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
@Track 配合 @Observed 使用,标记哪些属性变化时应该触发 UI 更新。未标记的属性变化不会导致 UI 刷新,可以减少不必要的渲染。
1.11 @Reusable:标记组件可复用
typescript
@Reusable
@Component
struct ListItem {
@State title: string = '';
build() {
Text(this.title).fontSize(16)
}
}
@Reusable 标记的组件在列表滚动时可以被复用,减少组件创建和销毁的开销。适合长列表场景。
二、V2 状态管理装饰器
V2 是 ArkUI 推出的新一代状态管理体系,设计上更严格、更清晰,引入了计算属性、事件输出、精确监听等 V1 缺失的能力。V1 和 V2 可以混用,但同一个组件内不能混用。
2.1 @ComponentV2:创建自定义组件(V2)
typescript
@ComponentV2
struct MyComponent {
build() {
Text('V2 组件')
}
}
V2 组件用 @ComponentV2 替代 @Component。V2 组件内部只能使用 V2 装饰器(@Local / @Param 等),不能使用 @State / @Prop 等 V1 装饰器。
2.2 @Local:组件内部状态
typescript
@ComponentV2
struct Counter {
@Local count: number = 0; // 仅组件内部可修改
build() {
Column() {
Text(`计数:${this.count}`)
Button('+1').onClick(() => { this.count++; })
}
}
}
@Local 是 V2 版本的 @State,表示组件内部状态。与 @State 的区别在于:@Local 变量只能在当前组件内修改,不能从外部直接赋值,语义更严格。
2.3 @Param:组件外部输入
typescript
@ComponentV2
struct Child {
@Param inputValue: number = 0; // 从父组件接收,实时同步
build() {
Text(`接收:${this.inputValue}`)
}
}
@ComponentV2
struct Parent {
@Local value: number = 42;
build() {
Child({ inputValue: this.value })
}
}
@Param 是 V2 版本的 @Prop,从父组件接收数据。父组件的状态变化时,@Param 自动同步。
2.4 @Once:初始化同步一次
typescript
@ComponentV2
struct Child {
@Param @Once snapshot: number = 0; // 仅首次接收父组件的值,之后不同步
build() {
Text(`快照:${this.snapshot}`)
}
}
@Once 修饰的 @Param 变量只在初始化时同步一次父组件的值。之后父组件的值变化,子组件不会跟着变。适合"只需初始化配置"的场景。
2.5 @Event:规范组件输出
typescript
@ComponentV2
struct Child {
@Param value: number = 0;
@Event onReset: () => void = () => {};
build() {
Button('重置').onClick(() => { this.onReset(); })
}
}
@ComponentV2
struct Parent {
@Local count: number = 0;
build() {
Column() {
Text(`计数:${this.count}`)
Child({ value: this.count, onReset: () => { this.count = 0; } })
}
}
}
@Event 声明组件的输出回调。子组件通过调用 @Event 函数通知父组件。在 V2 中,@Param + @Event 替代了 V1 的 @Link 双向同步------父组件传数据用 @Param,子组件通知父组件用 @Event,数据流向更清晰。
2.6 @Provider + @Consumer:跨层级双向同步
typescript
@ComponentV2
struct Ancestor {
@Provider() themeColor: ResourceColor = '#007DFF';
build() {
Column() {
Text('祖先')
Descendant()
}
}
}
@ComponentV2
struct Descendant {
@Consumer() themeColor: ResourceColor = '#333333';
build() {
Column() {
Text('后代').fontColor(this.themeColor)
Button('改色').onClick(() => { this.themeColor = '#FF4444'; })
}
}
}
V2 版本的 @Provide / @Consume。@Provider() 和 @Consumer() 需要带括号调用。双向同步,任意一端修改另一端跟随。
2.7 @Monitor:状态变量修改异步监听
typescript
@ComponentV2
struct MyComponent {
@Local count: number = 0;
@Monitor('count')
onCountChange(monitor: IMonitor): void {
monitor.dirty.forEach(path => {
console.info(`@Monitor: ${path} 变化了`);
});
}
build() {
Button('+1').onClick(() => { this.count++; })
}
}
@Monitor 是 V2 版本的 @Watch,但更强大:可以监听多个变量、获取变化路径、获取变化前后的值。回调是异步执行的。
2.8 @SyncMonitor:状态变量修改同步监听
typescript
@ComponentV2
struct MyComponent {
@Local value: number = 0;
@SyncMonitor('value')
onValueChange(monitor: IMonitor): void {
// 同步执行,在 UI 刷新之前
console.info(`@SyncMonitor: value 变化为 ${this.value}`);
}
build() {
Button('+1').onClick(() => { this.value++; })
}
}
@SyncMonitor 与 @Monitor 功能相同,但回调是同步执行的,在 UI 刷新之前触发。适合需要在 UI 更新前执行前置逻辑的场景。
2.9 @Computed:计算属性
typescript
@ComponentV2
struct Cart {
@Local price: number = 4999;
@Local quantity: number = 1;
@Computed
get total(): number {
return this.price * this.quantity;
}
build() {
Column() {
Text(`总价:¥${this.total}`)
Button('数量+1').onClick(() => { this.quantity++; })
}
}
}
@Computed 声明一个计算属性。当依赖的 @Local / @Trace 变量变化时,计算属性自动重新求值。计算属性有缓存------依赖不变时不会重复计算。
2.10 @ObservedV2 + @Trace:可观察类与属性标记
typescript
@ObservedV2
class CartItem {
name: string;
@Trace quantity: number; // 标记为可观察
@Trace price: number;
untracked: string; // 未标记,变化不触发 UI
constructor(name: string, price: number, quantity: number) {
this.name = name;
this.price = price;
this.quantity = quantity;
}
}
@ObservedV2 标记类为可观察(V2 版本的 @Observed)。@Trace 标记类中哪些属性变化时应该触发 UI 刷新。与 V1 的 @Observed 相比,V2 是属性级精确追踪 ------只有标记了 @Trace 的属性变化才会触发更新,V1 是整个对象替换才触发。
2.11 @Type:标记类属性的类型
typescript
@ObservedV2
class Order {
@Type(CartItem)
item: CartItem; // 标记属性的类型,用于序列化/反序列化
@Trace status: string;
constructor(item: CartItem, status: string) {
this.item = item;
this.status = status;
}
}
@Type 标记类属性的具体类型,主要用于持久化(如 Preferences / RDB 存储)时的序列化和反序列化。框架需要知道属性的类型才能正确还原对象。
2.12 @ReusableV2:标记组件可复用(V2)
typescript
@ComponentV2
@ReusableV2
struct ListItem {
@Param title: string = '';
build() {
Text(this.title).fontSize(16)
}
}
V2 版本的 @Reusable,标记组件可复用。
三、通用 UI 装饰器
通用装饰器与状态管理无关,用于 UI 结构封装、样式复用、参数校验等。
3.1 @Entry:标记页面入口
typescript
@Entry
@Component
struct MyPage {
build() {
Text('这是一个页面')
}
}
@Entry 标记一个自定义组件为页面入口。每个页面(路由对应的 .ets 文件)只能有一个 @Entry。
3.2 @Builder:自定义构建函数
typescript
@Entry
@Component
struct MyPage {
@Builder
CardHeader(title: string, subtitle: string) {
Column() {
Text(title).fontSize(20).fontWeight(FontWeight.Bold)
Text(subtitle).fontSize(14).fontColor('#999')
}
}
build() {
Column() {
this.CardHeader('标题一', '副标题一')
this.CardHeader('标题二', '副标题二')
}
}
}
@Builder 将一段 UI 描述封装为方法,可以接收参数,在多处复用。类似 Vue 的"渲染函数"或 React 的"自定义 Hook 返回 JSX"。
3.3 @LocalBuilder:维持组件关系
typescript
@Component
struct MyPage {
@State count: number = 0;
@LocalBuilder
MyContent() {
// this.count 可以正确访问当前组件的状态
Text(`计数:${this.count}`)
}
build() {
Column() {
this.MyContent()
}
}
}
@LocalBuilder 与 @Builder 功能类似,区别在于 this 的指向。@Builder 中 this 指向可能丢失,@LocalBuilder 始终指向当前组件,可以安全访问组件的状态变量和方法。
3.4 @BuilderParam:引用 @Builder 函数
typescript
@Component
struct CustomContainer {
@BuilderParam header: () => void;
@BuilderParam content: () => void;
build() {
Column() {
this.header()
this.content()
}
}
}
@Entry
@Component
struct MyPage {
@Builder
MyHeader() { Text('标题').fontSize(18) }
@Builder
MyContent() { Text('内容').fontSize(14) }
build() {
CustomContainer({
header: this.MyHeader,
content: this.MyContent
})
}
}
@BuilderParam 在自定义组件中声明一个"槽位",调用方可以传入任意 @Builder 函数填充。类似 Vue 的具名插槽。
3.5 @Styles:定义组件重用样式
typescript
@Styles
function cardBoxStyle() {
.width('100%')
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(12)
.shadow({ radius: 4, color: 'rgba(0,0,0,0.08)', offsetY: 2 })
}
@Entry
@Component
struct MyPage {
build() {
Column() {
Text('卡片 A')
}.cardBoxStyle()
Column() {
Text('卡片 B')
}.cardBoxStyle()
}
}
@Styles 封装一组通用属性(width、height、padding、margin、backgroundColor 等所有组件都有的属性),不能包含组件特有属性(如 Text 的 fontSize)。
3.6 @Extend:定义扩展组件样式
typescript
@Extend(Text)
function titleStyle() {
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#333333')
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
}
@Entry
@Component
struct MyPage {
build() {
Column() {
Text('标题一').titleStyle()
Text('标题二').titleStyle()
}
}
}
@Extend 为特定系统组件 (如 Text、Button)定义一组扩展样式,可以包含组件特有属性。比 @Styles 更灵活,但只能用于指定的组件类型。
3.7 @AnimatableExtend:定义可动画属性
typescript
@AnimatableExtend(Text)
function animatableFontSize(size: number) {
.fontSize(size)
}
@Component
struct AnimatableExample {
@State fontSize: number = 16;
build() {
Column() {
Text('动画文字')
.animatableFontSize(this.fontSize)
.onClick(() => {
this.fontSize = this.fontSize === 16 ? 32 : 16;
})
}
.animation({ duration: 400, curve: Curve.EaseInOut })
}
}
@AnimatableExtend 定义一个可参与动画过渡的自定义属性。当属性值变化时,框架会按动画曲线平滑过渡,而不是瞬间跳变。
3.8 @Require:校验构造传参
typescript
@Component
struct RequiredChild {
@Require @Prop requiredProp: string; // 必传
@Prop optionalProp: string = '默认值'; // 可选
build() {
Column() {
Text(`必传:${this.requiredProp}`)
Text(`可选:${this.optionalProp}`)
}
}
}
@Entry
@Component
struct MyPage {
build() {
// RequiredChild() // 编译报错:缺少 requiredProp
RequiredChild({ requiredProp: '必须传入' }) // 正确
}
}
@Require 标记一个 @Prop / @Link 为必传参数。如果调用方未传,编译阶段就会报错,而不是运行时出现 undefined。
3.9 @Env:环境变量
typescript
@Entry
@Component
struct MyPage {
@Env('sys.language') sysLanguage: string = 'zh';
build() {
Column() {
Text(`系统语言:${this.sysLanguage}`)
}
}
}
@Env 读取系统环境变量(如语言、颜色模式、屏幕方向等)。当系统环境变化时(如用户切换语言),@Env 变量自动更新并触发 UI 刷新。
四、V1 vs V2 对比速查
| V1 装饰器 | V2 装饰器 | 用途 | 关键差异 |
|---|---|---|---|
| @Component | @ComponentV2 | 创建组件 | V2 内部只能用 V2 装饰器 |
| @State | @Local | 内部状态 | @Local 语义更严格,仅内部可改 |
| @Prop | @Param | 外部输入 | @Param 不支持默认值修改 |
| @Link | @Param + @Event | 双向同步 | V2 拆分为输入和事件,方向更清晰 |
| @Provide | @Provider() | 提供数据 | V2 需带括号 |
| @Consume | @Consumer() | 消费数据 | V2 需带括号 |
| @Watch | @Monitor | 变化监听 | @Monitor 异步,支持多变量和路径 |
| - | @SyncMonitor | 同步监听 | V2 新增,UI 刷新前同步触发 |
| - | @Computed | 计算属性 | V2 新增,带缓存 |
| @Observed | @ObservedV2 | 可观察类 | V2 配合 @Trace 实现属性级追踪 |
| @ObjectLink | @Trace | 属性观察 | V2 是属性级标记,更精确 |
| - | @Once | 仅同步一次 | V2 新增 |
| - | @Type | 类型标记 | V2 新增,用于序列化 |
| @Reusable | @ReusableV2 | 组件复用 | V2 版本 |
五、工程结构
ArkTS装饰器总览/
├── pages/
│ ├── V1Decorators.ets # V1 状态管理装饰器完整示例
│ ├── V2Decorators.ets # V2 状态管理装饰器完整示例
│ └── CommonDecorators.ets # 通用 UI 装饰器完整示例
└── preview.html # 网页预览(4 个 Tab 页可交互)
在 DevEco Studio 中运行
- 新建 Empty Ability 工程
- 将三个
.ets文件放入entry/src/main/ets/pages/ - 在
resources/base/profile/main_pages.json中注册:
json
{
"src": [
"pages/V1Decorators",
"pages/V2Decorators",
"pages/CommonDecorators"
]
}
- 运行查看效果
网页预览
用浏览器打开 preview.html,底部有 4 个 Tab 页:V1 装饰器、V2 装饰器、通用装饰器、总览对比。每个页面都可以交互操作,观察状态变化的效果。
六、选择建议
新项目用 V1 还是 V2?
如果项目刚启动,建议直接用 V2。V2 的数据流更清晰(输入/输出分离)、计算属性减少冗余状态、属性级追踪减少不必要的渲染。
老项目要不要迁移?
不急着全部迁移。V1 和 V2 可以在同一项目中混用(不同组件之间)。建议在新增功能时尝试 V2,逐步替换核心组件。同一个组件内不能混用 V1 和 V2 装饰器。
通用装饰器用哪个版本?
@Builder / @Extend / @Styles 等通用装饰器与 V1/V2 无关,任何版本都可以使用。@Entry 是页面入口标记,也与版本无关。
七、常见问题
Q:@State 和 @Local 到底有什么区别?
A:功能上都能触发 UI 刷新。@Local 的语义是"仅组件内部可修改"------外部不能通过构造参数修改它。@State 没有这个限制。在 V2 组件中只能用 @Local。
Q:@Link 和 @Param + @Event 有什么区别?
A:@Link 是双向绑定,父和子都持有同一引用,任意一端修改另一端自动同步。@Param + @Event 把输入和输出拆开------父组件传数据用 @Param,子组件通知父组件用 @Event。后者的数据流更单向、更可预测。
Q:@Observed + @ObjectLink 和 @ObservedV2 + @Trace 有什么区别?
A:V1 的 @Observed 需要整个对象被替换或重新赋值才能触发更新,@ObjectLink 只是在子组件中建立观察。V2 的 @Trace 是属性级的------只修改某个标记了 @Trace 的属性就能触发更新,不需要替换整个对象。
Q:@Builder 和 @LocalBuilder 该用哪个?
A:如果 Builder 内部需要访问组件的 this(状态变量、方法),用 @LocalBuilder。如果只是纯粹的 UI 结构封装,不依赖组件状态,用 @Builder 即可。
总结
ArkUI 的装饰器体系虽然数量多(38 个),但核心逻辑只有三条线:
- 状态在哪里 →
@State/@Local(内部状态)、@Prop/@Param(外部输入)、@Provide/@Provider(跨层级提供) - 状态怎么变 →
@Link/@Param+@Event(双向)、@Watch/@Monitor(监听变化)、@Computed(计算派生) - 状态变了谁刷新 →
@Observed+@ObjectLink/@ObservedV2+@Trace(类对象属性级追踪)
通用装饰器则是 UI 层面的辅助工具------封装结构(@Builder)、复用样式(@Extend / @Styles)、校验参数(@Require)、读取环境(@Env)。
掌握这些,ArkTS 声明式 UI 的全貌就清晰了。
推荐大家体验用AI编程,人工智能学习小站如下,已整理好相应高质量资源:
前言 -- 人工智能学习网
推荐大家体验用AI编程,人工智能学习小站如下,已整理好相应高质量资源:
前言 -- 人工智能学习网
推荐大家体验用AI编程,人工智能学习小站如下,已整理好相应高质量资源:
前言 -- 人工智能学习网