在这里插入图片描述

一、我们想解决什么问题

1.1 想象你在装修房子

想象一下,你要跟装修工人描述你想要的客厅。你会怎么说?

方式一(命令式):「先去左边那面墙刷白色乳胶漆,等干了之后在离地1米2的位置钉一根木条,木条上挂三幅40×60的装饰画,画和画之间间隔50厘米,每幅画离木条上边缘2厘米……哦对了,第二幅画右边缘要对齐那根水管的中线……」

方式二(声明式):「我想要一个简约北欧风的客厅,主色调白色,沙发背景墙上挂三幅装饰画。」

哪种更高效?显然是第二种。你不需要知道油漆该怎么刷、木条该钉多高,工人心里有数——他们把「简约北欧风」翻译成具体的施工步骤,你只管说你要什么就行。

ArkUI 干的事情本质上就是这第二种——你描述「我要什么」,框架帮你搞定「怎么做」。

1.2 命令式 UI 的烦恼

在 Android 开发里,传统做法是 XML 布局加 Java/Kotlin 代码。一个页面的开发流程大概是这样:先在 XML 里写出所有控件的层级关系和样式,然后到 Java 代码里一个个 findViewById 把控件找出来,再逐个 setTextsetImageResourcesetOnClickListener……

页面简单的时候还好,一旦复杂起来,代码就变成了一锅粥:

  • 数据变了你得手动同步 UI。 比如用户登录成功了,你得手动找到用户名 TextView,把它的文字改成服务器返回的用户名。这在专业术语里叫「手动 DOM 操作」,既繁琐又容易出错——万一哪天产品加了一个新字段,你得在七八个地方都加上更新的代码。
  • 状态多了要写大量胶水代码。 比如一个页面同时有「登录状态」「加载状态」「错误状态」「空状态」,你要写一堆 if-else 来决定显示哪个、隐藏哪个。
  • 换一套皮肤或者布局,改起来牵一发动全身。 想把按钮从蓝色换成红色?得在整个项目里搜索所有按钮的 ID,逐个改。
  • 页面逻辑和 UI 代码搅在一起。 按钮的点击事件处理代码和布局描述分散在两个文件里,读代码的时候要在 XML 和 Java 之间跳来跳去。

这就好比你每次想换一套衣服,还得先告诉裁缝:「把袖子长度减2厘米,领口加深1厘米,纽扣换成贝壳扣……」累不累?

1.3 从「告诉怎么做」到「告诉要什么」

声明式 UI 的出现,就是为了彻底解决这个问题。

它的核心思想非常朴素:你描述最终要呈现的「状态」,而不是达到那个状态的每一个步骤。

比如说,你不需要写「先加载图片,再设置圆角,再放到容器里,再响应点击」,你只需要说「这里放一张圆角图片,点击后跳转」。剩下的事情——图片怎么加载、圆角怎么画、点击热区怎么设置——全是框架的事。

ArkUI 就是 HarmonyOS 上的声明式 UI 实现。它的语法优雅、概念清晰,用起来的感觉很像在写一份「需求清单」而不是「施工说明书」。

打个更形象的比喻:传统 UI 开发像用 GPS 导航——你得告诉导航软件每一步怎么走(左转、第三个路口右转……);声明式 UI 则像告诉导航软件「我要去这个地址」——你只管说目的地,它自己规划路线。

这就是 ArkUI 想解决的问题:让 UI 开发回归本质——描述,而不是指令。让开发者把精力放在「做什么」上,而不是「怎么做」上。


二、数据模型设计

2.1 先定义数据,再构建 UI

声明式 UI 的第一步永远是:你想展示什么数据? 在动手画界面之前,我们先把这个想清楚。

很多新手容易犯的错误是:拿到需求就开始画界面——这里放一个列表,那里放一个按钮,然后再来想数据从哪来。这样写出来的代码,数据和 UI 是分离的、甚至对不上的,改起来特别痛苦。

正确的姿势是:先问自己三个问题——

  1. 这个页面展示哪些实体?(比如用户、项目、商品)
  2. 每个实体的哪些属性要展示在界面上?
  3. 哪些属性会变化?(比如点赞数、关注状态)这些就是「状态」

拿我们这篇文章的示例来说,我们要做一个「开发者信息卡」页面,展示一个开发者的头像、昵称、个人签名、代码示例、GitHub 地址、粉丝数,以及当前用户是否关注了他。数据模型可以这样定义:

// 定义开发者信息的数据结构
interface DeveloperProfile {
  avatarUrl: string;       // 头像图片地址
  nickname: string;        // 昵称
  signature: string;       // 个人签名,一句话介绍自己
  codeSnippet: string;     // 一行代码示例,展示开发者的风格
  githubUrl: string;       // GitHub 个人主页地址
  followersCount: number;  // 粉丝数
  followingCount: number;  // 关注数
  repoCount: number;       // 项目数
  isFollowing: boolean;     // 当前登录用户是否已关注该开发者
  isExpanded: boolean;     // 卡片是否展开了更多信息
}

为什么要先定义这个?因为 ArkUI 的响应式机制是基于数据的。数据一变,UI 自动跟着变。你不需要写一行代码去手动更新某个 Text 控件的内容——只要改数据,界面自己就刷新了。这就是声明式 UI 最爽的地方。

2.2 页面状态的设计思路

在 ArkUI 中,页面上的数据分为几类,每类的处理方式不同:

第一类:状态变量(@State)
页面内部自己管理的数据,变化后依赖它的 UI 部分会自动刷新。比如开关是否打开、输入框里的文字、列表是否在加载中、对话框是否显示——这些用 @State 管理。

第二类:应用数据
从后端获取的、或者跨页面共享的数据。比如用户信息、网络请求返回的列表——这些通常放在 @State 变量里,也可以在 aboutToAppear() 生命周期里从网络加载。

拿我们的开发者信息卡来说,isFollowing(是否已关注)和 isExpanded(是否展开了更多内容)都是典型的状态变量——用户点了关注按钮,isFollowing 就要从 false 变成 true,UI 上的按钮文字也要从「关注」变成「已关注」。这种场景用 @State 来管理最合适。

// 页面状态定义
@State profile: DeveloperProfile = {
  avatarUrl: 'https://avatars.githubusercontent.com/u/123456',
  nickname: 'AtomGit 官方',
  signature: '让开源更简单,让协作更高效',
  codeSnippet: 'git push origin main',
  githubUrl: 'https://github.com/atomgit',
  followersCount: 1024,
  followingCount: 256,
  repoCount: 42,
  isFollowing: false,
  isExpanded: false
}

@State isLoading: boolean = false       // 加载状态
@State errorMessage: string = ''       // 错误信息

注意到没有?@State 是一个装饰器,它告诉 ArkUI:「这个变量是页面的私有状态,当它变化时,依赖它的 UI 部分要自动刷新。」这是声明式 UI 架构的精髓——数据驱动,自动更新。

2.3 父子组件间的数据流动

在一个完整的页面里,组件之间是嵌套的,父组件和子组件之间需要传递数据。ArkUI 提供了几种装饰器来处理这种关系:

  • @Prop:从父组件传入子组件的数据,单向——子组件拿到的是副本,修改它不会影响父组件的数据。这适合只读的数据展示。
  • @Link:从父组件传入子组件的数据,双向绑定——子组件修改它,父组件的数据也会跟着变。这适合需要子组件能修改父组件数据的场景。
  • @ObjectLink:针对对象/数组类型的双向绑定,比 @Link 更强大,适合列表项等场景。
// 父组件中:
// @State projectList 是"所有者",持有真实数据
// ProjectCard 用 @ObjectLink 声明对 project 的引用
ForEach(this.projectList, (project: Project) => {
  ListItem() {
    ProjectCard({ project: project })  // 传入的是引用,不是副本
  }
})

// 子组件 ProjectCard 中:
@Component
struct ProjectCard {
  @ObjectLink project: Project   // 绑定到父组件传入的对象引用
  // 修改 this.project 的属性 = 直接修改父组件的 projectList 中的数据
}

这个数据流模型非常重要,理解透了,你就能写出结构清晰、状态可控的 ArkUI 代码。记住一个原则:谁持有数据,谁用 @State。谁需要使用数据,谁用 @ObjectLink 或 @Prop。


三、核心设计决策

3.1 框架选型:ArkUI vs Flutter vs React Native

在 HarmonyOS 生态里,开发 UI 有多条路可走。以下是我们最终选择 ArkUI 的核心考量:

维度 ArkUI(声明式) Flutter React Native 传统 XML + Java
性能 ⭐⭐⭐⭐⭐ 原生渲染,直接操作Ark引擎 ⭐⭐⭐⭐ JS引擎桥接,有一定开销 ⭐⭐⭐ JS引擎桥接,开销较大 ⭐⭐⭐⭐⭐ 原生,但代码量大
学习成本 ⭐⭐⭐⭐ 低,声明式语法直观 ⭐⭐⭐ 中,需熟悉Dart语言 ⭐⭐⭐ 中,需熟悉React思想 ⭐⭐⭐⭐ 高,模板代码多
生态适配 ⭐⭐⭐⭐⭐ 鸿蒙原生支持最好 ⭐⭐⭐⭐ 好,跨平台但非原生 ⭐⭐⭐ 需原生适配 ⭐⭐⭐ 成熟但非未来方向
声明式程度 ⭐⭐⭐⭐⭐ 纯声明式 ⭐⭐⭐⭐⭐ 纯声明式 ⭐⭐⭐⭐ 声明式但有命令式痕迹 命令式
开发效率 ⭐⭐⭐⭐⭐ 高 ⭐⭐⭐⭐ 高 ⭐⭐⭐ 中 ⭐⭐⭐ 低
状态管理 ⭐⭐⭐⭐ 内置装饰器方案,简洁 ⭐⭐⭐⭐⭐ Provider/Bloc 等成熟方案 ⭐⭐⭐⭐ Redux/Zustand 等成熟 ⭐⭐⭐ 手动管理
多设备适配 ⭐⭐⭐⭐⭐ HarmonyOS 全设备覆盖 ⭐⭐⭐ 跨平台但需额外适配 ⭐⭐⭐ 跨平台但需额外适配 ⭐⭐ 需分别开发

3.2 为什么最终选 ArkUI?

原因很朴素:在 HarmonyOS 上做开发,用官方原生框架,永远是最省力的选择。

Flutter 虽然也能跑在 HarmonyOS 上(OpenHarmony 的 Flutter 适配一直在推进),但毕竟不是「亲生」的。Flutter 的渲染引擎 Skia 是 C++ 写的,要跑在 HarmonyOS 上需要做大量的平台适配工作。ArkUI 是 HarmonyOS 的亲儿子,API 设计、系统集成、性能优化都针对 HarmonyOS 深度打磨,没有中间层性能损耗。

而且 ArkUI 的声明式语法非常友好——你会发现,用 ArkUI 写一个页面,比用 XML 写少 60% 以上的代码量。这个差距在实际项目中是相当可观的:

  • XML 方案:XML 文件 + Java/Kotlin 逻辑 + 大量 findViewById + 手动同步状态 → 一个页面 200+ 行
  • ArkUI 方案:单一 .ets 文件,UI 和逻辑放在一起,状态自动同步 → 同样功能的页面 60-80 行

在团队协作中,这个差距意味着更低的维护成本、更少的 Bug、以及新人更快的上手速度。

3.3 关于语言选择:eTS

ArkUI 使用的是 eTS(enhanced TypeScript),即增强版的 TypeScript。选择 TypeScript 而非 Java/Kotlin 的原因很直接:类型安全 + 声明式语法天然契合。

TypeScript 的 interface、泛型、装饰器和 ArkUI 的数据绑定机制配合得天衣无缝。你写一个 interface Project,IDE 就能在你写 .name 的时候提示类型、不让你把 .name 赋值给一个数字——这些类型检查在 UI 开发中非常有价值,能把大量 Bug 消灭在开发阶段,而不是等到运行时报错。

如果你之前写过 Vue 或 React,那上手 eTS 会非常快——ArkUI 的思维方式跟它们一脉相承,只是 API 语法不同。Vue 的「模板语法 + script setup」,React 的「JSX + Hooks」,和 ArkUI 的「.build() + @State」在思想上如出一辙。


四、完整代码实现

4.1 页面入口:一个完整的热门项目列表

先来看一个完整的 ArkUI 页面示例,我们实现一个「热门开源项目」列表,每一项展示项目名称、描述、编程语言和 Star 数量:

// Index.ets — 热门项目列表页
@Entry
@Component
struct Index {
  @State projectList: Project[] = [
    {
      name: 'OpenHarmony',
      desc: '开放原子开源基金会孵化项目,面向全场景的开源操作系统',
      lang: 'C/C++',
      stars: 42000,
      url: 'https://atomgit.com/openharmony'
    },
    {
      name: 'ArkUI',
      desc: 'HarmonyOS 声明式 UI 开发框架,用更少的代码做更多的事',
      lang: 'TypeScript',
      stars: 8800,
      url: 'https://atomgit.com/arkui'
    },
    {
      name: 'DevEco Studio',
      desc: 'HarmonyOS 应用开发一站式 IDE,基于 IntelliJ IDEA 定制',
      lang: 'Java',
      stars: 6500,
      url: 'https://atomgit.com/deveco'
    }
  ]

  @State isRefreshing: boolean = false

  build() {
    Column() {
      // 页面标题栏
      Row() {
        Text('🔥 热门开源项目')
          .fontSize(24)
          .fontWeight(FontWeight.Bold)
        Blank()
        Text(`${this.projectList.length}`)
          .fontSize(14)
          .fontColor('#999999')
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 16, bottom: 12 })

      // 项目列表
      List() {
        ForEach(this.projectList, (project: Project) => {
          ListItem() {
            ProjectCard({ project: project })
          }
          .onClick(() => {
            // 实际项目中这里会打开详情页
            console.info(`打开项目: ${project.name}`)
          })
        })
      }
      .width('100%')
      .layoutWeight(1)
      .padding({ left: 16, right: 16 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F5F5F5')
  }
}

这里有几个关键点需要解释一下:

@Entry 装饰器标记这个组件是页面的入口——一个页面有且只有一个 @Entry。没有这个装饰器,HarmonyOS 就不知道「这个组件应该作为页面显示,而不是作为一个普通 UI 片段」。

@Component 声明这是一个 UI 组件,注意它和 @Entry 是两个不同的概念:@Component 表示「这是一个组件」,@Entry 表示「这个组件是一个页面入口」。普通的小组件(比如我们后面要写的 ProjectCard)只用 @Component,不需要 @Entry

build() 方法是组件的「蓝图」——里面写的,就是你要展示的 UI 结构。每次关联的状态变量(@State)变化时,这个 build() 方法会重新执行,生成新的 UI 描述。

ColumnList 是容器组件,用来组织页面布局。Text 是文本,List 是列表,ForEach 是循环渲染——它的作用就像 JavaScript 里的 map 函数,遍历数据生成对应的 UI 元素。layoutWeight 是弹性布局参数,设为 1 表示让 List 占满剩余空间。

4.2 项目卡片组件:可复用的 UI 积木

列表里每个项目用一张卡片展示,我们把它抽成一个独立组件。组件抽离的原则是:「这东西在项目中可能出现多次吗?」只要可能,就抽出来。

// ProjectCard.ets — 项目卡片组件
interface Project {
  name: string
  desc: string
  lang: string
  stars: number
  url: string
}

@Component
export struct ProjectCard {
  @ObjectLink project: Project

  build() {
    Row() {
      // 左侧:语言色块图标
      Column() {
        Text(this.project.lang.charAt(0))
          .fontColor('#FFFFFF')
          .fontSize(18)
          .fontWeight(FontWeight.Bold)
      }
      .width(44)
      .height(44)
      .backgroundColor(this.getLangColor(this.project.lang))
      .borderRadius(10)
      .justifyContent(FlexAlign.Center)

      // 中间:文字信息
      Column() {
        Text(this.project.name)
          .fontSize(17)
          .fontWeight(FontWeight.Medium)
          .fontColor('#1A1A1A')
          .margin({ bottom: 4 })

        Text(this.project.desc)
          .fontSize(13)
          .fontColor('#666666')
          .maxLines(2)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .margin({ bottom: 6 })

        Row() {
          Text(`${this.formatStars(this.project.stars)}`)
            .fontSize(12)
            .fontColor('#FF9500')
            .fontWeight(FontWeight.Medium)

          Text(`  ·  ${this.project.lang}`)
            .fontSize(12)
            .fontColor('#999999')
        }
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)
      .padding({ left: 12 })
    }
    .width('100%')
    .padding(14)
    .backgroundColor('#FFFFFF')
    .borderRadius(14)
    .shadow({
      radius: 8,
      color: 'rgba(0, 0, 0, 0.06)',
      offsetX: 0,
      offsetY: 2
    })
  }

  getLangColor(lang: string): string {
    const colorMap: Record<string, string> = {
      'TypeScript': '#3178C6',
      'C/C++': '#00599C',
      'Java': '#ED8B00',
      'Python': '#3776AB',
      'Go': '#00ADD8',
      'Rust': '#DEA584'
    }
    return colorMap[lang] || '#888888'
  }

  formatStars(stars: number): string {
    if (stars >= 10000) {
      return (stars / 10000).toFixed(1) + 'w'
    }
    return stars.toString()
  }
}

注意 @ObjectLink 这个装饰器。当我们在父组件里用 ForEach 渲染列表项时,每个子组件需要一个对数据的引用——@ObjectLink 就是用来建立这个「双向绑定」关系的。

简单理解:@State 是数据的「持有者」,@ObjectLink 是数据的「使用者」,两者联动,数据一变 UI 就变。如果你改了 this.project.stars 的值——比如用户刷新了数据,服务器返回了新的 star 数——卡片上的显示会自动更新。整个过程不需要写一行「更新代码」,这就是声明式的威力。

4.3 交互:从关注按钮到卡片展开

最后加一点真实的交互:点击卡片可以展开更多操作区,点击「关注」按钮切换关注状态。这部分代码展示了如何在 ArkUI 中处理用户交互:

// 在父组件 Index 中添加展开状态管理
@State expandedProject: string = ''
@State followingSet: Set<string> = new Set()

// 修改后的 ForEach,带展开逻辑
ForEach(this.projectList, (project: Project) => {
  ListItem() {
    Column() {
      ProjectCard({ project: project })

      // 展开的操作区
      if (this.expandedProject === project.name) {
        Row() {
          Button(this.isFollowing(project.name) ? '已关注' : '+ 关注')
            .fontSize(13)
            .height(32)
            .backgroundColor(
              this.isFollowing(project.name) ? '#EEEEEE' : '#007AFF'
            )
            .fontColor(this.isFollowing(project.name) ? '#666666' : '#FFFFFF')
            .borderRadius(16)
            .onClick(() => {
              this.toggleFollow(project.name)
            })

          Blank()

          Text('复制链接')
            .fontSize(13)
            .fontColor('#007AFF')
            .onClick(() => {
              // 实际项目中这里会调用剪贴板 API
              console.info(`复制链接: ${project.url}`)
            })
        }
        .width('100%')
        .padding({ top: 10, bottom: 4 })
        .transition({ type: TransitionType.Insert, opacity: 0 })
      }
    }
  }
  .onClick(() => {
    // 切换展开状态
    animateTo({ duration: 250 }, () => {
      this.expandedProject =
        this.expandedProject === project.name ? '' : project.name
    })
  })
})

ArkUI 的事件绑定语法非常直观:写在组件上,语法是 .onXxx(),比如 .onClick() 表示点击事件,.onScroll() 表示滚动事件,.onChange() 表示值变化事件。这比 Android 的 setOnClickListener 语法更紧凑,也更符合「声明」的意味——在描述组件结构的时候「顺手」把行为也声明了,代码读起来就像在读产品需求文档。


五、深度技术原理

5.1 声明式 UI 的心脏:Diffing 算法

你可能好奇:ArkUI 怎么知道「哪个数据变了,应该更新哪部分 UI」?这背后靠的是 Diffing 算法(也叫「差异计算」或者「比对算法」)。

打个比方来解释:想象你有一份餐厅的旧菜单(旧的 UI 描述),然后厨师长更新了几道菜(数据变了)。他不需要重印整份菜单,他只需要对比新旧两份菜单,找出「哪些菜变了」——新加了一道甜品、删了一道凉菜、两道热菜换了名字——然后只重印变化的那几页。

这就是 Diffing 的核心思想:比较新旧两个状态的差异,只更新实际变化的部分。

ArkUI 的渲染管线是这样的:

用户操作 / 数据变化
       ↓
  触发状态更新(@State 变量的 setter 被调用)
       ↓
  ArkUI 生成新 UI 树描述(新的 build() 结果)
       ↓
  Diffing Engine 对比新旧两棵 UI 树
       ↓
  计算出最小更新集(哪些节点变了、哪些要删除、哪些要新增)
       ↓
  只把这些变化应用到真实屏幕上

关键点在于最后一步:ArkUI 不是重新渲染整个页面,而是只更新「实际变化了的那部分」。 这就像你在 Excel 里改了一个单元格的数字,Excel 不会重新生成整张表格——它只刷新那一个格子的显示。

这就是声明式 UI 性能出色的根本原因:它天然追求最小化更新。 在 Android 的 XML 方案里,状态一变,开发者往往要手动更新一片 UI——比如列表里某一项的点赞数变了,你可能得 notifyDataSetChanged() 让整个列表重绘。ArkUI 不会,它只会精确刷新那一个点赞数。

5.2 ArkUI 的渲染架构:三层分离

ArkUI 的渲染管线分为三层,理解这三层对深入掌握它非常有帮助:

第一层:框架层(Framework Layer)
也就是你写的 eTS 代码。你用声明式的语法描述「我要什么 UI」,框架把这些描述转换成内部的「渲染指令」。这层完全用 TypeScript/eTS 实现,负责组件树的构建、状态管理和生命周期。

第二层:渲染引擎(Render Engine)
负责执行渲染指令,构建 UI 树。它用 C++ 实现,和 HarmonyOS 的图形子系统直接对接,效率很高。这层是性能的关键——ArkUI 的渲染引擎经过了大量优化,在 HarmonyOS 的 Ark 编译器加持下,可以直接生成高效的图形指令。

第三层:平台适配层(Platform Adaptation Layer)
负责对接不同设备的屏幕、输入、字体等系统能力。ArkUI 在 HarmonyOS 上是深度适配的,这一层非常薄,几乎没有性能损耗。

对比一下 Flutter 的架构,你就明白了差距在哪里:Flutter 也有三层——Dart 代码层、Engine 层、Platform 层。但 Flutter 在 iOS/Android 上多了一个 Platform View 层,用来嵌入原生控件(比如地图、相机 WebView),这层桥接是有运行时开销的。ArkUI 在 HarmonyOS 上没有这个问题——因为 HarmonyOS 从底层设计就是为 ArkUI 量身打造的,不需要桥接层。

5.3 响应式数据绑定原理

@State 和 UI 之间的「自动同步」是怎么实现的?答案是 Proxy(代理)模式

当你在 @State 变量上声明 isFollowing 时,ArkUI 实际上把这个属性的访问和修改都「劫持」了——用 JavaScript 的 Proxy 包裹起来。

// 概念上的工作原理(简化版)
this.isFollowing = true
//     ↓
// Proxy setter 被触发
//     ↓
// 记录「isFollowing 属性从 false 变成了 true」
//     ↓
// 通知渲染引擎:「有状态变了,重新渲染依赖 isFollowing 的组件」
//     ↓
// 渲染引擎执行 Diffing,发现只有「关注按钮」这个组件依赖 isFollowing
//     ↓
// 只重新渲染关注按钮,其他 UI 完全不动

这和 Vue 3 的 ref / reactive 原理非常像。如果你熟悉 React 的 useState,理解起来也不难——核心都是「数据变 → 自动触发重渲染」,只是实现手段各有不同。

有一点很妙的是:ArkUI 的响应式是编译时增强的。这是什么意思呢?普通的运行时代理(Vue 3 的 reactive),每次访问和修改属性都要走 Proxy 的拦截逻辑。而 ArkUI 在编译阶段会对 @State 变量做静态分析,生成优化后的代码——能确定哪些组件依赖哪些状态的情况下,编译后的代码会直接指向需要更新的组件,减少了运行时的查找开销。

5.4 组件化设计哲学:组合优于继承

ArkUI 的组件化设计有两个核心理念,理解它们能帮你写出更好的代码:

第一,可复用即组件。 只要一段 UI 可能被重复使用,就把它抽成一个独立的 @Component。这个组件有自己的数据输入(@Prop@ObjectLink)、自己的内部状态(@State)、自己的样式、自己的行为(.onClick() 等事件)。抽出来之后,在任何地方都能用——就像乐高积木,磨坏了换一块就行,不用把整辆车拆了重做。

第二,组合优于继承。 ArkUI 不支持类的继承语法来扩展组件(class MyCard extends BaseCard {} 这样的写法不存在),而是推崇「组合」——大组件由小组件堆叠而成。这和 React 的组件树哲学一脉相承:复杂的页面 = 简单的组件 × 正确的组合。

为什么不用继承?因为继承容易造成「组件地狱」——你写了一个 BaseCard,然后有人继承 BaseCard 写了个 LargeCard,然后有人继承 LargeCard 写了个 SpecialLargeCard……改一行样式要往上游追溯七八层,改出一个 Bug 来都不知道是谁的锅。组合就没有这个问题,每个组件都是独立的,改了不会影响别人。


六、常见问题解答(FAQ)

Q1:ArkUI 和 Flutter 有什么区别?我应该选哪个?

核心区别在于「血缘关系」。ArkUI 是 HarmonyOS 亲生的,Flutter 是跨平台的第三方框架。

如果你的目标平台就是 HarmonyOS / OpenHarmony,选 ArkUI 准没错——性能更好、系统集成更深、文档和社区都在快速成长,而且华为官方会持续投入。反过来,如果你需要同时支持 iOS、Android、HarmonyOS 多个平台,且能接受一定的平台桥接开销,Flutter 也是一个成熟的选择——它有更大的社区和更丰富的第三方库生态。

一句话总结:在 HarmonyOS 上开发,用 ArkUI。

Q2:ArkUI 支持响应式布局吗?手机和平板怎么适配?

支持,而且做得相当完善。ArkUI 提供了多种响应式布局方案:

  • 线性布局Column(垂直)、Row(水平)、Stack(层叠)——最基础的布局容器
  • 栅格布局GridGridItem——适合做九宫格、相册等规则排列
  • 列表布局ListListItem——适合做长列表,有内置的懒加载优化
  • 条件渲染if/elsebuild() 里直接用——根据条件显示或隐藏某块 UI
  • 循环渲染ForEach ——遍历数组生成重复 UI

HarmonyOS 的多设备适配思路是「一次开发,多端部署」。ArkUI 底层会根据目标设备的屏幕尺寸和分辨率自动调整布局参数。举个例子,你在手机上写了一个两列的 Grid,在平板上会自动变成四列——不需要写设备判断代码。同一个布局文件,在不同设备上呈现为最适合该设备的样式。

Q3:ArkUI 的性能真的比传统 XML + Java 更好吗?

从底层架构看,ArkUI 确实有优势:

传统 XML + Java 的方案,数据到 UI 的流程是这样的:

  1. XML 解析 → 生成控件对象
  2. Java 代码 findViewById 查找控件
  3. 数据变化 → Java 代码手动调用 setText / setImage
  4. 手动调用 notifyDataSetChanged() 等刷新方法

ArkUI 的流程是这样的:

  1. 数据变化 → 触发状态更新
  2. Diffing Engine 计算差异
  3. 最小化更新 UI

不过,这个差距在简单页面上感知不强——两种方案都很快。但在复杂页面(比如长列表、频繁数据更新的场景)里,ArkUI 的优势会明显体现出来:代码量少、维护成本低、UI 更新自动且精准。数据一变,UI 自动变——你不需要在十几个地方写同步代码,也不会漏掉某个忘记更新的角落。

Q4:我之前没学过 TypeScript,能直接上手 ArkUI 吗?

能上手,但建议先花一两个小时熟悉 TypeScript 的基础语法,特别是这几个概念:

  • interface:定义数据结构的方式,比如 interface User { name: string; age: number }
  • type:类型别名,和 interface 类似但更灵活
  • 装饰器 @Xxx 的概念:TypeScript 的实验性语法,ArkUI 大量使用,比如 @State@Component
  • 箭头函数和模板字符串list.map(item => item.name)`Hello ${name}`

ArkUI 的声明式语法本身非常友好,门槛不高。很多写过 Vue 或者 React 的开发者,第一次看到 ArkUI 代码就能猜出一半的意思。TypeScript 的类型系统反而是帮助你的好东西——它会在你写错的时候告诉你,而不是等 App 跑起来才发现 Bug。

Q5:ArkUI 能做动画效果吗?会不会很复杂?

能,而且做起来比传统方案简单很多。ArkUI 内置了完整的动画能力:

显式动画 animateTo:告诉框架「某个变化要带动画」,闭包里做的所有样式变化都会自动带上过渡动画。

Column() {
  // 内容
}
.width(this.isExpanded ? '100%' : '50%')
.onClick(() => {
  animateTo({ duration: 300, curve: Curve.EaseOut }, () => {
    this.isExpanded = !this.isExpanded  // 这个变化会自动带动画
  })
})

属性动画 .animation():直接在组件上挂载动画配置。

Text('Hello')
  .opacity(this.isVisible ? 1 : 0)
  .animation({ duration: 400, curve: Curve.EaseInOut })

过渡动画 transition:组件出现和消失时的动画效果。

if (this.showBanner) {
  Text('欢迎使用 AtomGit')
    .transition({ type: TransitionType.Insert, opacity: 0 })
}

这些 API 用起来非常直觉——你描述「我要什么动画」,框架帮你算中间帧。 不需要你写插值公式、不需要你开计时器、不需要你管理动画状态。框架帮你全包了。

Q6:ArkUI 适合做复杂应用吗?比如电商 App?

非常适合。HarmonyOS 官方的应用——设置、浏览器、应用市场、音乐、钱包——全部用 ArkUI 开发,这本身就是最好的证明。

电商 App 里的商品列表、商品详情、购物车、订单流程——这些 ArkUI 都能 hold 住。多层嵌套组件、复杂状态管理(比如购物车同时管理商品数量和选中状态)、网络请求后的数据加载和骨架屏——这些都有成熟的实践方案。

关键是你要把组件拆分做好。遵循一个原则:一个组件只做一件事。购物车页面不要写成一个巨大的单文件——拆成 CartHeader(头部)、CartItem(商品行)、CartFooter(底部结算栏)三个组件,每个都简单清晰。然后在父组件里组合它们,通过 @State@Link / @ObjectLink 传递数据。这样写出来的代码,任何人接手都能快速理解。


七、运行效果

运行上面代码示例后,你会看到一个清爽的「热门开源项目」列表页面:

在这里插入图片描述

点击任意卡片,卡片会轻轻放大(scale 缩放动画,0.98 → 1.0),然后展开底部操作区:

在这里插入图片描述

点击「关注」按钮,按钮文字从「+ 关注」变为「已关注」,背景色从蓝色 (#007AFF) 渐变过渡到浅灰色 (#EEEEEE),文字颜色从白色渐变到深灰色。整个过程流畅自然——全部由 ArkUI 的 animateTo 框架自动处理,不需要你写任何插值计算代码。

点击列表外区域,操作区自动收起,也是带动画的。


八、扩展方向

ArkUI 的学习路径远不止于此,以下几个方向值得深入探索:

第一,动画与交互进阶。
ArkUI 的 animateTotransition API 非常强大。结合手势识别(Gesture 组件提供的 PanGesture 滑动手势、PinchGesture 双指缩放、SwipeGesture 滑动方向识别),你可以实现非常流畅的交互动效:下滑关闭页面、侧滑删除、左滑拉出操作菜单、长按弹出上下文菜单。双指捏合缩放图片、列表惯性滚动……这些在 ArkUI 里都有成熟的支持,而且代码量比传统方案少得多。

第二,自定义组件与 Canvas 绘制。
当内置组件不满足需求时,ArkUI 允许你用 Canvas 绘制自定义图形。结合 RenderingContext(绑定到 Canvas 组件的绘图上下文),你可以实现统计图表(柱状图、折线图、饼图)、签名板、手绘涂鸦板、甚至简单的游戏画面。这部分能力来自于 HarmonyOS 的 2D 图形子系统,API 设计参考了 HTML5 Canvas 的风格,上手成本很低。

第三,状态管理与路由。
单页面复杂了就需要更好的状态管理。HarmonyOS 提供了 AppStorage(全局应用存储)、LocalStorage(页面级存储)、PersistentStorage(持久化存储)等机制来做全局状态共享和持久化。页面路由(Navigation)也有完整的 API 支持——NavPathStack 可以管理路由栈,实现前进、后退、参数传递等功能。

第四,多设备适配实战。
把手里的 Demo 部署到手机、平板、智慧屏甚至车机上,体验「一次开发、多端部署」的实际效果。不同设备的屏幕尺寸、交互方式(触屏 vs 遥控器 vs 语音)差异很大,亲自动手适配一遍,会让你真正理解 ArkUI 设计响应式布局的用心之处。华为官方有完整的「多设备 UI 适配指南」,值得一读。

第五,深入源码理解原理。
ArkUI 的核心渲染代码是开源的(OpenHarmony 的 AUI 部分在 Gitee 上可以找到),有余力的同学可以去读一读源码。重点关注:Diffing 算法的具体实现(怎么比较两棵 UI 树的差异)、Proxy 代理的具体实现(怎么劫持状态变化)、以及渲染管线的数据流(状态变化后,信号是怎么传递到渲染引擎的)。读完源码,你会对声明式 UI 的理解上一个台阶——不只是「会用」,而是真正懂得「为什么这样设计」。

第六,结合 HarmonyOS 的分布式能力。
HarmonyOS 有一个独特的能力——分布式任务调度。ArkUI 可以感知「附近有其他 HarmonyOS 设备」,从而把任务无缝流转到另一台设备上执行。比如你在手机上选了一张图片,可以「一碰」传到平板上继续编辑;或者手机上开的视频通话,可以转到智慧屏上继续。这部分能力和 ArkUI 的状态管理结合得非常好,是 HarmonyOS 区别于其他移动 OS 的独特亮点。


Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐