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

本文记录了在 HarmonyOS Next 平台上,使用 ArkTS API 24 开发「笔的进化路线」时间线应用的完整实践过程,涵盖数据模型设计、UI 布局方案、API 24 兼容性要点以及编译调试全流程。


一、项目背景与概述

1.1 为什么做这个应用?

书写工具——「笔」——是人类文明最重要的发明之一。从古埃及人用芦苇笔在纸莎草上书写,到今天我们用智能笔在平板上绘画和笔记,笔的进化历程本身就是一部浓缩的人类文明史。

然而,对于大多数学习者来说,这段历史散落在不同的资料中,缺乏一个直观、可交互的时间线展示工具。因此,我决定在 HarmonyOS Next 平台上,使用 ArkTS 语言(API 24)开发一个「笔的进化路线」应用,以时间线卡片的形式,清晰展示从古至今各类笔的发展脉络。

1.2 技术选型

技术项 选择
操作系统 HarmonyOS Next (API 24)
开发语言 ArkTS (基于 TypeScript,兼容 API 24 版本)
开发工具 DevEco Studio
数据存储 内存级 JSON 对象(模拟数据)
UI 布局 全 ArkTS 声明式组件

1.3 应用功能一览

  • 时间线展示:以纵向时间轴布局展示 10 个笔的进化阶段
  • 交互式卡片:点击卡片展开/收起详情信息
  • 数据统计:底部统计面板展示进化阶段数量和跨度
  • 视觉风格:卡片式圆形时间点 + 连接线,配合 Emoji 图标区分各笔型

二、应用设计与数据模型

2.1 数据接口定义

在 ArkTS API 24 中,interface 用于定义数据模型的结构化类型。我们定义了 PenStage 接口来描述每一个进化阶段:

interface PenStage {
  id: number         // 排序 ID
  name: string       // 笔的名称
  icon: string       // 图标 key
  year: string       // 出现的年代
  origin: string     // 起源地
  desc: string       // 详细介绍
  details: string    // 展开后的详细参数
}

接口设计要点

  • 所有字段均为必填(非可选),确保数据完整性
  • icon 字段使用字符串 key,而不是直接存储 Emoji,便于后续扩展为图标字体或图片资源
  • yearorigin 分离为独立字段,便于后续增加"按年代排序"或"按地域筛选"功能

2.2 模拟数据集

应用内置了 10 个进化阶段的数据,全部以 JSON 风格的对象数组在内存中构建。这里在 ArkTS 中使用了 push() 方法依次添加,而不是使用 map()forEach(),原因将在第三章详细说明。

数据亮点

序号 笔的名称 年代 发源地 关键成就
1 芦苇笔 约公元前3000年 古埃及·美索不达米亚 人类最早的书写工具
2 羽毛笔 公元6世纪 欧洲·中世纪 弹性笔尖,统治千年
3 金属笔尖笔 1803年 英国·伯明翰 首次金属笔尖量产
4 自来水笔(钢笔) 1884年 美国·纽约 毛细供墨革命
5 圆珠笔 1943年 阿根廷·布宜诺斯艾利斯 滚珠带墨,全球普及
6 记号笔 1952年 美国·芝加哥 多表面书写
7 滚珠笔 1963年 日本·东京 水性+滚珠结合
8 中性笔(啫喱笔) 1984年 日本·东京 凝胶墨水,学生首选
9 触控笔 2007年 全球·智能手机时代 数字输入复兴
10 智能笔 2010年代 全球·数字时代 书写+录音+云同步

2.3 数据初始化函数

aboutToAppear() 生命周期中调用 initData() 初始化数据。这是 ArkTS 中标准的启动逻辑:

aboutToAppear(): void {
  this.initData()
}

initData() 内部创建了一个局部数组 data,逐条 push() 添加数据后,一次性赋值给 @State penList

initData(): void {
  let data: PenStage[] = []
  data.push(this.makeStage(...))
  data.push(this.makeStage(...))
  // ...
  this.penList = data
}

这里使用了一个辅助方法 makeStage() 来降低代码重复,提高可读性:

makeStage(id: number, name: string, icon: string, year: string,
          origin: string, desc: string, details: string): PenStage {
  let item: PenStage = {
    id, name, icon, year, origin, desc, details
  }
  return item
}

三、ArkTS API 24 兼容性要点 —— 最重要的章节

在开发过程中,遇到了多个 API 版本差异问题。以下是 ArkTS API 24 的兼容性要点总结,这是本博客最核心的部分。

3.1 为什么 API 24 有特殊的限制?

HarmonyOS Next 的 ArkTS 是 TypeScript 的一个强约束子集。API 24 版本进一步收紧了语法支持范围,目的是:

  1. 运行时性能优化:静态分析可以生成更高效的字节码
  2. 内存安全:禁止某些动态特性,降低 OOM 风险
  3. 跨平台一致性:减少运行时行为差异

3.2 禁止使用的高级数组方法

API 24 不支持以下数组方法(部分列表):

方法 原因 替代方案
.map() 匿名函数 → 函数对象创建开销大 使用 for 循环 + push()
.forEach() 同上 使用 for 循环
.filter() 同上 使用 for 循环 + if
.reduce() 同上 使用 for 循环累加
.find() 同上 使用 for 循环 + break
.some() / .every() 同上 使用 for 循环 + 布尔标记
.includes() 运行时类型检查开销 使用 indexOf() !== -1
.startsWith() / .endsWith() 字符串方法,部分版本不支持 使用 indexOf() === 0 或正则

实战方案:在本应用中,初始化 10 条数据使用了 push() 而非数组字面量一次性赋值。push() 是 API 24 支持的,但 forEach() 遍历渲染时需要使用 ArkTS 提供的 ForEach 组件而非数组方法。

3.3 @Builder 的限制

ArkTS 的 @Builder 装饰器是构建 UI 片段的核心机制,但有两个关键限制:

限制一:@Builder 不能有泛型参数

错误写法:

@Builder
MyCard<T>(item: T) { ... }  // ❌ 编译错误

正确写法:必须显式指定具体类型

@Builder
StageCard(item: PenStage, isSelected: boolean) { ... }  // ✅

限制二:@Builder 内部不能使用可选链

错误写法:

@Builder
SomeBuilder() {
  Text(item?.name)  // ❌ 编译错误
}

正确写法:使用三元运算符或短路逻辑

@Builder
SomeBuilder() {
  Text(item ? item.name : '')  // ✅
}

3.4 关于 .italic() 方法

这是实际遇到的编译错误。ArkTS API 24 的 TextAttribute 类型没有 .italic() 方法,必须使用 .fontStyle(FontStyle.Italic) 替代:

// ❌ 错误
Text('引言文字').italic(true)

// ✅ 正确
Text('引言文字').fontStyle(FontStyle.Italic)

3.5 立即执行函数表达式(IIFE)

在 @State 初始化中,不能使用 IIFE:

// ❌ 错误
@State data: SomeType[] = (() => {
  // ...
})()

// ✅ 正确:在 aboutToAppear 中初始化
@State data: SomeType[] = []

aboutToAppear(): void {
  this.initData()
}

3.6 Record 类型

API 24 的 Record<K, V> 类型支持有限。某些场景下建议使用自定义 interface 替代:

// 可能有问题
@State map: Record<string, number> = {}

// 更稳妥的方案
interface StringNumberMap {
  [key: string]: number
}
@State map: StringNumberMap = {}

3.7 条件渲染语法

API 24 支持 if / else / else if 语句块用于条件渲染,但不支持 && 短路渲染:

// ✅ 正确
if (isSelected) {
  Column() { ... }
}

// 不确定的写法
// { isSelected && <Column>...</Column> }  // 兼容性存疑

3.8 循环渲染语法

使用 ForEach 组件代替 forEach() 方法:

// ✅ 正确
ForEach(this.penList, (item: PenStage) => {
  this.StageCard(item, ...)
}, (item: PenStage) => {
  return 'pen_' + item.id
})

// ❌ 错误:this.penList.forEach(...) 不兼容

ForEach 有三个参数:

  1. 数据源:数组
  2. 子组件生成函数(item, index?) → 返回组件
  3. 键值生成函数:用于列表 diff 优化,可选但强烈推荐

四、UI 布局逐层解析

4.1 整体布局结构

Column (根容器)
├── TitleBar (@Builder)
├── Scroll
│   └── Column
│       ├── 引言文字
│       ├── ForEach 渲染 10 个 StageCard
│       ├── SummarySection (@Builder)
│       └── PageFooter (@Builder)
└── Scroll (占满剩余空间)

4.2 标题栏(TitleBar)

@Builder
TitleBar() {
  Row() {
    Text('🖊️').fontSize(28)
    Text(' 笔的进化路线')
      .fontSize(22)
      .fontWeight(FontWeight.Bold)
      .fontColor('#2C3E50')
  }
  .width('100%')
  .padding(16)
  .backgroundColor('#F8F9FA')
}

设计思路

  • 使用 Emoji 🖊️ 作为视觉锚点,无需额外图标库
  • 固定灰色背景 #F8F9FA,与主内容区 #F5F7FA 微差区分
  • 深色字体 #2C3E50 保证可读性

4.3 时间轴卡片(StageCard)

这是应用的核心 UI 组件。每个卡片包含:

左侧时间轴

  • 圆形节点(Circle 组件),未选中时蓝色,选中时红色
  • 节点下方垂直连线(2px 宽,灰色 #BDC3C7
  • 选中时节点放大阴影(shadow + radius: 6

右侧内容卡片

  • 第一行:Emoji 图标 + 笔名 + 年代 + 展开/收起箭头
  • 第二行:发源地
  • 第三行:详细介绍文字
  • 条件渲染:选中时显示详情区域(带分割线和 details 内容)

交互逻辑

.onClick(() => {
  if (this.selectedIndex === item.id) {
    this.selectedIndex = -1  // 点击已选中的卡片 → 收起
  } else {
    this.selectedIndex = item.id  // 点击其他卡片 → 切换
  }
})

4.4 统计面板(SummarySection)

@Builder
SummarySection() {
  Column() {
    Divider()
    Text('📊 进化历程概览')
    Row() {
      this.StatChip('📅 跨越', '10 个阶段')
      this.StatChip('🌍 从埃及', '到智能时代')
      this.StatChip('✍️ 不变', '书写初心')
    }
    Text('从芦苇到智能,笔的进化也是人类文明的缩影。')
  }
}

三个 StatChip 均匀分布在行内,使用了 FlexAlign.SpaceEvenly 实现平均间距。

4.5 空状态处理

当数据尚未加载完成时,显示空状态占位:

@Builder
EmptyStage(msg: string) {
  Column() {
    Text('📝').fontSize(64).opacity(0.3)
    Text(msg).fontSize(16).fontColor('#999').margin({ top: 12 })
  }
  .width('100%')
  .height('60%')
  .justifyContent(FlexAlign.Center)
}

通过 this.penList.length === 0 条件控制,在实际设备上(数据即时初始化)几乎不可见,但作为健壮性设计保留。


五、Emoji 图标映射方案

5.1 为什么用字符串 key 而非直接写 Emoji?

在 ArkTS 组件中可以直接使用 Emoji(如 Text('🖊️')),但为了代码维护性,在数据层使用字符串 key,在展示层通过 iconToEmoji() 方法映射:

iconToEmoji(icon: string): string {
  if (icon === 'reed') return '🌾'
  if (icon === 'quill') return '🪶'
  if (icon === 'metal_nib') return '🔩'
  if (icon === 'fountain') return '🖋️'
  if (icon === 'ballpoint') return '🖊️'
  if (icon === 'marker') return '🖍️'
  if (icon === 'rollerball') return '✒️'
  if (icon === 'gel') return '💧'
  if (icon === 'stylus') return '✏️'
  if (icon === 'smart_pen') return '🧠'
  return '📝'
}

优点

  1. 数据层与展示层解耦——如需替换为自定义图标字体或 SVG,只需修改映射函数
  2. 集中管理,避免 Emoji 在不同编辑器中的渲染差异
  3. 便于单元测试——可以测试 key 覆盖率而无需处理 Unicode 渲染

5.2 Emoji 选择原则

每个 Emoji 的选择尽量贴近实物特征:

笔的种类 Emoji 选择理由
芦苇笔 🌾 稻禾 芦苇秆的视觉关联
羽毛笔 🪶 羽毛 直指材料来源
金属笔尖 🔩 螺母 金属机械感
钢笔 🖋️ 钢笔 标准书写符号
圆珠笔 🖊️ 圆珠笔 标准书写符号
记号笔 🖍️ 蜡笔 粗笔尖的联想
滚珠笔 ✒️ 笔尖 强调书写端
中性笔 💧 水滴 啫喱墨水的水润感
触控笔 ✏️ 铅笔 数字绘画工具的联想
智能笔 🧠 大脑 智能/科技属性

六、色彩设计方案

6.1 颜色体系

用途 色值 场景
页面背景 #F5F7FA 主背景,柔和灰蓝
标题栏背景 #F8F9FA 微差区分
卡片背景 #FFFFFF 纯白卡片,干净
主标题色 #2C3E50 深蓝灰,专注
辅助文字 #7F8C8D 灰蓝,次要信息
时间轴连线 #BDC3C7 浅灰,不抢眼
时间轴节点(默认) #3498DB 蓝色,中性
时间轴节点(选中) #E74C3C 红色,强调
分割线 #ECF0F1 极浅灰,弱分割
统计卡片背景 #F0F3F5 浅色,从属感

6.2 为什么选择这种色彩方案?

  • 低饱和度基调:以灰、白、蓝为主色调,避免视觉疲劳
  • 红色高亮:仅在选中状态使用红色(#E74C3C),形成强烈的注意力锚点
  • 自然渐变:从页面背景到卡片背景再到统计卡片,三级递进增加层次感

6.3 阴影设计

// 未选中
.shadow({ radius: 3, color: '#00000018' })

// 选中
.shadow({ radius: 8, color: '#00000018' })

选中时阴影半径从 3 扩大到 8,产生卡片"浮起"的视觉反馈。颜色使用低透明度黑色(18%),保证在浅色背景下自然柔和。


七、声明式 UI 与数据流

7.1 @State 数据驱动

应用中的两个核心状态:

@State penList: PenStage[] = []      // 数据源
@State selectedIndex: number = -1    // 当前选中项,-1 表示无选中
  • penListaboutToAppear() 中被填充,驱动 ForEach 渲染
  • selectedIndex 在点击卡片时更新,驱动 isSelected 条件判断

7.2 数据流向图

用户点击卡片
    ↓
.onClick() 更新 selectedIndex
    ↓
StageCard 的 isSelected 参数重新计算
    ↓
条件渲染:显示/隐藏 details 内容
    ↓
UI 自动刷新(无手动 DOM 操作)

整个过程完全符合声明式 UI 的单向数据流范式。

7.3 生命周期钩子

本应用使用了一个生命周期钩子:

aboutToAppear(): void {
  this.initData()
}

aboutToAppear 在组件被创建且即将显示时调用,是 ArkTS 中初始化数据的最佳位置。对应的还有 aboutToDisappear(),在本应用中未使用,但可用于清理资源(如定时器、监听器)以防止内存泄漏。


八、编译与调试经验总结

8.1 常见编译错误及解决方案

错误代码 错误信息 原因 解决方案
10505001 Property ‘italic’ does not exist API 24 无 .italic() 改为 .fontStyle(FontStyle.Italic)
10505001 Property ‘map’ does not exist API 24 禁用 .map() for + push() 替代
10505001 IIFE not supported 初始化中使用 IIFE 移到 aboutToAppear()
10505001 Generic @Builder @Builder 不支持泛型 使用具体类型
10505001 Optional chaining not supported ?. 语法 使用三元运算符

8.2 构建命令

# 完整构建
hvigorw --mode module -p module=entry -p product=default assembleHap

# 清理后构建(解决缓存问题)
hvigorw clean --mode module -p module=entry -p product=default assembleHap

8.3 IDE 缓存问题

在开发过程中,有一个常见陷阱:命令行构建成功,但 IDE(DevEco Studio)仍然显示红标

原因:IDE 的语法检查器与命令行编译器可能使用不同的缓存。

解决步骤

  1. Build → Clean Project(清理构建缓存)
  2. Build → Rebuild Project(重新构建,刷新 IDE 索引)
  3. 若仍有红标:File → Invalidate Caches → Invalidate and Restart
  4. 重启后 IDE 重新索引整个项目

8.4 Build JS 阶段的作用

构建输出中经常看到:

UP-TO-DATE :entry:default@BuildJS...

这个阶段负责编译 ArkTS 代码中的 JavaScript 桥接层。如果你的应用包含网络请求、文件访问等依赖 JS 引擎的功能,这个阶段会输出对应的 JS 包。在本应用中(纯 ArkTS 声明式 UI),这个阶段基本是空的。


九、代码优化与最佳实践

9.1 组件拆分策略

本应用将 @Builder 拆分为 5 个独立片段:

@Builder 职责 复用性
TitleBar 顶部标题 单次使用,但隔离使主代码更清晰
EmptyStage 空状态占位 条件渲染,优雅降级
StageCard 核心时间线卡片 循环渲染 10 次
SummarySection 底部统计 单次使用,内含 3 个 StatChip
StatChip 单个统计指标 被 SummarySection 复用 3 次
PageFooter 底部版权 单次使用

9.2 键值优化

ForEach 中,第三个参数是键值生成函数:

ForEach(this.penList, ..., (item: PenStage) => {
  return 'pen_' + item.id
})

为什么需要键值?

  • 当数据源发生变化(增/删/移动),框架需要识别哪些项是新增/删除/变更的
  • 使用稳定的键值(如 'pen_' + id)可以最小化 DOM 更新范围

键值选择原则

  • 使用唯一且稳定的值,不要使用索引
  • 键值前缀可以防止与其他列表的键值冲突

9.3 避免在 @Builder 中定义状态

每个 @Builder 不应该定义自己的 @State——它们是纯展示组件,所有的动态数据应该通过参数传入。这样确保了数据流的一致性,避免了状态分散在 UI 代码中难以追踪。

9.4 硬编码 vs 配置化

当前应用的数据直接写在代码中。如果需要支持多语言或从外部加载数据,可以:

  1. 抽离为 JSON 资源文件:放在 resources/rawfile/ 目录下
  2. 使用 Resource 类型:ArkTS 支持 $r() 引用资源
  3. 网络加载:通过 http 模块请求远程数据
// 未来扩展方向
import http from '@ohos.net.http'

fetchRemoteData(): void {
  let req = http.createHttp()
  req.request('https://api.example.com/pen-evolution', ...
    (err, data) => {
      if (data && data.responseCode === 200) {
        this.penList = JSON.parse(data.result as string)
      }
    })
}

十、与「图书阅读记录 APP」的对比

在同一项目中,我们还开发了「图书阅读记录 APP」,这里做一些对比分析:

维度 图书阅读记录 APP 笔的进化路线 APP
数据类型 表单录入 + 列表 时间线展示
数据规模 动态增长(用户录入) 固定 10 条(内置)
交互复杂度 输入框、搜索、删除 点击展开/收起
布局复杂度 搜索栏 + 列表 时间轴 + 卡片
主要难点 @State 的 IIFE 问题 @Builder 泛型限制
共用技术点 ForEach、@State、Scroll ForEach、@State、Scroll
共用兼容性点 .map() 替代、.italic() 替代 .map() 替代、.italic() 替代

两个应用共同展示了 ArkTS API 24 开发的全流程,覆盖了大多数常见场景。


十一、完整源码解读

以下是对 PenEvolution.ets 全文件的逐段解读。

11.1 入口与组件定义

@Entry
@Component
struct PenEvolution {
  • @Entry:标记该组件是页面的入口
  • @Component:声明为一个可复用的 ArkTS 组件
  • struct:ArkTS 使用结构体而非 class 定义组件

11.2 状态变量

@State penList: PenStage[] = []
@State selectedIndex: number = -1
  • @State 装饰的变量变化时会触发 UI 重新渲染
  • penList 存储所有笔的进化数据
  • selectedIndex 存储当前选中卡片的 id,-1 表示无选中

11.3 生命周期

aboutToAppear(): void {
  this.initData()
}

这是组件即将显示前的回调,执行时机在 build() 渲染之前。

11.4 数据初始化

initData(): void {
  let data: PenStage[] = []
  data.push(this.makeStage(0, '芦苇笔', 'reed', ...))
  data.push(this.makeStage(1, '羽毛笔', 'quill', ...))
  // ... 共 10 条
  this.penList = data
}

使用 let 创建局部变量,push 逐条添加,最后一次性赋值给 @State 变量。

11.5 辅助工厂方法

makeStage(id, name, icon, year, origin, desc, details): PenStage {
  let item: PenStage = { id, name, icon, year, origin, desc, details }
  return item
}

ES6 简洁属性语法({ id } 等同 { id: id })在 ArkTS 中可用。

11.6 Emoji 映射

iconToEmoji(icon: string): string {
  if (icon === 'reed') return '🌾'
  // ... 共 10 个分支
}

使用全 if 链而非 switch,因为 ArkTS API 24 对 switch 的一些模式支持不确定。

11.7 主 build() 函数

build() {
  Column() {
    this.TitleBar()
    if (this.penList.length === 0) {
      this.EmptyStage('加载笔的进化数据...')
    } else {
      Scroll() {
        Column() {
          Text('一支笔的进化史,就是半部人类文明史。')
            .fontStyle(FontStyle.Italic)
          ForEach(this.penList, (item) => {
            this.StageCard(item, this.selectedIndex === item.id)
          }, (item) => 'pen_' + item.id)
          this.SummarySection()
          this.PageFooter()
        }
      }
    }
  }
}

结构亮点

  • 空状态保护在 Scroll 外层,避免空数组渲染空 Scroll
  • ForEach 使用键值 'pen_' + id 确保 diff 效率
  • selectedIndex === item.id 作为布尔表达式传入,简洁直观

十二、ArkTS API 24 开发者备忘录

12.1 ✅ 推荐的写法

// 循环:for + push
let arr: string[] = []
for (let i = 0; i < 10; i++) {
  arr.push('item' + i)
}

// 条件渲染:if 语句
if (condition) {
  ComponentA()
} else {
  ComponentB()
}

// 字体样式
Text().fontStyle(FontStyle.Italic)

// ForEach
ForEach(data, (item) => { Component(item) }, (item) => key)

// @Builder
@Builder
MyBuilder(param: SpecificType) { ... }

12.2 ❌ 避免的写法

// 数组方法
data.map(x => ...)
data.forEach(x => ...)
data.filter(x => ...)
data.find(x => ...)
data.some(x => ...)
data.includes(x)

// 可选链
obj?.prop
arr?.[0]

// IIFE
@State x = (() => { ... })()

// 泛型 @Builder
@Builder
MyBuilder<T>(param: T) { ... }

// 不支持的样式方法
.italic(true)

12.3 调试技巧

  1. 打印日志:使用 console.info()console.warn()console.error()
  2. 断点调试:DevEco Studio 支持在 ArkTS 代码中设置断点
  3. 布局边界:暂时为组件设置 .border(1, Color.Red) 查看布局边界
  4. 隔离测试:将复杂组件的最小复现提取到独立文件测试

十三、未来扩展方向

13.1 功能扩展

  1. 数据持久化:使用 @ohos.data.preferences@ohos.data.distributedKVStore 存储用户笔记
  2. 图片展示:为每种笔增加历史照片或示意图
  3. 动画效果:卡片进入时使用 animateTo() 添加过渡动画
  4. 搜索过滤:按年代范围或发源地筛选
  5. 多语言:支持英文等其他语言

13.2 性能优化

  1. LazyForEach:数据量增大时(>50 条),使用 LazyForEach 替代 ForEach 实现虚拟列表
  2. @Prop/@Link:当数据层级更深时,使用 @Prop@Link 传递状态,减少不必要的渲染
  3. 状态合并:多个 @State 合并为一个对象,减少变更通知次数

13.3 架构演进

当前:单体 struct + @Builder
    ↓
中期:拆分为独立 Component(PenCard、TimeLine、StatPanel)
    ↓
远期:MVVM + 数据层(Repository + DataSource)

十四、总结

「笔的进化路线」应用虽然功能简洁,但覆盖了 ArkTS API 24 开发中的核心知识点:

  1. 数据模型设计:使用 interface 定义结构化类型
  2. 声明式 UI@State + 条件渲染 + ForEach 循环渲染
  3. @Builder 组件化:将 UI 片段封装为可复用的构建器
  4. API 24 兼容性:绕过 mapforEachitalic、IIFE 等限制
  5. 交互反馈:点击选中/取消选中、阴影变化、时间轴圆点颜色变化
  6. 编译调试:命令行构建、IDE 缓存清理、错误代码定位

更重要的是,这个应用展示了如何在 API 24 的限制下,依然写出清晰、可维护的代码——不使用高级数组方法不等于不能写出好代码,用 for + push() 同样优雅,用 if 链替代 map 同样可读。

笔的进化还在继续——从芦苇到智能笔,从模拟到数字。而我们的 HarmonyOS 开发之旅,也才刚刚开始。


Logo

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

更多推荐