【星光不负 码向未来】从深圳湾沙龙到 HarmonyOS 开发实战

20 阅读7分钟

昨天深圳湾的海风带着些许咸味,我走进 1024 开发者沙龙的会场。作为一名全网 4w + 粉丝的技术博主,但说实话,其实我还是一个刚接触鸿蒙不到三个月的 "小白",能和这么多技术大牛坐在一起探讨 HarmonyOS NEXT 的 "深水区",心里既兴奋又忐忑。但当讲师在台上演示代码的那一刻,我突然意识到 —— 鸿蒙开发,其实没有想象中那么遥不可及。

回到家已经是晚上九点,脑子里还在回放沙龙上的技术分享。

趁着这股热乎劲儿,我想把这段时间从零开始学习鸿蒙的经历,以及今天的参会收获整理出来。这不仅是对自己学习历程的复盘,也希望能给同样在摸索的朋友们一些参考。

1 ArkTS: 重新认识 TypeScript 的 "近亲"

刚开始接触 HarmonyOS 开发时,最让我头疼的就是 ArkTS。虽然官方说它是 TypeScript 的超集,但真正上手后发现,这个 “超集” 可不简单。

在沙龙现场,一位华为的技术专家用了个很贴切的比喻:“如果说 TypeScript 是一辆普通汽车,那 ArkTS 就是在这辆车上装了涡轮增压、主动悬架和智能驾驶系统。” 这话说得我当时就笑了,但细想确实有道理 ——ArkTS 不仅继承了 TypeScript 的类型系统,还针对鸿蒙的声明式 UI 开发做了深度优化。

2 声明式 UI 的第一步

记得我写的第一个鸿蒙应用,就是经典的 "Hello World"。但和传统开发不同,鸿蒙的声明式 UI 让我眼前一亮。

简单的代码样式如下:

@Entry

@Component

struct HelloWorld {

  @State message: string = '你好,HarmonyOS!'



  build() {

    Column() {

      Text(this.message)

        .fontSize(24)

        .fontWeight(FontWeight.Bold)

        .fontColor(Color.Blue)

    }

    .width('100%')

    .height('100%')

    .justifyContent(FlexAlign.Center)

  }

}

这段代码的逻辑清晰得让人舒服 ——@Entry 标记入口组件,@Component 定义组件,@State 管理状态。build () 方法里用链式调用描述 UI 结构,读起来就像在说 “我要一个占满屏幕的纵向容器,里面有段蓝色粗体文字,居中显示”。

运行这段代码,你会看到手机屏幕中央显示着 “你好,HarmonyOS!”。

简单,但这种声明式的写法却蕴含着强大的潜力。

温馨提示:@State 装饰的变量发生变化时,UI 会自动刷新。这点特别重要,初学者经常忘记加这个装饰器,导致界面更新不及时。

3 状态管理的智慧

在沙龙上,认识一位大佬,他和我讲了状态管理。他说很多开发者刚入门时,分不清 @State、@Prop、@Link 的区别。我深有同感,最初写代码时就经常用错。

后来我总结了个口诀:

(1)@State 是 "私有财产",只有自己能改。

(2)@Prop`是" 单向传递 ",父组件给子组件,子组件只能看不能改。

(3)@Link 是 "双向绑定",父子组件都能改,实时同步。

举个实际例子,做一个计数器应用:

@Entry

@Component

struct CounterApp {

  @State count: number = 0



  build() {

    Column({ space: 20 }) {

      Text(`当前计数: ${this.count}`)

        .fontSize(32)

      

      CounterButton({ count: $count })

    }

    .width('100%')

    .height('100%')

    .justifyContent(FlexAlign.Center)

  }

}



@Component

struct CounterButton {

  @Link count: number



  build() {

    Row({ space: 10 }) {

      Button('-')

        .onClick(() => {

          this.count--

        })

      

      Button('+')

        .onClick(() => {

          this.count++

        })

    }

  }

}

这个例子中的父组件 CounterApp 通过 @State 管理计数,子组件 CounterButton 通过 @Link 与父组件双向绑定。点击加减按钮,计数会实时更新显示。这种组件间通信的方式,在实际项目中特别常用。

4 CodeGenie:AI 助力开发的惊喜体验

在 DevEco Studio 中,我使用 CodeGenie 来输入:" 创建一个音乐播放器界面,包含封面、歌名、进度条和播放按钮 "。

CodeGenie 思考了几秒,居然真的给我生成了代码:

@Component

export struct MusicPlayer {

  @State songName: string = '星辰大海'

  @State artist: string = '未知歌手'

  @State isPlaying: boolean = false

  @State progress: number = 30



  build() {

    Column({ space: 20 }) {

      // 封面

      Image($r('app.media.album_cover'))

        .width(200)

        .height(200)

        .borderRadius(10)

      

      // 歌曲信息

      Column({ space: 5 }) {

        Text(this.songName)

          .fontSize(20)

          .fontWeight(FontWeight.Bold)

        Text(this.artist)

          .fontSize(14)

          .fontColor('#999999')

      }

      

      // 进度条

      Row({ space: 10 }) {

        Text('01:23')

          .fontSize(12)

        Slider({

          value: this.progress,

          min: 0,

          max: 100

        })

          .width('60%')

          .onChange((value: number) => {

            this.progress = value

          })

        Text('04:56')

          .fontSize(12)

      }

      .width('90%')

      

      // 播放按钮

      Button(this.isPlaying ? '暂停' : '播放')

        .fontSize(16)

        .width(120)

        .onClick(() => {

          this.isPlaying = !this.isPlaying

        })

    }

    .width('100%')

    .padding(20)

  }

}

虽然生成的代码还需要调整细节,比如图片资源路径、时间格式化等,但整体框架已经搭好了。这比我自己从零写要快太多。更重要的是,通过阅读 CodeGenie 生成的代码,我学到了很多布局技巧,比如 ColumnRow 的嵌套使用、Slider 组件的属性设置等。

CodeGenie 生成的代码是起点而非终点,需要根据实际需求进行调整和优化。但它确实能大幅降低学习成本,特别适合初学者快速上手。

5 云开发:告别后端配置的烦恼

在沙龙的云开发专场,大佬给我演示了如何用 AGC (AppGallery Connect) 快速搭建后端服务,鸿蒙的云开发让这一切变得简单。

我尝试做了个简单的待办事项应用:

(1)在 AGC 控制台创建云数据库,定义了一个 TodoItem 对象类:

export class TodoItem {

  id?: string

  title: string = ''

  completed: boolean = false

  createTime: number = 0

}

 

(2)在应用中调用云数据库 API:

import cloudDatabase from '@hw-agconnect/database'



@Entry

@Component

struct TodoApp {

  @State todoList: TodoItem[] = []

  @State inputText: string = ''

  private dbZone?: cloudDatabase.CloudDBZone



  async aboutToAppear() {

    // 初始化云数据库

    this.dbZone = await cloudDatabase.getCloudDBZone('TodoZone')

    this.loadTodos()

  }



  async loadTodos() {

    if (!this.dbZone) return

    

    const query = cloudDatabase.query(TodoItem)

    const result = await this.dbZone.executeQuery(query)

    this.todoList = result.getSnapshotObjects()

  }



  async addTodo() {

    if (!this.dbZone || !this.inputText) return

    

    const newTodo: TodoItem = {

      title: this.inputText,

      completed: false,

      createTime: Date.now()

    }

    

    await this.dbZone.executeUpsert(newTodo)

    this.inputText = ''

    this.loadTodos()

  }



  build() {

    Column({ space: 15 }) {

      // 输入框

      Row({ space: 10 }) {

        TextInput({ placeholder: '添加待办事项' })

          .layoutWeight(1)

          .onChange((value: string) => {

            this.inputText = value

          })

        

        Button('添加')

          .onClick(() => this.addTodo())

      }

      .width('90%')

      .padding({ top: 20 })

      

      // 待办列表

      List({ space: 10 }) {

        ForEach(this.todoList, (item: TodoItem) => {

          ListItem() {

            Row({ space: 10 }) {

              Checkbox()

                .select(item.completed)

                .onChange((value: boolean) => {

                  item.completed = value

                  this.dbZone?.executeUpsert(item)

                })

              

              Text(item.title)

                .fontSize(16)

                .decoration({

                  type: item.completed ? TextDecorationType.LineThrough : TextDecorationType.None

                })

            }

            .width('100%')

            .padding(10)

            .backgroundColor('#f5f5f5')

            .borderRadius(8)

          }

        })

      }

      .width('90%')

      .layoutWeight(1)

    }

    .width('100%')

    .height('100%')

  }

}

这段代码实现了完整的增查改功能,数据自动同步到云端。用户在不同设备上登录,都能看到相同的待办列表。最神奇的是,我没写一行后端代码,连服务器都不用自己搭建。

运行效果就是一个简洁的待办清单:顶部输入框可以添加事项,下方列表展示所有待办,点击复选框标记完成状态,完成的事项会显示删除线。所有操作实时同步到云端。

6 预加载:提升用户体验的关键

大佬还和我提到,很多开发者容易忽视应用启动速度,导致用户体验不佳。预加载技术可以显著改善这个问题。

我在自己的应用中实现了页面预加载。

思路是在应用启动时,提前加载常用页面的资源:

import router from '@ohos.router'



@Entry

@Component

struct MainPage {

  async aboutToAppear() {

    // 预加载详情页

    router.preloadPage({

      url: 'pages/DetailPage',

      mode: router.RouterMode.Standard

    })

  }



  build() {

    Column() {

      List() {

        ForEach([1, 2, 3, 4, 5], (item: number) => {

          ListItem() {

            Text(`新闻标题 ${item}`)

              .fontSize(18)

              .padding(15)

              .onClick(() => {

                router.pushUrl({

                  url: 'pages/DetailPage',

                  params: { id: item }

                })

              })

          }

        })

      }

    }

  }

}

预加载后,点击列表项跳转详情页的速度明显变快。用户几乎感觉不到加载过程,体验提升了一个档次。

预加载会占用内存,不要一次性加载太多页面。建议只预加载高频访问的 1-2 个页面。

7 AppLinking: 深度链接的魔力

AppLinking 是我在沙龙上从大佬身上学到的另一个实用技术,它允许通过 URL 直接打开应用的特定页面,这在营销推广、内容分享场景下特别有用。

下面我来展示具体的步骤:

(1)在 module.json5 中配置:

{

  "abilities": [

    {

      "name": "EntryAbility",

      "skills": [

        {

          "actions": ["ohos.want.action.viewData"],

          "uris": [

            {

              "scheme": "myapp",

              "host": "detail",

              "path": "/news"

            }

          ]

        }

      ]

    }

  ]

}

(2)在代码中处理链接跳转:

import Want from '@ohos.app.ability.Want'



export default class EntryAbility extends UIAbility {

  onCreate(want: Want) {

    // 解析深度链接参数

    if (want.uri) {

      const params = this.parseUri(want.uri)

      // 根据参数跳转到相应页面

      if (params.id) {

        router.pushUrl({

          url: 'pages/DetailPage',

          params: { id: params.id }

        })

      }

    }

  }



  parseUri(uri: string): Record<string, string> {

    const params: Record<string, string> = {}

    const query = uri.split('?')[1]

    if (query) {

      query.split('&').forEach(item => {

        const [key, value] = item.split('=')

        params[key] = decodeURIComponent(value)

      })

    }

    return params

  }

}

配置完成后,用户点击 myapp://detail/news?id=123 这样的链接,就能直接跳转到应用的新闻详情页,id 参数也会正确传递。这种无缝跳转的体验,对提高用户留存率很有帮助。

8 从沙龙到实践的思考

从深圳湾沙龙回来,我的脑子里一直在回想那些技术分享。讲师们以及结识的大佬不仅讲解了 HarmonyOS NEXT 的新特性,更重要的是分享了实际开发中的经验和踩过的坑。

大佬和我说的一句话让我印象深刻:" 鸿蒙开发的门槛确实不低,但一旦跨过这道门槛,你会发现这是一个充满可能的新世界。" 这三个月的学习历程,我深有体会。

刚开始接触 ArkTS 时,我觉得语法太复杂,各种装饰器看得眼花缭乱。但当我真正理解了声明式 UI 的设计理念后,反而觉得这种方式更符合直觉。写代码就像搭积木,每个组件职责清晰,组合起来就能构建复杂的界面。

云开发更是解决了我的大难题。以前做个人项目,光是搭建后端环境就要花好几天。现在有了 AGC,数据库、存储、认证这些服务开箱即用,让我能把更多精力放在业务逻辑和用户体验上。

回顾这段学习历程,我总结了一些经验,希望能帮到刚入门的朋友:

第一阶段:熟悉基础

从 "鸿蒙第一课" 开始,系统学习 ArkTS 语法和声明式 UI。不要急着做复杂项目,先把官方的示例代码都跑一遍,理解每个装饰器、每个组件的作用。这个阶段可能需要 2-3 周。

第二阶段:动手实践

选一个简单的项目实战,比如待办清单、天气应用、新闻阅读器。从零开始搭建,遇到问题先查文档,实在解决不了再去论坛提问。这个过程会踩很多坑,但正是通过踩坑才能真正理解技术细节。这个阶段建议持续 1-2 个月。

第三阶段:深入优化

当基本功能实现后,开始关注性能、用户体验这些细节。学习预加载、APMS 监控、云测试等进阶技术。参加线下沙龙、开发者社区交流,吸收别人的经验。这个阶段是个持续迭代的过程。

学习编程没有捷径,但有方法。多写代码,多看别人的代码,多思考为什么这样写。遇到问题不要怕,每解决一个问题,你的技能就提升一分。

从深圳湾沙龙走出来,我拍了张海边的照片。夕阳把海面染成了金色,几只海鸥在空中盘旋。那一刻我突然想到,学习鸿蒙开发就像在这片海上航行 —— 刚开始可能会迷失方向,可能会遇到风浪,但只要坚持前行,总能到达彼岸。

这篇文章记录了我这三个月的学习历程,也融入了昨天沙龙的收获。如果你也是鸿蒙的初学者,希望这些经验能给你一些帮助。如果你已经在鸿蒙开发的路上走了一段时间,欢迎分享你的故事。

技术的世界很大,鸿蒙的舞台很广。2025 年的这个秋天,让我们一起码上 "鸿" 图,在 HarmonyOS NEXT 的深水区里探索更多可能。

星光不负赶路人,代码终将成就梦想。