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

前言

先表个态:我是真心喜欢 TypeScript 的——类型收窄丝滑、泛型推导聪明、装饰器也能玩花活儿。可一脚踏进 ArkTS 的世界,我还是“哦豁”了一下:看起来像 TS,写起来像 UI 直抒胸臆。它把“声明式 UI + 响应式状态 + 组件语义”绑得紧紧的,语法层面又加了不少“只此一家”的扩展。于是就有了这篇“半研究、半唠嗑”的长文:我会按你给的提纲,把 ArkTS 的语法地基挖开,再跟 TypeScript 挨个对比,尤其把 声明式 UI、@State、@Prop 等装饰器背后的响应式机制讲透。放心,有大量可跑的代码片段,还附了我在项目里踩过的坑和解决套路。

如果你也曾在 .ets 文件里一边写 Column(){...} 一边怀疑人生——那这篇就是给你的“回魂指南”。😄

目录(你可以当它是进度条)

  • ArkTS 语法概览
  • 类型系统:和 TS 的“像”与“不像”
  • 装饰器与响应式机制:@State、@Prop、@Link、@Provide/@Consume、@Watch、@Builder
  • 与 TypeScript 语法对比:对照示例 + 心法总结
  • 实战小节:把“声明式 UI + 状态流”跑起来(可直接粘贴)
  • 进阶:生命周期、AppStorage、表单与列表的性能细节
  • 易错点与优化心法(含对照清单)
  • 收尾:我为什么说写 ArkTS 最像“把 UI 当数据写”

ArkTS 语法概览:它到底“像 TS 的哪里,不像哪里的谁”

一句话版:ArkTS ≈ TypeScript 的静态类型能力 + 声明式 UI DSL + 组件状态装饰器系统。文件后缀是 .ets,编译链走 ArkCompiler,产物直接服务于 HarmonyOS 的渲染与运行时。它和 TS 的“亲缘关系”很近,但在组件定义、UI 语法、装饰器语义上做了“强意见”的增补:

  1. 组件用 @Component struct 声明

    • struct 不是 TS/JS 的语法,而是 ArkTS 的组件定义语法
    • 组件必须实现 build(),UI 用函数式 DSL 写出来(Column { ... }Text('...') 等)。
    • 不能像 TS 那样 new MyComponent();组件是声明式使用MyComponent({ propA: 1 })
  2. 页面入口用 @Entry 指定

    • 入口组件常配合 UIAbility 使用。@Entry 让系统知道这是页面根,有自己的生命周期钩子(如 aboutToAppearaboutToDisappearonPageShowonPageHide)。
  3. 响应式状态靠装饰器

    • @State:组件本地可变状态;变化会触发该组件及依赖子树重建。
    • @Prop:父传子的只读输入(对子组件而言);父变子随。
    • @Link:父子双向联动(要慎用);
    • @Provide/@Consume:跨层级依赖注入式状态分发;
    • @Watch:监听指定字段改变时的副作用回调;
    • @Builder:把一段 UI 结构抽成“可复用的 UI 生成函数”。
  4. 声明式 UI DSL

    • 不写 <div/>,也不写 XML,而是在 ArkTS 里直接写 UI

      Column() {
        Text(this.title).fontSize(20).fontWeight(FontWeight.Bold)
      }
      
    • 组件方法链是属性配置器build() 返回的不是虚拟 DOM,而是被编译器编译为高效 UI 指令

  5. 运行期特征

    • ArkTS 编译产物由系统渲染引擎承接;状态变化驱动最小化 UI 刷新
    • 由于“语言-UI-状态”三位一体,语法限制比 TS 更强(比如组件字段必须受状态系统管理,随意引用外部可变变量会被限制/告警)。

是不是有点儿“写代码像写设计稿”的感觉?这就是 ArkTS 给我的第一印象:把“UI 是数据”的理念落到了语言层面

类型系统:和 TypeScript 的“像”与“不像”

还能不能用 TS 的那些“家伙事儿”?

,而且大多数时候用得很顺。ArkTS 基于 TS 的类型系统,常见的语法糖和能力都在:

  • 基本类型string | number | boolean | null | undefined | symbol | bigint 均可用。
  • 类型组合:联合、交叉、字面量类型都 OK。
  • 结构化类型interfacetype、索引签名、泛型泛用。
  • 函数类型:泛型函数、重载、this 参数类型。
  • 类与可见性classpublic/protected/privatereadonlyabstract
  • 枚举:数值/字符串枚举可用;实际项目更推荐字面量联合替代。
  • 类型守卫intypeofinstanceof、用户自定义守卫。
  • 工具类型PartialPickOmit 等常见工具类型依旧舒服。

哪些地方“有点不一样”?

  1. 组件状态字段必须显式纳入响应系统

    • 在普通 TS 里,类字段想怎么变就怎么变;
    • 在 ArkTS 组件里,影响 UI 的字段必须用装饰器@State@Prop@Link…)声明;不受管控的可变字段不会触发 UI 更新,编译器也会盯你。
  2. UI DSL 的链式 API 需通过类型约束

    • 例如 Text(...).fontSize(20).fontWeight(FontWeight.Bold)
      每个链条都在类型上标注可用属性,非法链式调用会编译期报错
    • 这和 TS 在 React 里写 JSX 的类型体验类似,但 ArkTS 强制性更强。
  3. struct 语义独立于 TS

    • struct 不是 TS 的关键字,在 ArkTS 里它专用于组件定义;
    • struct 里有自己的生命周期与 UI build() 约定,不能被随意实例化
  4. 资源系统类型化

    • 访问资源(文案、颜色、图片)常通过 $r('app.string.xxx'),其类型与可选性由资源表决定;
    • 这在 TS 世界里通常依赖代码生成或第三方声明,而 ArkTS 语言侧和编译侧协同更紧。

一个小例子:类型收窄在 ArkTS 里的“直觉感”

type Todo = { id: string; title: string; done: boolean }

function toggle(t: Todo | undefined): Todo | undefined {
  if (!t) return undefined;       // 类型收窄后,下面对 t 的访问安全
  return { ...t, done: !t.done }
}

这段 TS/ArkTS 通吃。但要挂到 UI 上,ArkTS 会“更较真”:你如果把 toggle() 的结果回写到组件字段,最好这个字段是 @State,否则 UI 不会跟着动。

装饰器与响应式机制:把“数据即 UI”落实到字段

@Entry 与 @Component:登场与身份

@Entry
@Component
struct TodoPage {
  // ...
  build() {
    Column() { Text('Hello ArkTS') }
  }
}
  • @Entry:告诉系统“我是一页”。
  • @Component:告诉编译器“我是一段可复用 UI,受状态系统管控”。
    两者常常并存,但页面根需要 @Entry,普通子组件不用。

@State:组件本地的“会触发刷新”的字段

@Component
struct Counter {
  @State count: number = 0

  build() {
    Column() {
      Text(`Count: ${this.count}`)
      Button('+1').onClick(() => this.count++)
    }
  }
}
  • 写到 @State 就是宣誓效忠响应式系统:每次变更都会触发本组件及依赖子树的 UI 重建
  • 心法:小而准。把真正驱动 UI 的字段收紧在 @State,别用海量状态“淹没”组件。

@Prop:父传子的只读输入

@Component
struct TodoItem {
  @Prop item: { id: string; title: string; done: boolean }

  build() {
    Row() {
      Text(this.item.title).opacity(this.item.done ? 0.6 : 1)
    }
  }
}
  • 子组件不可直接修改 @Prop
  • 父组件变,子组件会随之刷新(单向数据流)。

@Link:父子“双向牵手”,强大且要慎用

@Component
struct Child {
  @Link value: number

  build() {
    Row() {
      Button('child++').onClick(() => this.value++)
    }
  }
}

@Component
struct Parent {
  @State n: number = 0
  build() {
    Column() {
      Text(`n=${this.n}`)
      Child({ value: this.n })   // @Link 绑定:父子共享同一份“可变引用”
    }
  }
}
  • @Link 让子组件可直接改父状态(在一些输入控件场景很香);
  • 但双向绑定容易让数据流复杂化,只在输入同步明确的场景用,例如表单输入框、开关等。

@Provide / @Consume:跨层级“状态注入”

@Component
struct AppRoot {
  @Provide theme: 'light' | 'dark' = 'light'

  build() {
    Column() {
      Header()
      Content()
    }
  }
}

@Component
struct Content {
  @Consume theme: 'light' | 'dark'

  build() {
    Column() {
      Text('Theme from root: ' + this.theme)
    }
  }
}
  • @Provide 在祖先组件挂变量,后代用 @Consume 取;
  • 这相当于 React 的 Context,但更靠语言层。用它统一主题、语言、用户会话,省心。

@Watch:侦听某个字段的变化

@Component
struct SearchBox {
  @State keyword: string = ''

  @Watch('keyword')
  onKeywordChange(oldV: string, newV: string) {
    // 触发防抖搜索、日志等副作用
  }

  build() {
    TextInput({ text: this.keyword, placeholder: '搜索' })
      .onChange(v => this.keyword = v)
  }
}
  • @Watch 避免把副作用塞进 build()
  • 切记:副作用要幂等,避免 Watch → 改状态 → 再触发 Watch 的死循环。

@Builder:把“UI 片段”抽为可复用生成器

@Builder
function Section(title: string, content: () => void) {
  Column() {
    Text(title).fontSize(18).fontWeight(FontWeight.Medium)
    content()
  }.padding(12).borderRadius(12)
}

@Component
struct Demo {
  build() {
    Section('今天要做啥', () => {
      Text('1. 写代码  2. 喝咖啡')
    })
  }
}
  • 类似“无状态子树”;
  • 重复 UI 组合模式抽象起来,比复制粘贴优雅得多。

与 TypeScript 语法对比:用“并排小样”说话

1. 组件定义方式

TypeScript(React)

type Props = { title: string }
function Card({ title }: Props) {
  const [count, setCount] = useState(0)
  return <div className="card">
    <h3>{title}</h3>
    <button onClick={() => setCount(count + 1)}>{count}</button>
  </div>
}

ArkTS

@Component
struct Card {
  @Prop title: string
  @State count: number = 0

  build() {
    Column() {
      Text(this.title).fontSize(18)
      Button(`${this.count}`).onClick(() => this.count++)
    }
    .padding(12).borderRadius(12)
  }
}

要点对比

  • React 用函数 + Hook 表达状态;ArkTS 用 struct + 装饰器。
  • ArkTS 没有 JSX,UI 语法直接是 DSL;方法链配置样式/属性。
  • 两者都声明式;ArkTS 的编译器更知道“你在搭建 UI 树”,因此很多错误在编译期就挡下来了。

2. 父子传参与更新

TypeScript(React)

function App() {
  const [n, setN] = useState(0)
  return <Child n={n} onInc={() => setN(n + 1)} />
}

ArkTS

@Component
struct App {
  @State n: number = 0
  build() {
    Child({ n: this.n, onInc: () => this.n++ })
  }
}

@Component
struct Child {
  @Prop n: number
  onInc: () => void

  build() {
    Row() {
      Text(`${this.n}`)
      Button('+1').onClick(() => this.onInc())
    }
  }
}

要点对比

  • 事件回调传递很像,但 ArkTS 会强约束哪些字段能驱动刷新(@Prop/@State),逻辑字段(如 onInc)不参与响应式依赖。
  • 这能让“什么是状态、什么是纯逻辑”在语义上更清楚。

3. 双向绑定(TS 里多靠受控组件;ArkTS 原生有 @Link)

TypeScript(React 受控输入)

<input value={name} onChange={e => setName(e.target.value)} />

ArkTS(@Link)

@Component
struct NameEditor {
  @Link name: string

  build() {
    TextInput({ text: this.name, placeholder: '你的名字' })
      .onChange(v => this.name = v)
  }
}

要点对比

  • ArkTS @Link 让父子共享“同一份可变引用”,语义上更像“受控 + 同步写回”。
  • 但是!业务复杂时更建议单向数据流@Link 只放在输入控件等闭环场景。

4. 生命周期

TypeScript(React)useEffect 承担大部分副作用与生命周期逻辑。
ArkTS:组件有明确的钩子函数,常见有:

  • aboutToAppear():组件即将可见,做数据初始化。
  • aboutToDisappear():组件离场前清理资源。
  • onPageShow()/onPageHide():页面维度的可见性切换。

要点对比

  • ArkTS 的钩子贴近页面/组件语义,不需要你自己“模拟生命周期”。
  • 但也要避免把副作用塞到 build() 里——那是 UI 的声明阶段

实战:用 ArkTS 写一个“声明式清单项”,顺手演示 @State 与 @Prop

目标:父组件维护清单数组;子组件展示单项,点击勾选;演示父传子本地状态的区分。

// types.ets
export interface Todo {
  id: string
  title: string
  done: boolean
}
// TodoItem.ets
import { Todo } from './types'

@Component
export struct TodoItem {
  @Prop item: Todo
  onToggle: (id: string) => void

  build() {
    Row() {
      Checkbox({ name: 'done', group: 'todo', select: this.item.done })
        .onChange(() => this.onToggle(this.item.id))
      Text(this.item.title)
        .decoration({ type: this.item.done ? TextDecorationType.LineThrough : TextDecorationType.None })
        .opacity(this.item.done ? 0.6 : 1)
        .margin({ left: 8 })
        .layoutPriority(1)
    }
    .padding(10)
    .borderRadius(12)
    .backgroundColor('#ffffff')
    .margin({ bottom: 8 })
  }
}
// TodoList.ets
import { Todo } from './types'
import { TodoItem } from './TodoItem'

@Entry
@Component
export struct TodoList {
  @State input: string = ''
  @State list: Todo[] = [
    { id: 't1', title: '买牛奶', done: false },
    { id: 't2', title: '写 ArkTS 笔记', done: true }
  ]

  private add() {
    const title = this.input.trim()
    if (!title) return
    const item: Todo = { id: `t_${Date.now()}`, title, done: false }
    this.list = [item, ...this.list]          // 注意:用不可变写法触发对比
    this.input = ''
  }

  private toggle(id: string) {
    this.list = this.list.map(t => t.id === id ? { ...t, done: !t.done } : t)
  }

  build() {
    Column() {
      Row() {
        TextInput({ text: this.input, placeholder: '记录一下?' })
          .onChange(v => this.input = v).width('70%')
        Button('添加').onClick(() => this.add())
      }.margin({ bottom: 12 }).width('100%').justifyContent(FlexAlign.SpaceBetween)

      List() {
        ForEach(this.list, (it: Todo) => {
          TodoItem({ item: it, onToggle: (id: string) => this.toggle(id) })
        }, it => it.id)
      }
    }.padding(16)
  }
}

几句经验

  • 不可变更新(返回新数组/对象)更容易让编译器/运行时定位变更范围;
  • @State 一旦更改,该组件的 build() 会重跑,ArkUI 做差量更新;
  • 子组件 @Prop 随父刷新,自身不维护状态,易懂、易测。

进阶语法与机制:把“响应式”理解到骨子里

1. 依赖收集与重建边界(高层直觉版)

  • 影响 UI 的字段都被装饰器注册(@State/@Prop/@Link/...);
  • build() 中凡是读取了这些字段的分支,都会被记录为依赖;
  • 字段变化 → 框架根据依赖关系精准触发重建
  • @Provide/@Consume 的变更会沿层级扩散给使用它的组件

这和“React Fiber + Hook 依赖”思路类似,但 ArkTS/ArkUI 是语言 + 编译器参与,依赖跟踪更靠前。

2. AppStorage 与跨页面共享

  • 需求:登录态、主题、全局设置要跨页面共享;
  • 做法:使用 AppStorage(全局 KV),或 @Provide 到根组件:
// 根组件
@Component
struct Root {
  @Provide userName: string = ''
  build() { ... }
}

// 任意子孙
@Component
struct Profile {
  @Consume userName: string
  build() { Text('Hello ' + this.userName) }
}

AppStorage 则可以通过 API 设置 / 订阅,跨页面生效。当你的状态需要“全局单例”时,AppStorage 更合适;当你只想在某个子树内共享,@Provide/@Consume 更优雅。

3. 列表性能:ForEach、Item 复用与“别滥用阴影”

  • 大列表尽量稳定 key,示例里 ForEach(..., it => it.id) 很关键;
  • 卡片阴影、毛玻璃是渲染大户,移动端要克制
  • 交互时优先做位移/透明度动画,让它落在合成层。

4. 表单:@Link 的合理边界

  • 单输入控件用 @Link 很顺;

  • 复杂表单建议局部状态 + 单向提交流

  • @Watch 可做节流/防抖提交:

    @Watch('form')
    onFormChange() {
      // debounce(() => submit(this.form), 300)
    }
    

与 TS 的“潜规则”比较清单(速查表)

维度 TypeScript(常见搭配:React) ArkTS
UI 表达 JSX(运行时解释 + 虚拟 DOM) ArkUI DSL(编译期转 UI 指令)
组件定义 函数组件/类组件 @Component struct
页面入口 路由配置/顶层组件 @Entry 入口组件 + UIAbility
状态 Hook/外部状态库 @State/@Prop/@Link/@Provide/@Consume
双向绑定 受控组件手写回调 @Link 原生双向
副作用 useEffect @Watch + 生命周期钩子
全局共享 Context/Redux AppStorage + @Provide/@Consume
资源访问 字符串常量/代码生成 $r('app.string.xxx') 有资源表支持
列表 Key key 属性 ForEach 的 key 选择器
类型系统 标准 TS 基于 TS,UI/状态有额外约束
编译产物 JS + 运行时框架 ArkCompiler 产物直连系统渲染

易错点与“反直觉”总结(都是我真踩过)

  1. 把业务变量写成普通字段

    • 结果:UI 不刷新。
    • 解决:影响 UI 的字段都改成 @State/@Prop/@Link 等受管控字段。
  2. build() 里做副作用

    • 结果:重建频繁导致抖动或死循环。
    • 解决:用 @Watch / 生命周期钩子;build() 只生成 UI。
  3. @Link 到处飞

    • 结果:数据流像绕口令。
    • 解决:以单向为主,@Link 限定在输入控件等“小闭环”。
  4. ForEach 的 key 不稳定

    • 结果:滚动/勾选错乱,重用失败。
    • 解决:使用业务稳定 ID
  5. 过度依赖超重组件样式

    • 结果:掉帧。
    • 解决:阴影/模糊谨慎;动画尽量位移/透明度,避免反复布局。
  6. 生命周期混淆

    • 结果:数据没拉到或泄漏。
    • 解决:初始化放 aboutToAppear();计时器/订阅在 aboutToDisappear() 清理。

进阶对比:装饰器语义与 TS 装饰器的“同名不同命”

  • TS 装饰器(实验特性)更偏“元编程/元数据”用途:比如注入 DI、加元数据给反射用;
  • ArkTS 装饰器直接参与响应式系统@State/@Prop/... 是编译-运行时都理解的“状态契约”。

换句话说:在 ArkTS 里,装饰器不是“锦上添花”,而是“基本法”。

一个“混合体”对照:TypeScript 写 React vs. ArkTS 写相同组件

React(TS)

type Todo = { id: string; title: string; done: boolean }

export function List() {
  const [list, setList] = useState<Todo[]>([])
  const [text, setText] = useState('')

  const add = () => {
    if (!text.trim()) return
    setList([{ id: Date.now()+'' , title: text, done: false }, ...list])
    setText('')
  }

  const toggle = (id: string) =>
    setList(list.map(t => t.id === id ? { ...t, done: !t.done } : t))

  return <div className="wrap">
    <input value={text} onChange={e => setText(e.target.value)} />
    <button onClick={add}>Add</button>
    <ul>
      {list.map(it => (
        <li key={it.id} onClick={() => toggle(it.id)}>
          <span style={{ textDecoration: it.done ? 'line-through' : 'none' }}>{it.title}</span>
        </li>
      ))}
    </ul>
  </div>
}

ArkTS

type Todo = { id: string; title: string; done: boolean }

@Entry
@Component
struct List {
  @State list: Todo[] = []
  @State text: string = ''

  private add() {
    const t = this.text.trim()
    if (!t) return
    this.list = [{ id: `${Date.now()}`, title: t, done: false }, ...this.list]
    this.text = ''
  }

  private toggle(id: string) {
    this.list = this.list.map(it => it.id === id ? { ...it, done: !it.done } : it)
  }

  build() {
    Column() {
      Row() {
        TextInput({ text: this.text, placeholder: 'Add...' })
          .onChange(v => this.text = v).layoutPriority(1)
        Button('Add').onClick(() => this.add())
      }.margin({ bottom: 12 })

      List() {
        ForEach(this.list, (it: Todo) => {
          Row() {
            Text(it.title)
              .decoration({ type: it.done ? TextDecorationType.LineThrough : TextDecorationType.None })
          }
          .onClick(() => this.toggle(it.id))
          .padding(8)
        }, it => it.id)
      }
    }.padding(16)
  }
}

观察

  • 两边逻辑几乎一致,但ArkTS 的 UI 与状态语义天然对齐@State 改变 → build() 重跑 → DSL 做差量更新;
  • React 依赖 Hook 与虚拟 DOM;ArkTS 依赖装饰器与编译器内建的 UI 指令集。

小技巧:把 TypeScript 心智迁移到 ArkTS 的“三步走”

  1. 先把“状态是什么”写出来:把会影响 UI 的字段全部列成 @State 或其变体;
  2. 把“数据怎么流”说清楚:父 → 子用 @Prop,极少数场景用 @Link;跨层级用 @Provide/@Consume
  3. 把副作用挪走:用 @Watch 和生命周期钩子管理,build()只写 UI

FAQ 片段:你可能会问的那些“细琐但关键”的差别

Q1:ArkTS 里能不能像 TS 那样写“自由的 class 逻辑层”?
A:能,但别把 class 当组件。普通 class 写业务、工具、网络层都行;组件仍然用 @Component struct

Q2:泛型、条件类型这些 TS“黑魔法”还能用吗?
A:基本能用。在 UI DSL 层面泛型不常见,但在数据/服务/工具层你照常写,ArkTS 编译器能识别。

Q3:装饰器是“运行时黑魔法”吗?
A:不是。ArkTS 的装饰器是编译期 + 运行时共同约定,“声明即契约”。你不用担心摇摇欲坠的反射元数据。

Q4:我能在 build() 外生成一段动态 UI 再拼进去吗?
A:用 @Builder,或用子组件表达复合 UI;记得让它们各自管理状态依赖,这样重建边界更清楚。

一点“工程化”私货:如何让 ArkTS 项目更像你熟悉的 TS 大型工程

  • 模块边界清晰/components 放纯 UI 组件;/services 放网络/存储;/models 放类型;/stores 放共享状态(可基于 AppStorage 封一层)。
  • 资源类型可视化:用脚本检查资源键名与 $r() 调用的一致性。
  • 测试:UI 层做“交互-状态”用例(点击 → 状态变化);逻辑层纯 TS 测试。
  • 性能日志:在生命周期里埋点,统计 build() 次数与耗时;对大列表做切片渲染。

“差异研究”的终极版图(总结)

  1. 语言层面把 UI 做成“一等公民”

    • TS 世界里 UI 更多是框架能力(JSX/虚拟 DOM);
    • ArkTS 把 UI DSL 和状态装饰器写进了“语言-编译器-运行时”的统一闭环。
  2. 响应式是显式契约,不是隐式约定

    • @State/@Prop/@Link/@Provide/@Consume/@Watch 让依赖与边界看得见;
    • 对比 TS + React 的 Hook 心智,ArkTS 的“谁变了触发谁”更可控。
  3. 装饰器在 ArkTS 不是点缀,是地基

    • TS 装饰器更多给库作者玩;
    • ArkTS 装饰器是你写 UI 的必修课
  4. 迁移成本不高,但使用姿势要换

    • 语法手感 80% 一致;
    • 但你得把“状态、生命周期、副作用、UI 构建”按 ArkTS 的规则来。

结语:为什么我说写 ArkTS 像“把 UI 当数据写”

在 TypeScript 的大草原上,我们习惯用框架补齐“UI 即数据”的故事;到了 ArkTS,语言自己站出来讲故事。你要做的,是把状态讲清楚,把数据流讲明白,剩下的“绘制”“刷新”“最小化更新”,交给编译器与运行时去表演。

倘若你也喜欢那种“眼见为实”的控制感——改一个值,UI 就乖乖地以最小代价响应——ArkTS 会让你乐此不疲。下次谁再问你:“ArkTS 和 TypeScript 有啥不一样?”你就反问他:**“都 2025 年了,你还把 UI 当模板写,不当数据写吗?”**😉

附:可直接粘贴的最小对照项目片段

1)接口与数据模型(通 TS)

// common/types.ets
export type ID = string
export interface Todo { id: ID; title: string; done: boolean; updatedAt: number }

2)服务层(通 TS)

// common/http.ets
import http from '@ohos.net.http';
const BASE = 'http://127.0.0.1:3000'
export async function get<T>(path: string): Promise<T> {
  const c = http.createHttp()
  try {
    const res = await c.request(BASE + path, { method: 'GET', connectTimeout: 8000, readTimeout: 8000 })
    if (res.responseCode >= 200 && res.responseCode < 300) return JSON.parse(res.result as string)
    throw new Error(`HTTP ${res.responseCode}`)
  } finally { c.destroy() }
}
export async function post<T>(path: string, body: object): Promise<T> {
  const c = http.createHttp()
  try {
    const res = await c.request(BASE + path, {
      method: 'POST',
      header: { 'Content-Type': 'application/json' },
      extraData: JSON.stringify(body)
    })
    if (res.responseCode >= 200 && res.responseCode < 300) return JSON.parse(res.result as string)
    throw new Error(`HTTP ${res.responseCode}`)
  } finally { c.destroy() }
}

3)列表与项(ArkTS 组件)

// components/TodoItem.ets
import { Todo } from '../common/types'

@Component
export struct TodoItem {
  @Prop item: Todo
  onToggle: (id: string) => void
  onRemove: (id: string) => void

  build() {
    Row() {
      Checkbox({ name: 'done', group: 'todo', select: this.item.done })
        .onChange(() => this.onToggle(this.item.id))
      Text(this.item.title)
        .decoration({ type: this.item.done ? TextDecorationType.LineThrough : TextDecorationType.None })
        .opacity(this.item.done ? 0.6 : 1)
        .margin({ left: 8 })
        .layoutPriority(1)
      Button('删').onClick(() => this.onRemove(this.item.id))
    }
    .padding(10)
    .borderRadius(12)
    .backgroundColor('#fff')
    .margin({ bottom: 8 })
  }
}
// pages/TodoList.ets
import { Todo } from '../common/types'
import { TodoItem } from '../components/TodoItem'

@Entry
@Component
export struct TodoList {
  @State input: string = ''
  @State list: Todo[] = []

  private add() {
    const t = this.input.trim()
    if (!t) return
    const todo: Todo = { id: `${Date.now()}`, title: t, done: false, updatedAt: Date.now() }
    this.list = [todo, ...this.list]
    this.input = ''
  }

  private toggle(id: string) {
    this.list = this.list.map(it => it.id === id ? { ...it, done: !it.done, updatedAt: Date.now() } : it)
  }

  private remove(id: string) {
    this.list = this.list.filter(it => it.id !== id)
  }

  build() {
    Column() {
      Row() {
        TextInput({ text: this.input, placeholder: 'Add a task' })
          .onChange(v => this.input = v).layoutPriority(1)
        Button('Add').onClick(() => this.add())
      }.margin({ bottom: 12 })

      if (this.list.length === 0) {
        Text('空空如也,来一个?').opacity(0.6)
      } else {
        List() {
          ForEach(this.list, (it: Todo) => {
            TodoItem({ item: it, onToggle: (id: string) => this.toggle(id), onRemove: (id: string) => this.remove(id) })
          }, it => it.id)
        }
      }
    }.padding(16)
  }
}

4)对照一眼懂:TS 写 React vs. ArkTS 写 UI

  • React:Hook 表达状态 → JSX 渲染 → 虚拟 DOM Diff。
  • ArkTS:装饰器声明状态 → ArkUI DSL → 编译时生成 UI 指令,按依赖重建。

这,就是真实可感的差异。

(未完待续)

Logo

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

更多推荐