Skip to content

ArkTS 装饰器

ArkTS 的核心:装饰器是声明式 UI 的基石,从 @Entry 到 @Builder,每种装饰器都有明确职责。


1. 装饰器概述

装饰器是 ArkTS 特有的语法特性,在编译期向类或成员注入元数据和额外行为。ArkTS 的装饰器分为两大类:

  • UI 组件装饰器:定义组件及其状态
  • 构建与样式装饰器:定义 UI 复用和样式扩展

装饰器分类总览

UI 组件装饰器
├── @Entry          - 页面入口
├── @Component      - 自定义组件
├── @CustomDialog   - 自定义弹窗
├── @Extend         - 组件样式扩展
└── @Styles         - 通用样式集合

构建与样式装饰器
├── @Builder        - UI 构建函数
├── @BuilderParam   - 父组件传递 UI 内容
├── @Styles         - 通用样式复用
└── @Extend         - 组件特有样式扩展

2. @Entry — 页面入口装饰器

2.1 基本用法

typescript
// 必须且只能有一个 @Entry
@Entry
@Component
struct Index {
    @State message: string = 'Hello'

    build() {
        Column() {
            Text(this.message)
                .fontSize(50)
                .fontWeight(FontWeight.Bold)
        }
        .width('100%')
        .height('100%')
    }
}

2.2 @Entry 规则

规则说明
数量每个页面文件只能有一个 @Entry
位置必须与 @Component 一起使用
作用标记该 struct 为页面入口,自动加入页面路由栈
导航@Entry 组件可以使用 Navigation 组件实现子页面导航

3. @Component — 自定义组件装饰器

3.1 基本用法

typescript
@Component
struct MyButton {
    @State text: string = 'Click Me'
    @State bgColor: Color = Color.Blue

    build() {
        Button(this.text)
            .backgroundColor(this.bgColor)
            .onClick(() => {
                this.bgColor = Color.Red  // 触发重新渲染
            })
    }
}

3.2 @Component 的规则

  • 必须使用 struct 定义,不能用 class
  • 必须有 build() 函数
  • 不可继承(与 class 的最大区别)
  • 不可用 new 创建(由框架管理实例)
  • 可以嵌套使用(组件中嵌套其他组件)
typescript
// 组件嵌套示例
@Component
struct Container {
    build() {
        Column() {
            MyButton()  // 子组件直接调用
            MyButton()
            MyButton()
        }
        .width('100%')
        .height('100%')
    }
}

4. @Builder — UI 构建函数装饰器

4.1 基本用法:提取可复用 UI 逻辑

typescript
@Component
struct ItemCard {
    @State title: string = '标题'
    @State content: string = '内容'

    // 用 @Builder 提取可复用的 UI 部分
    @Builder
    renderHeader() {
        Row() {
            Text(this.title)
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
            Blank()  // 填充剩余空间
            Text('详情 >')
                .fontSize(14)
                .fontColor(Color.Gray)
        }
        .width('100%')
    }

    build() {
        Column() {
            this.renderHeader()  // 调用 @Builder 函数
            Divider()
            Text(this.content)
                .fontSize(16)
                .margin({ left: 10, right: 10, top: 10, bottom: 10 })
        }
        .width('100%')
        .borderRadius(8)
        .backgroundColor(Color.White)
    }
}

4.2 @Builder 的传参方式

typescript
@Component
struct ListCard {
    @State items: string[] = ['item1', 'item2', 'item3']

    // 按引用传递:UI 随数据刷新
    @Builder
    renderItemWithRef(item: string) {
        Text(item)
            .fontSize(16)
    }

    // 按值传递:UI 不随数据刷新(性能更好,数据不可变)
    @Builder
    renderItemWithValue(value: string) {
        Text(value)
            .fontSize(16)
    }

    build() {
        Column() {
            // 调用 @Builder 传递数据
            ForEach(this.items, (item: string) => {
                this.renderItemWithRef(item)
                Divider()
            })
        }
    }
}

4.3 @Builder 与组件的区别

特性@Builder@Component
定义方式函数(方法)struct(结构体)
复用粒度UI 逻辑片段完整组件
参数传递函数参数@Prop/@Link
状态管理无状态@State 等
生命周期aboutToAppear 等
性能更轻量有一定开销

💡 面试考点@Builder 用于提取轻量级 UI 复用逻辑,性能优于创建完整的自定义组件。适用于简单的 UI 片段复用。


5. @BuilderParam — 父组件向子组件传递 UI

5.1 基本用法(类似 slot / render props)

typescript
// 子组件
@Component
struct Dialog {
    @BuilderParam contentBuilder: () => void

    build() {
        Column() {
            // 在弹窗内容区渲染父组件传入的 UI
            this.contentBuilder()
        }
        .width('90%')
        .borderRadius(12)
        .backgroundColor(Color.White)
    }
}

// 父组件
@Entry
@Component
struct Index {
    build() {
        Column() {
            Dialog() {
                () => {  // @BuilderParam 的传参格式
                    Text('这是弹窗内容')
                        .fontSize(18)
                    Button('关闭')
                        .onClick(() => {
                            // 关闭弹窗
                        })
                }
            }
        }
        .width('100%')
    }
}

5.2 带参数的 @BuilderParam

typescript
// 子组件
@Component
struct Card {
    @BuilderParam titleBuilder: () => void
    @BuilderParam bodyBuilder: (item: string) => void
    @State items: string[] = ['数据1', '数据2']

    build() {
        Column() {
            this.titleBuilder()
            ForEach(this.items, (item) => {
                this.bodyBuilder(item)
            })
        }
    }
}

// 父组件
Card() {
    () => Text('卡片标题').fontSize(20)
}
(content: string) => {
    Text(content).fontSize(16)
}

5.3 @BuilderParam 的应用场景

  • 弹窗/Dialog 的内容自定义
  • 卡片组件的标题/内容模板
  • 表单组件的自定义表单项
  • 列表项的自定义布局

💡 面试技巧:说 @BuilderParam 时,用类比的方式:"类似 Vue 的 slot(插槽)或 React 的 render props,让父组件向子组件传递一段 UI 结构。"


6. @Styles — 通用样式集合

6.1 基本用法

typescript
// 定义通用样式
@Styles function commonStyle() {
    .width('100%')
    .height(60)
    .borderRadius(8)
    .backgroundColor(Color.White)
    .padding(12)
}

@Styles function buttonStyle() {
    .width(120)
    .height(44)
    .borderRadius(22)
    .fontColor(Color.White)
    .fontWeight(FontWeight.Medium)
}

@Entry
@Component
struct Index {
    build() {
        Column() {
            // 应用通用样式
            Text('文本')
                .commonStyle()

            Button('按钮')
                .buttonStyle()
                .backgroundColor(Color.Blue)
        }
    }
}

6.2 @Styles 的限制

  • 只能使用通用属性(width/height/borderRadius 等),不能用组件特有属性
  • 不支持参数
  • 不支持多态(不同组件应用同一 @Styles 效果相同)

7. @Extend — 组件特有样式扩展

7.1 基本用法

typescript
// 针对特定组件(如 Text)的样式扩展
@Extend(Text) function textStyle(fontSize: number, color: Color = Color.Black) {
    .fontSize(fontSize)
    .fontColor(color)
    .fontWeight(FontWeight.Normal)
}

// 针对特定组件(如 Button)的样式扩展
@Extend(Button) function myButtonStyle() {
    .width('100%')
    .height(48)
    .borderRadius(24)
    .backgroundColor(Color.Blue)
    .fontColor(Color.White)
    .fontWeight(FontWeight.Medium)
}

@Entry
@Component
struct Index {
    build() {
        Column() {
            Text('大标题')
                .textStyle(28, Color.Blue)

            Text('小标题')
                .textStyle(16, Color.Gray)

            Button('点击')
                .myButtonStyle()
        }
    }
}

7.2 @Styles vs @Extend 对比

特性@Styles@Extend
作用范围通用属性(多组件)组件特有属性(特定组件)
支持参数❌ 不支持✅ 支持
多态✅ 可应用到不同组件❌ 仅限指定组件
示例width/height/borderRadiusfontSize/fontColor(仅 Text)

💡 记忆口诀

  • @Styles = 通用属性集合(.width, .height),多态
  • @Extend = 针对特定组件的私有属性扩展(如 Text 的 .fontColor),支持参数

8. @CustomDialog — 自定义弹窗装饰器

8.1 基本用法

typescript
@CustomDialog
struct MyDialog {
    controller: CustomDialogController
    @State title: string = '提示'
    @State content: string = '确定要删除吗?'

    build() {
        Column() {
            Text(this.title)
                .fontSize(20)
                .fontWeight(FontWeight.Bold)
                .margin({ bottom: 16 })

            Text(this.content)
                .fontSize(16)
                .margin({ bottom: 24 })

            Row() {
                Button('取消')
                    .width('48%')
                    .onClick(() => {
                        this.controller.close()
                    })
                Button('确定')
                    .width('48%')
                    .onClick(() => {
                        // 执行删除
                        this.controller.close()
                    })
            }
            .width('100%')
        }
        .width('80%')
        .padding(24)
    }
}

@Entry
@Component
struct Index {
    private dialogController: CustomDialogController = new CustomDialogController({
        builder: MyDialog({ controller: this.dialogController }),
        customPanelState: CustomPanelState.POPUP,
        alignment: DialogAlignment.Center,
        offset: { dx: 0, dy: 0 }
    })

    build() {
        Column() {
            Button('打开弹窗')
                .onClick(() => {
                    this.dialogController.open()
                })
        }
    }
}

9. 装饰器完整速查表

装饰器作用用在是否必选
@Entry页面入口struct每个页面一个
@Component自定义组件struct自定义组件必选
@State本地状态属性按需
@Prop单向数据流属性按需
@Link双向数据流属性按需
@BuilderUI 构建函数方法按需
@BuilderParam传递 UI 内容属性按需
@Styles通用样式函数按需
@Extend组件特有样式函数按需
@Observed深度监听class按需
@TraceV2 深度监听属性按需

10. 面试高频考点

Q1: @Builder 和 @BuilderParam 的区别?

回答

  • @Builder:提取组件内部的轻量级 UI 逻辑复用
  • @BuilderParam:让父组件向子组件传递一段 UI 结构(类似 slot/render props)

Q2: @Styles 和 @Extend 的区别?

回答

  • @Styles:通用属性集合(如 .width/.height),支持多组件复用
  • @Extend:针对特定组件(如 Text/Button)的私有属性扩展,支持参数

Q3: @Component 定义的组件可以继承吗?

回答:不可以。ArkTS 的 @Component struct 不支持继承,这是与 class 的最大区别。


🐱 小猫提示:装饰器是 ArkTS 面试的核心考点,尤其是 @Builder/@BuilderParam、@Styles/@Extend 这两对的区别。记住类比记忆法就稳了!