在这里插入图片描述
在这里插入图片描述

鸿蒙 ArkTS 自适应企业表单:用 onAreaChange 实现类似 Flutter LayoutBuilder 的响应式布局

一、项目背景与概述

在移动端与桌面端界限日益模糊的今天,企业级应用面临一个核心挑战:同一套代码如何在手机、平板、折叠屏乃至 PC 上都能提供优质的用户体验? 传统做法是为不同屏幕尺寸分别维护一套布局文件,但这样带来的维护成本是指数级增长的。

鸿蒙(HarmonyOS)作为面向全场景的分布式操作系统,天然就需要应对这种多设备、多形态的挑战。其原生开发语言 ArkTS(Ark TypeScript)提供了丰富的布局能力,但很多从 Flutter、React Native 或原生 Android/iOS 转过来的开发者,会习惯性地寻找类似 LayoutBuilderMediaQuerybreakpoints 这样的响应式工具。

本文将以一个 企业员工信息登记表单 为实战案例,详细讲解如何在鸿蒙 ArkTS 中利用 onAreaChange 回调实现类似 Flutter LayoutBuilder 的容器级响应式布局——当容器宽度大于 600vp 时自动切换为双列 Row 布局(大屏模式),小于 600vp 时切换为单列 Column 布局(小屏模式)。

这篇文章不仅会展示完整代码,更会深入剖析每个技术决策背后的思考过程,帮助读者建立起在鸿蒙生态中构建自适应界面的系统方法论。

二、鸿蒙 ArkTS 布局体系概述

2.1 什么是 ArkTS?

ArkTS 是鸿蒙原生应用开发的主力语言,基于 TypeScript 语法进行了扩展和约束。相比于标准 TypeScript,ArkTS 具有以下特点:

  • 强制类型安全:所有变量必须有明确的类型声明,不允许隐式 any
  • 装饰器驱动的响应式系统:通过 @State@Prop@Link@Provide@Consume 等装饰器管理组件的状态和通信
  • 声明式 UI 构建:与 Flutter、SwiftUI、Jetpack Compose 类似的声明式写法,UI 是状态的函数
  • 组件化架构:通过 @Component 定义可复用的 UI 组件,通过 @Builder 定义可复用的构建块
  • 严格的初始化规则:所有非 @State 属性必须提供编译时常量的默认值,@BuilderParam 必须关联一个默认的 @Builder 方法

2.2 布局容器

ArkTS 提供了三种核心布局容器:

容器 功能 对应 CSS/Flexbox
Column 垂直方向排列子组件 flex-direction: column
Row 水平方向排列子组件 flex-direction: row
Stack 层叠排列子组件(支持绝对定位) position: relative + 层叠

这三大容器配合 FlexAlign(主轴对齐)、HorizontalAlign(交叉轴对齐)、layoutWeight(权重分配)等属性,足以应对绝大多数布局场景。

2.3 响应式布局的途径

在 ArkTS 中,实现响应式布局主要有以下几种方式:

  1. onAreaChange 回调:监听容器的尺寸变化,手动控制布局分支切换(本文的核心方法)
  2. 断点系统(breakpoints):通过 GridRow / GridCol 组件配合 breakpoints 实现栅格布局
  3. MediaQuery:获取设备级别的媒体信息(如屏幕宽高、深色模式等)
  4. 百分比 / vp 单位:使用相对单位实现弹性缩放

其中,onAreaChange 是粒度最细、控制最灵活的方式——因为它监听的是 容器 的尺寸,而非设备屏幕的尺寸。这意味着即使在一个可拖拽分屏的应用中,当用户调整面板大小时,布局也能实时响应。

三、核心代码实现深度解析

3.1 顶层布局结构

整个页面的骨架是一个 Stack 容器,内部包含两层:

Stack
├── Column (主内容层)
│   ├── TitleBar (标题栏)
│   └── Column (表单区域 + onAreaChange 监听)
│       ├── MultiColumnForm / SingleColumnForm
│       └── SubmitButton
└── SuccessDialog (弹窗覆盖层,根据条件显示)

选择 Stack 作为顶层容器的原因是:弹窗组件需要覆盖在表单之上,而 Stack 天然支持层叠布局。

3.2 onAreaChange:ArkTS 版的 LayoutBuilder

这是整个项目的核心亮点。在 Flutter 中,我们这样写:

LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth > 600) {
      return _buildWideLayout();
    } else {
      return _buildNarrowLayout();
    }
  },
)

而在 ArkTS 中,我们通过 onAreaChange 回调配合 @State 实现同样的效果:

@State containerWidth: number = 0;

// 在 build() 中:
Column() {
  if (this.isMultiColumn) {
    this.MultiColumnForm()
  } else {
    this.SingleColumnForm()
  }
}
.width('100%')
.onAreaChange((oldValue: Area, newValue: Area): void => {
  this.containerWidth = newValue.width as number;
})

对比分析

特性 Flutter LayoutBuilder ArkTS onAreaChange
触发时机 每次布局重建时 容器尺寸实际变化时
返回信息 BoxConstraints(宽高约束) Area(完整的位置和尺寸信息)
声明方式 回调函数返回子树 回调更新状态,状态驱动 UI
性能特点 随布局树重建触发 仅尺寸变化时触发,更精确
使用场景 任何需要根据容器尺寸切换布局的地方 同上

值得注意的是,onAreaChange 是非惰性的——它只在容器的尺寸 实际发生变化 时才触发,而非在每次渲染帧都触发。这意味着它的性能开销非常低,适合高频交互场景。

3.3 计算属性驱动布局分支

通过一个只读计算属性 isMultiColumn 来驱动布局选择:

get isMultiColumn(): boolean {
  return this.containerWidth > 600;
}

在模板中直接使用 if (this.isMultiColumn) 来切换两个 @Builder 方法。这里的阈值 600vp(virtual pixel,虚拟像素)是经过实践检验的一个合理的分界点——手机竖屏通常在 360~420vp,平板竖屏在 600~800vp,桌面窗口通常在 1000vp 以上。

这种写法比直接在模板中写 if (this.containerWidth > 600) 更优雅,因为:

  • 逻辑集中:要修改阈值只需改一处
  • 语义清晰:isMultiColumn 的名称直接表达了意图
  • 可测试性:可以单独测试这个计算逻辑

3.4 双列表单(大屏模式)

isMultiColumntrue 时,渲染 MultiColumnForm() 方法。核心思路是用 Row 将两个表单项并排排列:

@Builder
MultiColumnForm(): void {
  Column() {
    // 第一行:姓名 + 性别
    Row() {
      FormFieldItem({ label: '姓名', required: true }) {
        TextInput({ placeholder: '请输入姓名', text: this.name })
          .onChange((val: string): void => { this.name = val; })
          .height(40)
          .backgroundColor('#F0F2F5')
          .borderRadius(8)
          .padding({ left: 12, right: 12 })
      }

      Blank().width(16)  // 列间距

      FormFieldItem({ label: '性别' }) {
        Row() {
          Radio({ value: 'male', group: 'gender' })
            .checked(this.gender === 'male')
            .onChange((): void => { this.gender = 'male'; })
          Text('男').fontSize(14).margin({ left: 4 })
          Blank().width(24)
          Radio({ value: 'female', group: 'gender' })
            .checked(this.gender === 'female')
            .onChange((): void => { this.gender = 'female'; })
          Text('女').fontSize(14).margin({ left: 4 })
        }
      }
    }
    .width('100%')
    // ... 后续行
  }
}

每一行都是一个 Row,内部包含两个 FormFieldItem,中间用 Blank().width(16) 作为间距。FormFieldItem 组件内部已经设置了 width('100%'),因此在 Row 中它会自动平分可用空间。

这种布局的优势在于:

  • 信息密度高:同样一屏可以展示更多内容
  • 视觉对齐:标签和输入框在一行内对齐,便于快速扫描
  • 节省滚动:减少用户的滚动操作

3.5 单列表单(小屏模式)

isMultiColumnfalse 时,渲染 SingleColumnForm() 方法。每个表单项独占一行:

@Builder
SingleColumnForm(): void {
  Column() {
    FormFieldItem({ label: '姓名', required: true }) {
      TextInput({ placeholder: '请输入姓名', text: this.name })
        .onChange((val: string): void => { this.name = val; })
        .height(40)
        .backgroundColor('#F0F2F5')
        .borderRadius(8)
        .padding({ left: 12, right: 12 })
    }

    Blank().height(16)

    FormFieldItem({ label: '性别' }) {
      // ...
    }
    // ... 后续表单项
  }
}

单列布局虽然信息密度低,但有以下不可替代的优点:

  • 聚焦性强:用户一次只能看到一个字段,填写更专注
  • 滑动顺畅:适合单手操作,手指覆盖区域合理
  • 触控友好:每个输入框都有足够大的点击区域

3.6 大屏与小屏的布局差异总结

布局特性 大屏模式(>600vp) 小屏模式(≤600vp)
核心容器 Row + Row 嵌套 Column 纵向排列
每行字段数 2 个 1 个
字段间距 行内 Blank().width(16),行间 Blank().height(20) Blank().height(16)
左右内边距 32vp 16vp
信息密度
适用场景 平板、桌面、横屏手机 竖屏手机、小尺寸窗口

四、组件化设计:FormFieldItem

4.1 组件职责

FormFieldItem 是一个通用表单项包装组件,职责清晰:

  1. 显示标签(含可选的必填标记 *
  2. 渲染子内容(输入框、选择器、开关等)
  3. 统一间距和样式

4.2 @BuilderParam 的妙用

ArkTS 中的 @BuilderParam 是组件化构建的强大工具。它类似于 Vue 的 slot(插槽)或 Flutter 的 child 参数:

@Component
struct FormFieldItem {
  label: string = '';
  required: boolean = false;
  @BuilderParam content: () => void = this.defaultContent;

  @Builder
  defaultContent(): void {
    Text(' ').fontSize(14)
  }

  build() {
    Column() {
      // 标签行
      Row() {
        Text(this.label).fontSize(14).fontColor('#333').fontWeight(FontWeight.Medium)
        if (this.required) {
          Text(' *').fontSize(14).fontColor('#E53E3E')
        }
        Blank().layoutWeight(1)
      }
      .width('100%')

      Blank().height(8)

      // 子内容插槽
      this.content()
    }
    .width('100%')
    .alignItems(HorizontalAlign.Start)
  }
}

使用时通过尾随闭包语法传入子内容:

FormFieldItem({ label: '姓名', required: true }) {
  TextInput({ placeholder: '请输入姓名', text: this.name })
    .onChange((val: string) => { this.name = val; })
    .height(40)
    .backgroundColor('#F0F2F5')
    .borderRadius(8)
    .padding({ left: 12, right: 12 })
}

这里的 { ... } 就是 @BuilderParam content 的实际参数。这种语法设计使得组件的使用非常自然——标签属性通过 {} 传入,子内容通过尾随闭包传入。

4.3 @BuilderParam 的初始化规则

在之前的调试过程中,我们遇到了一个 ArkTS 的编译规则:

如果组件属性支持本地初始化,则必须设置一个合法的、不依赖运行时的默认值。

对于 @BuilderParam content: () => void,它需要有一个默认的 @Builder 方法:

@BuilderParam content: () => void = this.defaultContent;

@Builder
defaultContent(): void {
  Text(' ').fontSize(14)
}

这里的 = this.defaultContent 引用了一个 @Builder 方法。这是 ArkTS 中少数允许引用 this 的默认值场景——因为 @Builder 方法是编译期处理的函数引用,不属于运行时状态。

如果我们不提供默认值,IDE 会报错:“@BuilderParam content 没有设置默认值”。如果提供的默认值不是 @Builder 方法引用(而是一个 lambda () => {}),也会报错。

五、弹窗实现:从 @CustomDialog 到纯组件

5.1 初版方案:@CustomDialog + CustomDialogController

最初的弹窗方案使用了 @CustomDialog 装饰器:

@CustomDialog
struct SuccessDialog {
  controller: CustomDialogController;
  // ...
}

但这种方式带来了几个问题:

  1. controller 属性没有默认值:ArkTS 要求所有非 @State 属性必须有默认值,但 CustomDialogController 的构造函数需要 builder 参数,形成了循环依赖
  2. CustomDialogController 不能是 @State:编译器明确禁止将 CustomDialogController 标记为 @State
  3. API 弃用风险AlertDialog.show() 已经从 API 26 开始标记为弃用

5.2 最终方案:纯组件 + Stack 覆盖层

最终采用了更受控的纯组件方案:

@Component
struct SuccessDialog {
  name: string = '';
  email: string = '';
  phone: string = '';
  department: string = '';
  isFullTime: boolean = true;
  @Link showDialog: boolean;

  build() {
    Column() {
      // 半透明遮罩
      Column() {
        // 弹窗卡片
        Column() {
          Text('提交成功').fontSize(20).fontWeight(FontWeight.Bold)
          // ... 信息展示行
          Button('确定').onClick(() => { this.showDialog = false })
        }
        .width('85%')
        .padding(24)
        .backgroundColor(Color.White)
        .borderRadius(16)
        .shadow({ radius: 20, color: 'rgba(0,0,0,0.15)', offsetY: 8 })
      }
      .width('100%').height('100%')
      .backgroundColor('rgba(0,0,0,0.4)')
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
    }
    .width('100%').height('100%')
  }
}

在父组件中,通过 Stack 和条件渲染控制弹窗的显示:

Stack() {
  // 主表单
  Column() { /* ... */ }

  // 弹窗覆盖层
  if (this.showSuccessDialog) {
    SuccessDialog({
      name: this.name,
      email: this.email,
      phone: this.phone,
      department: this.department,
      isFullTime: this.isFullTime,
      showDialog: this.showSuccessDialog
    })
  }
}

点击"确定"时通过 @Link 反向修改父组件的 showSuccessDialog 状态来关闭弹窗。

5.3 方案对比

特性 @CustomDialog 方案 纯组件方案
代码复杂度 低(内置动画) 中(手动管理)
可控性 中(受框架约束) 高(完全控制)
编译通过率 低(易触发 ArkTS 规则) 高(遵循标准组件规则)
动画效果 内置淡入淡出 需要手动实现
样式自由度 较低 完全自由

六、状态管理与数据绑定

6.1 表单状态的定义

所有表单字段都使用 @State 装饰器定义,确保 UI 随数据变化自动更新:

@State name: string = '';
@State email: string = '';
@State phone: string = '';
@State department: string = '';
@State jobPosition: string = '';
@State joinDate: string = '';
@State remarks: string = '';
@State gender: string = 'male';
@State isFullTime: boolean = true;
@State showSuccessDialog: boolean = false;

6.2 双向数据绑定

在 ArkTS 中,双向绑定需要手动实现。以 TextInput 为例:

TextInput({ placeholder: '请输入姓名', text: this.name })
  .onChange((val: string): void => {
    this.name = val;
  })

text 参数显示当前值,onChange 回调更新状态。这种方式虽然比 Flutter 的 TextEditingController 略显繁琐,但胜在直接——没有中间层,状态流向一目了然。

6.3 特殊控件的状态同步

Radio 单选框:通过 checked 属性和 group 属性管理选中状态:

Radio({ value: 'male', group: 'gender' })
  .checked(this.gender === 'male')
  .onChange((): void => { this.gender = 'male'; })
Radio({ value: 'female', group: 'gender' })
  .checked(this.gender === 'female')
  .onChange((): void => { this.gender = 'female'; })

这里有一个坑:大屏和小屏模式不能共用同一个 group 名称,否则会出现选择不同步的问题。因此大屏用 group: 'gender',小屏用 group: 'gender2'

Toggle 开关

Toggle({ type: ToggleType.Switch, isOn: this.isFullTime })
  .onChange((val: boolean): void => { this.isFullTime = val; })

七、视觉设计与样式系统

7.1 色彩系统

本项目采用了一套简洁的视觉设计,整体风格偏向专业、清爽:

用途 色值 应用场景
背景色 #F5F7FA 页面背景
卡片背景 #FFFFFF 标题栏、弹窗卡片
输入框背景 #F0F2F5 TextInput、TextArea
主色 #4A6CF7 提交按钮、主题元素
标题色 #1A1A2E 页面标题
标签色 #333333 表单项标签
辅助色 #8E8EA0 副标题、说明文字
错误色 #E53E3E 必填标记 *

7.2 按钮样式

提交按钮采用了带有投影的现代风格:

Button() {
  Text('提交登记')
    .fontSize(16)
    .fontColor(Color.White)
    .fontWeight(FontWeight.Medium)
}
.width('100%')
.height(48)
.backgroundColor('#4A6CF7')
.borderRadius(10)
.shadow({
  radius: 8,
  color: 'rgba(74,108,247,0.3)',
  offsetY: 4
})

shadow 属性为按钮添加了柔和的下投影,增强了立体感和点击欲望。

7.3 间距系统

间距在 UI 设计中至关重要。本项目采用了 8 的倍数作为间距单位:

  • 标签与输入框间距:8vp
  • 小屏表单项间距:16vp
  • 大屏表单项间距:20vp
  • 表单与边缘间距:24vp
  • 页面顶部间距:32vp

八、ArkTS 开发的血泪教训与最佳实践

在开发这个项目的过程中,我们遇到了多个 ArkTS 特有的编译问题和设计约束。下面逐一记录,希望能帮助后来的开发者少走弯路。

8.1 属性名不能与系统方法冲突

问题@State position: string = ''; 导致编译错误。

原因positionCommonAttribute 接口中定义的方法(用于设置组件位置),在子类 Index 中声明同名字段会与方法重名。

解决方案:重命名为 jobPosition

教训:在定义 @State 变量时,避开所有已知的组件属性方法名,如 widthheightpositionmarginpaddingalignshadow 等。

8.2 非 @State 属性必须有编译时常量默认值

问题controller: CustomDialogController; 没有默认值,触发编译错误。

原因:在 ArkTS 的 @Component 中,所有非装饰器属性(没有 @State@Prop@Link 等)必须有一个默认值,且默认值必须是编译时常量(不能是 new 表达式或函数调用)。

解决方案:避免在组件属性中存储需要运行时初始化的对象,改用其他方式管理。

8.3 @BuilderParam 的默认值必须是 @Builder 方法引用

问题@BuilderParam content: () => void; 没有默认值 → 编译错误。

解决方案:提供一个默认的 @Builder 方法:

@BuilderParam content: () => void = this.defaultContent;

@Builder
defaultContent(): void {
  Text(' ').fontSize(14)
}

8.4 private 属性不能通过构造函数初始化

问题

private label: string = '';
FormFieldItem({ label: '姓名' })  // Warning: private 属性不能通过构造函数初始化

解决方案:去掉 private,ArkTS 中不写访问修饰符等同于 public(但不能显式写 public):

label: string = '';

8.5 Radiovalue 参数必须是字符串

问题

Radio({ value: 0, group: 'gender' })  // Error: number 不能赋值给 string

解决方案:使用字符串值:

Radio({ value: 'male', group: 'gender' })

8.6 @Builder 方法中不能对自定义组件链式调用属性方法

问题:在 @Builder 方法中调用 FormFieldItem({...}) { ... }.layoutWeight(1) 导致 Cannot find name 'layoutWeight' 错误。

原因:在 @Builder 上下文中,自定义组件实例化后不能像在 build() 方法中那样链式调用属性设置方法。这是一个已知的 ArkTS 编译器限制。

解决方案

  • 对于 width('100%'):直接在 FormFieldItembuild() 方法内部设置
  • 对于 layoutWeight:将 FormFieldItem 包裹在 Column 容器中,在容器上设置 layoutWeight(但为了简洁,我们最终选择让大屏模式不做权重分配)

8.7 避免 @Builder 中调用 @Builder 传参导致的冗余

这是一个设计层面的建议:大屏模式(MultiColumnForm)和小屏模式(SingleColumnForm)虽然逻辑相似但布局结构不同。如果尝试通过参数化的 @Builder 方法来统一两者,代码反而会因为条件分支过多而难以维护。因此我们选择保留两个独立的 @Builder 方法,虽然代码量稍大,但清晰度更高。

九、性能考量

9.1 onAreaChange 的性能

onAreaChange 只在容器的尺寸实际变化时回调,不会在每次帧渲染时触发。这意味着:

  • 旋转屏幕:触发一次
  • 拖拽分屏:每次拖拽暂停时触发
  • 键盘弹出/收起:触发一次
  • 窗口最大化/恢复:触发一次

因此性能开销可以忽略不计。

9.2 条件渲染的性能

使用 if (this.isMultiColumn) 进行布局分支切换时,ArkTS 会销毁当前分支的组件树并创建新分支的组件树。这意味着切换时会有短暂的 UI 重建过程。

为了优化,可以考虑:

  1. 使用 opacityvisibility:对于只是隐藏/显示的场景,用这些属性代替条件渲染
  2. 懒加载:对于非首屏内容,可以使用 LazyForEach
  3. @Builder 缓存:ArkTS 编译器会对 @Builder 方法进行优化缓存

对于本项目的场景(用户不频繁切换窗口大小),条件渲染的开销完全可以接受。

十、测试验证

10.1 构建验证

通过 hvigorw assembleApp --build-mode debug 命令可以验证代码的编译正确性。每次修改后都应该运行此命令,确保:

  1. 0 ERROR:编译错误必须全部修复
  2. 0 ArkTS WARNING:IDE 的警告也应尽量清零,因为有些警告在 DevEco Studio 的预览器中可能会被当作错误处理

10.2 预览验证

在 DevEco Studio 中,可以打开 Index.ets 文件,点击右侧的 Previewer 标签进行实时预览。通过拖拽预览器的边框改变窗口宽度,观察布局是否在 600vp 处正确切换。

10.3 真机验证

建议在以下设备上验证:

  • 手机(竖屏 360~420vp):验证单列布局
  • 平板(竖屏 600~800vp):验证双列布局
  • 平板(横屏 800~1280vp):验证双列布局的宽屏表现
  • 折叠屏(展开态 ~800vp):验证布局切换的流畅性

十一、扩展与展望

11.1 三列布局

本项目的阈值是 600vp,这只是一个起点。对于更大尺寸的屏幕(如 PC 端 1440vp+),可以增加第三个阈值,实现三列布局:

get layoutMode(): LayoutMode {
  if (this.containerWidth > 1200) return LayoutMode.TRIPLE;
  if (this.containerWidth > 600) return LayoutMode.DOUBLE;
  return LayoutMode.SINGLE;
}

11.2 表格视图

对于更复杂的企业表单(如订单列表、审批流),可以在大屏模式下切换为表格视图,在 Row 中嵌入 List / Grid 组件,显示更多数据列。

11.3 与 GridRow/GridCol 结合

ArkTS 的 GridRow / GridCol 组件提供了更完善的栅格系统,可以结合本文的 onAreaChange 方法,实现更精细的断点控制:

GridRow({
  columns: this.isMultiColumn ? { sm: 12, md: 6 } : { sm: 12 },
  gutter: { x: 16, y: 16 }
}) {
  GridCol({ span: { sm: 12, md: 6 } }) { /* 表单字段 */ }
  GridCol({ span: { sm: 12, md: 6 } }) { /* 表单字段 */ }
}

11.4 表单验证

当前项目还未添加表单验证功能。可以扩展 FormFieldItem 组件,增加 validate 属性和错误提示能力:

FormFieldItem({ label: '邮箱', required: true, validate: EmailValidator }) {
  // ...
}

十二、总结

12.1 技术要点回顾

  1. 容器级响应式布局:使用 onAreaChange 监听容器宽度变化,结合 @State 和计算属性 isMultiColumn 驱动布局分支切换
  2. 组件化设计:通过 @Component + @BuilderParam 实现可复用的表单项包装组件
  3. 纯组件弹窗:用 Stack + 条件渲染 + @Link 替代 @CustomDialog,避免框架约束
  4. ArkTS 开发规范:遵循 ArkTS 的初始化规则、访问修饰符约束和类型要求

12.2 架构评价

维度 评价
可维护性 布局清晰,两个模式独立 @Builder,修改互不影响
可扩展性 可轻松添加第三个阈值实现三列布局
性能 onAreaChange 惰性触发,条件渲染仅在切换时重建
代码可读性 命名语义化,组件职责单一,@Builder 方法结构清晰
可测试性 状态纯函数,分离了数据与 UI

12.3 心得体会

在鸿蒙生态中做响应式布局,Flutter 开发者可能会本能地寻找 LayoutBuilder 的替代品。onAreaChange 虽然 API 签名和使用方式与 LayoutBuilder 不同——它不返回子树而是通过状态驱动——但最终达成的效果是一致的:代码根据容器的实时尺寸智能地选择最合适的布局方案

更重要的是,onAreaChange 的工作方式更符合 ArkTS 的声明式哲学:UI 是状态的函数,而不是事件的直接结果。这种从"命令式回调"到"声明式状态驱动"的思维转换,是掌握 ArkTS 的关键一步。


本文档对应的完整源代码位于项目根目录 entry/src/main/ets/pages/Index.ets 中。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐