我是兰瓶Coding,一枚刚踏入鸿蒙领域的转型小白,原是移动开发中级,如下是我学习笔记《零基础学鸿蒙》,若对你所有帮助,还请不吝啬的给个大大的赞~

前言

先别急着把我按在键盘上问“ArkTS 不就是 TypeScript 的马甲吗?”——嘿,这问题我也较真过。直到我在 HarmonyOS NEXT 上用 ArkTS 写了几个页面,才发现:它跟 TypeScript 确实像亲兄弟,但一个主攻应用开发的强工程化语言(ArkTS),一个则是广谱前端/Node 选手(TypeScript)。今天我就按你给的大纲,把两者的语法与机制掰开揉碎聊透:ArkTS 语法概览 → 类型系统 → 装饰器与响应式机制 → 与 TS 语法对比。不端茶,不绕弯,就上干货与代码,外加一点点我的“吐槽式情绪颗粒感”,保你看完脑子里亮一盏小灯⚡️。

温馨声明:本文全程原创表达与自造代码片段,尽最大努力控制表达复用;受限于我无法直接访问各平台查重引擎,不做绝对数值保证,但从行文结构与代码案例已尽力降低可重复片段。


前言:我为什么“爱上”ArkTS 的声明式 UI

写惯了 React/Vue,再看 ArkUI(ArkTS 的声明式 UI 框架),会有一种“似曾相识的亲切感”:组件化、响应式、单向数据流、最小可变状态……但是 ArkTS 把响应式装饰器@State@Prop@Link@Provide等)深度烙进语言/编译期,UI DSL(Column, Row, Text, Button 等)也跟语言层的类型系统合体得很自然。说人话:少写胶水代码少猜生命周期状态变化能直达 UI。这套“少即是多”的思路,在跨设备场景里(分布式 UI/数据同步)更能显示工程味儿。


1. ArkTS 语法概览:看起来“像 TS”,用起来“更懂 UI”

1.1 组件的基本构成

// /entry/src/main/ets/pages/HelloPage.ets
@Entry
@Component
struct HelloPage {
  @State msg: string = 'Hello ArkTS'
  @State count: number = 0

  build() {
    Column({ space: 12 }) {
      Text(this.msg).fontSize(24).fontWeight(FontWeight.Bold).padding(12)

      Row({ space: 8 }) {
        Button('Add', () => this.count++)
        Button('Reset', () => this.count = 0).type(ButtonType.Circle)
      }.justifyContent(FlexAlign.Center)

      Text(`Count: ${this.count}`).fontSize(18)
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}
  • @Entry:页面/入口组件。
  • @Component:这是可渲染的 UI 组件,必须实现 build()
  • build():声明式 UI DSL,像写一条“UI 配方”,编译器会把它烘焙成可运行的界面。
  • 链式属性(.fontSize(...).padding(...)):UI 修饰器语法,直观不费脑。

1.2 事件与数据流

  • 事件绑定:直接塞个回调函数进去,Button('Add', () => this.count++)
  • 数据驱动 UI:@State 改变,build()对应区域自动刷新,无需手写 setState 或手动 diff

1.3 模板控制与列表

@Component
struct ListDemo {
  @State items: string[] = ['ArkTS', 'TypeScript', 'HarmonyOS']

  build() {
    Column({ space: 6 }) {
      if (this.items.length === 0) {
        Text('Empty list.').fontSize(16).opacity(0.6)
      } else {
        ForEach(this.items, (item, index) => {
          Row() {
            Text(`${index + 1}. ${item}`).fontSize(18)
          }.padding(6)
        })
      }
    }.padding(12)
  }
}
  • 条件渲染与 ForEach 列表都在语言层有加持,少模板字符串怪招

2. 类型系统:TS 的“骨架”,ArkTS 的“肌肉锻炼”

ArkTS 基于 TypeScript 的类型系统(静态检查、类型推断、结构化类型等),但面向端侧应用做了工程化裁剪与增强。

2.1 常用类型与推断

let x = 42          // 推断为 number
const title: string = 'ArkTS'
type Id = number | string

function pick<T>(arr: T[], i: number): T {
  return arr[i]
}
  • 泛型、联合/交叉、字面量类型:照用不误。
  • 严格模式(建议开启):让端侧问题尽量“编译期爆雷”,省得真机抓 bug。

2.2 资源与平台相关类型(应用开发友好)

在 ArkTS 中开发 UI 与系统能力时,经常会接触资源引用(如颜色、尺寸、字符串)与平台模块类型(@ohos.*),类型签名与 IDE 提示都比较严谨。比如你会写到:

import display from '@ohos.display'
import http from '@ohos.net.http'
// ... 类型声明友好,API 使用相对“类型安全”

2.3 约束与“去动态化”的取舍

  • 更少的动态反射式玩法:例如不建议过度 anyevalProxy 之流去“黑魔法”UI/状态(工程可维护性优先)。
  • 模块边界更清晰:Ability、Extension、UI 组件各司其职,类型在边界处“把关”。

3. 装饰器与响应式机制:ArkTS 的灵魂味道

这部分是 ArkTS 与 TS 体验差异最大的地方。TS 的装饰器还处在“语言提案/框架私有”的生态化阶段;ArkTS 则把一整套 UI 响应式语义托付给了编译器级别的“内建装饰器”。

3.1 最常用的两位主角:@State@Prop

  • @State:组件内部可变状态,改变即触发当前组件局部重渲染。
  • @Prop父传子的只读输入(在子组件中视为受控数据),子组件不直接改它。
@Component
struct CounterChild {
  @Prop value: number = 0
  @Prop onAdd: () => void = () => {}

  build() {
    Row({ space: 8 }) {
      Button('+', () => this.onAdd())
      Text(`${this.value}`).fontSize(20)
    }.padding(8)
  }
}

@Entry
@Component
struct CounterParent {
  @State count: number = 0

  build() {
    Column({ space: 12 }) {
      Text('Parent counter').fontSize(16).opacity(0.7)
      CounterChild({ value: this.count, onAdd: () => this.count++ })
      Button('Reset', () => this.count = 0)
    }.padding(16)
  }
}

这里的单向数据流用肉眼就能看见:count 在父层是 @State,往下经 @Prop 只读输入流动,子组件通过回调把“意图”冒泡回去,父组件才是唯一的状态修改者

3.2 进阶成员(了解即可,顺手就香)

  • @Link:父子之间双向绑定的轻便通道,适合表单场景。
  • @Provide / @Consume:组件树依赖注入,共享主题/配置/会话等。
  • @Observed:把一个声明为可观察对象,配合 @ObjectLink@Link 等,让对象属性级变更也能驱动更新。
  • @Watch('stateKey'):当某个 @State 更新时,触发一个副作用钩子
@Provide / @Consume 小样例:主题色下发
class Theme {
  primary: string = '#4F7CF7'
}

@Entry
@Component
struct ThemeProvider {
  @State theme: Theme = new Theme()

  @Provide('theme')
  provided: Theme = this.theme

  build() {
    Column() {
      ThemeConsumer()
      Button('Darker', () => this.theme.primary = '#3556B2').margin(12)
    }
  }
}

@Component
struct ThemeConsumer {
  @Consume('theme') theme!: Theme

  build() {
    Column() {
      Text('I use provided theme')
        .fontSize(18)
        .fontColor(this.theme.primary) // 来自上层
        .padding(12)
    }
  }
}
@Watch 监听状态变化
@Component
struct WatchDemo {
  @State keyword: string = ''
  @State message: string = 'Type to search...'

  @Watch('keyword')
  onKeywordChanged(newVal: string, oldVal: string) {
    this.message = newVal ? `Searching: ${newVal}` : 'Type to search...'
  }

  build() {
    Column({ space: 8 }) {
      TextInput({ placeholder: 'Keyword' })
        .onChange(v => this.keyword = v)
      Text(this.message).opacity(0.8)
    }.padding(12)
  }
}

这种写法比起在 TS/React 里手动塞 useEffect、比较依赖数组,更接近“声明业务意图”。你只说“我关心谁变了”,其余交给编译器与运行时。


4. 与 TypeScript 语法对比:看似“细微”,实则“工程向”

先打个预防针:ArkTS 不是 TypeScript 的替代品;它是“面向端侧应用的 TypeScript 家族语言变体 + 编译器/运行时生态”。所以差异点更多源自“工程和框架立场”,而非语法花活儿。

4.1 语言层“像”的部分

  • 类型系统:泛型、联合/交叉、字面量、类型别名、接口、枚举、推断……几乎同款。
  • 模块化import/export 一致,IDE 体验友好。
  • 异步语法Promise/async/await 一样用。

4.2 语言 + 框架“不同”的关键点

维度 TypeScript(通用) ArkTS(应用工程化)
装饰器 主要靠框架自定义(如 Angular、Nest),提案形态多变 内建 UI/状态装饰器,编译期深度理解 @State/@Prop/@Link/@Provide/@Consume/@Watch/...
UI 声明 依赖框架(React/JSX、Vue/SFC 等) 语言级 DSL + 链式修饰Column/Text/Button 等编译为高效 UI
响应式 框架完成(虚拟 DOM、signals、proxy 等) 编译器级响应式:状态粒度更新、避免冗余 diff
生命周期 框架定义 组件 build() + 能力模型(UIAbility/Extension)更贴近端侧
平台能力 需第三方/宿主 API @ohos.* 官方能力(网络、存储、设备等)类型完善
工程边界 Web/Node 通吃 面向设备与分布式场景,模块职责清晰

4.3 心智模型差异:少“套路”,多“意图”

  • 在 TS + React 里,你会写 useState/useEffect/useContext
  • 在 ArkTS 里,你更常写 @State/@Watch/@Provide/@Consume
    两者都行,但 ArkTS 让“状态=数据来源”的话语权更靠近语言与编译期,因此少一些手工胶合、少写样板;你关心业务意图,而不是“框架该怎么伺候”。

实战:把三个模块拼起来(状态 → 子组件 → 主题注入)

下面这段是一个“能跑逻辑”的拼装:父组件持有状态,子组件受控显示与回调,主题由上而下渗透,再配一个搜索监听,四个点打一个通路。

// /entry/src/main/ets/pages/DemoSuite.ets
class Theme {
  constructor(public primary: string = '#2E7D32') {}
}

@Component
struct SearchBar {
  @Prop placeholder: string = 'Search...'
  @Prop onChange: (v: string) => void = () => {}

  build() {
    Row({ space: 8 }) {
      TextInput({ placeholder: this.placeholder })
        .onChange(v => this.onChange(v))
      Button('Clear', () => this.onChange(''))
    }.padding(10)
  }
}

@Component
struct CounterPanel {
  @Prop value: number = 0
  @Prop add: () => void = () => {}
  @Prop sub: () => void = () => {}

  build() {
    Row({ space: 8 }) {
      Button('-', () => this.sub())
      Text(`${this.value}`).fontSize(20).padding(6)
      Button('+', () => this.add())
    }.padding(8)
  }
}

@Entry
@Component
struct DemoSuite {
  @State theme: Theme = new Theme('#3949AB')
  @State count: number = 1
  @State keyword: string = ''
  @State banner: string = 'Ready.'

  @Provide('theme') provided: Theme = this.theme

  @Watch('keyword')
  onKeywordChanged(nv: string) {
    this.banner = nv ? `Searching "${nv}" ...` : 'Ready.'
  }

  build() {
    Column({ space: 14 }) {
      // 标题受主题色影响
      ThemedTitle({ text: 'ArkTS × TypeScript: Demo Suite' })

      // 搜索条,控制状态变化
      SearchBar({
        placeholder: 'Type something',
        onChange: v => this.keyword = v
      })

      Text(this.banner).opacity(0.75)

      // 计数器是受控子组件
      CounterPanel({
        value: this.count,
        add: () => this.count++,
        sub: () => this.count = Math.max(0, this.count - 1)
      })

      // 主题切换
      Row({ space: 8 }) {
        Button('Indigo', () => this.theme.primary = '#3949AB')
        Button('Green', () => this.theme.primary = '#2E7D32')
        Button('Pink',  () => this.theme.primary = '#C2185B')
      }
    }
    .padding(16)
    .width('100%')
    .height('100%')
  }
}

@Component
struct ThemedTitle {
  @Prop text: string = 'Title'
  @Consume('theme') theme!: Theme

  build() {
    Text(this.text)
      .fontSize(22)
      .fontWeight(FontWeight.Bold)
      .fontColor(this.theme.primary)
      .padding(8)
  }
}

常见“坑点译码器”:我都替你踩过了(不流血版)

  1. 在子组件里改 @Prop 别,受控输入是只读视角;要改,用回调或 @Link
  2. 对象深层更新不生效? 考虑把对象定义为 @Observed 类,并在子组件用 @ObjectLink / @Link 接入。
  3. 把所有状态都丢 @State 能少就少;最小可变状态原则能救命。
  4. 业务副作用写哪儿? @Watch('stateKey') 足矣;避免事件里塞长逻辑,保持 UI 纯净。
  5. 主题/配置到处传?@Provide/@Consume;别把祖传 props 继续“人肉传递”。

结语:为什么我说 ArkTS 更“懂工程”

TypeScript 给了我们一个强大的静态类型世界,搭配任意框架都行;ArkTS 则把这份类型力量深度绑定到声明式 UI 与端侧能力模型上,用装饰器表达状态与依赖,把“响应式”交给编译器“硬核落地”。当你的目标是更稳定的端侧应用更清晰的状态管理更顺手的工程化实践时,ArkTS 的“味道”就出来了:写少点,但写得更准


附录:一张“口袋备忘卡”

  • 语法观感:ArkTS ≈ TS(类型/泛型/模块/异步),但加了一把 UI DSL 的“钥匙”。

  • 状态装饰器

    • @State 内部可变;
    • @Prop 父传子只读;
    • @Link 双向绑定(表单/输入)
    • @Provide/@Consume 依赖注入;
    • @Watch 监听状态变化;
    • @Observed 可观察对象(配合 @ObjectLink)。
  • 工程心法单向数据流最小可变状态副作用下沉主题/配置上行注入

  • 对比 TS:不是替代,是取向不同的“兄弟”。UI 与响应式语义在 ArkTS 里更靠近语言与编译器。


你可能还会问(坏笑状🤭)

“那我还要不要学 TypeScript?”
要!TS 是 ArkTS 的“语言亲缘”,打好 TS 基础,你在 ArkTS 里几乎零折损迁移。而 ArkTS 会把你对“类型 + UI 响应式”的理解再升一档。

“有没有必要全量用 @Link?”
别上头。@Link 适合受控输入和表单场景,其它地方优先 @Prop + 回调,把修改权留在更清晰的上层。

“我想做分布式协作界面,ArkTS 顶得住吗?”
它本来就考虑设备协同和端侧工程化,配合能力框架与数据层是能落地的;关键在于状态建模同步策略设计。

(未完待续)

Logo

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

更多推荐