ArkTS 声明式 UI 的本质:状态映射

ArkTS 声明式 UI 的本质:你不是在写界面,而是在写状态映射

如果你是从 TypeScript 的基础语法直接跳到 ArkTS 的 UI 开发,第一眼看到 build() 里的代码时,你可能会感到困惑。为什么这里全是函数调用 Column()Text(),但看起来又不像在调用普通函数?为什么修改了一个变量,界面就自动变了?你找不到任何"刷新屏幕"或"更新显示"的指令,但预览器里的内容确实在动。理解这种"自动"背后的机制,是掌握 ArkTS 的核心。


一、build() 函数:你写的是描述,不是命令

在传统的编程思维中,我们习惯"命令式"的写法:先创建对象,再设置属性,再执行动作。比如你在 TypeScript 中定义一个类,实例化后调用方法:

typescript 复制代码
const box = new Box();
box.setWidth(100);
box.setText('Hello');
box.show();

但在 ArkTS 中,你写的是:

typescript 复制代码
build() {
  Column() {
    Text('Hello')
      .width(100)
  }
}

这段代码的关键在于:Text('Hello') 不是在屏幕上画出一个文本框,而是在内存中生成一个"描述对象"。这个对象只包含三条信息:类型是 Text,内容是 'Hello',宽度是 100。它还没有被渲染,它只是一份"设计图纸"。

build() 的完整作用,就是把这些描述对象组装成一棵树(UI Description Tree),然后交给 ArkTS 框架。框架拿到这棵树后,会自己决定怎么把它画到屏幕上。如果某个状态变了,框架会重新调用你的 build(),拿到一棵新的树,对比两棵树的差异,只更新变化的部分。

这意味着 build() 可能会被反复执行。所以里面不能写副作用------不能修改外部变量,不能执行异步请求,不能做计算之外的任何事情。因为框架不保证它只执行一次。


二、@State:变量变了,框架怎么知道的?

ArkTS 中 @State 装饰器的作用,是把一个普通变量变成框架可追踪的依赖源

typescript 复制代码
@Entry
@Component
struct Counter {
  @State count: number = 0

  build() {
    Column() {
      Text(`${this.count}`)
        .fontSize(30)
      
      Button('增加')
        .onClick(() => {
          this.count++
        })
    }
  }
}

这里的 @State count 不是普通的 let count。当你第一次执行 build() 时,框架会悄悄记录:Text 这个组件读取了 count 的值 。当你点击按钮执行 this.count++ 时,框架检测到这个依赖源发生了变化,于是重新执行 build(),生成新的描述树,对比后发现 Text 的内容从 "0" 变成了 "1",只更新这一个地方。

整个过程你不需要手动通知框架"我改了数据请刷新",也不需要去找到具体的 Text 节点修改它的值。你只需要修改数据,框架通过依赖追踪自动完成更新。

核心原则:状态是唯一的真相,UI 只是状态的投影。 想改变界面,不要去想"怎么操作那个文本框",要去想"怎么改变这个数据"。


三、复用结构:@Builder 是模板,不是函数

当界面中有重复的结构时,你可能会本能地想写一个普通函数来复用。但 @Builder 不是普通函数:

typescript 复制代码
@Builder
  RowItem(title: string, desc: string) {
    Row() {
      Text(title).width(100)
      Text(desc).width(200)
    }
    .height(50)
  }

build() {
  Column() {
    this.RowItem('标题1', '描述1')
    this.RowItem('标题2', '描述2')
  }
}

@Builder 不能返回数据,不能被赋值给变量,不能作为参数传递。它只能出现在 build() 内部或另一个 @Builder 内部。它的作用是定义一段可复用的 UI 描述模板,调用时框架会把这段描述展开,插入到当前 UI 树中。

如果你需要重复项来自数组,用 ForEach

typescript 复制代码
@State items: { id: string; name: string }[] = [
  { id: '1', name: 'A' },
  { id: '2', name: 'B' }
]

build() {
  List() {
    ForEach(this.items, (item, index) => {
      ListItem() {
        Text(item.name)
      }
    }, (item, index) => item.id)
  }
}

ForEach 不是循环语句。它描述的是:数组中的每个元素,对应 UI 树中的这样一个片段 。第三个参数 key 是数组元素的唯一标识,帮助框架在数组增删时高效更新,而不是销毁整个列表重建。


四、条件渲染:if/else 决定分支是否存在

typescript 复制代码
@State isLoggedIn: boolean = false

build() {
  Column() {
    if (this.isLoggedIn) {
      Text('欢迎回来')
      Button('退出')
    } else {
      Button('登录')
    }
  }
}

这里的 if/else 不是控制程序流程,而是描述 UI 树的分支结构 。当 isLoggedInfalse 变为 true 时,框架对比前后两次 build() 返回的树:上一次有 Button('登录'),这一次有 Text('欢迎回来')Button('退出')。框架会销毁旧的按钮,创建新的文本和按钮。

这意味着条件渲染是节点的生与死 ,不是隐藏与显示。if 为假时,对应的 UI 描述片段根本不存在于树中。


五、总结:声明式 UI 的三层思维

层级 你在做什么 框架在做什么
定义状态 @State 声明数据 追踪谁依赖了这些数据
描述映射 build() 中写状态到 UI 的对应关系 对比前后两棵描述树,找出差异
触发更新 修改 @State 的值 重新执行 build(),最小化更新变化的部分

当你接受"UI 只是状态的投影"这一事实时,ArkTS 中那些看起来"不像在写界面"的语法就变得合理了。你不是在指挥框架"画这个、改那个",而是在告诉框架:"当数据是这样时,界面应该长这样。" 框架负责把"应该"变成"现实"。

相关推荐
ZC跨境爬虫3 小时前
跟着 MDN 学 HTML day_40:(DOMImplementation 接口完全解析)
前端·ui·html·媒体
guo_zhen_qian5 小时前
鸿蒙模拟器WebView使用Chrome inspect调试
chrome·华为·harmonyos
生活观察站6 小时前
2026鸿蒙生态适配工具测评|跨平台app开发平台选型指南
华为·harmonyos
xmdy58666 小时前
Flutter+开源鸿蒙实战|校园易生活Day7 个人中心完善+我的发布/收藏+退出登录+主题切换+全局UI美化(项目闭环)
flutter·开源·harmonyos
求学中--6 小时前
鸿蒙网络请求从入门到精通:HttpURLConnection+第三方库,GET/POST/文件上传全覆盖
开发语言·php·harmonyos
唐诺7 小时前
iOS UI 开发完全指南:UIKit 与 SwiftUI
ui·ios·swiftui
13509729427 小时前
Harmony OS 多功能录音小工具
harmonyos
13509729427 小时前
Harmony OS 定位功能开发实战
harmonyos
13509729427 小时前
Harmony OS 打造多功能录音与发音应用(音视频开发)
harmonyos