同是 TS 血统,为什么写 ArkTS 时我像换了脑子?”——ArkTS 语法特性与 TypeScript 差异研究!
本文针对鸿蒙开发者转型ArkTS的常见疑问,系统梳理了ArkTS与TypeScript的异同。文章指出ArkTS融合了TS的类型系统与声明式UI语法,通过装饰器(@State、@Prop等)实现响应式状态管理,强调"UI即数据"的设计理念。重点解析了ArkTS特有的组件定义方式(@Component struct)、声明式UI DSL和状态装饰器系统,并提供了可运行代码示例。同
我是兰瓶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 语法、装饰器语义上做了“强意见”的增补:
-
组件用
@Component struct声明struct不是 TS/JS 的语法,而是 ArkTS 的组件定义语法。- 组件必须实现
build(),UI 用函数式 DSL 写出来(Column { ... }、Text('...')等)。 - 不能像 TS 那样
new MyComponent();组件是声明式使用:MyComponent({ propA: 1 })。
-
页面入口用
@Entry指定- 入口组件常配合
UIAbility使用。@Entry让系统知道这是页面根,有自己的生命周期钩子(如aboutToAppear、aboutToDisappear、onPageShow、onPageHide)。
- 入口组件常配合
-
响应式状态靠装饰器
@State:组件本地可变状态;变化会触发该组件及依赖子树重建。@Prop:父传子的只读输入(对子组件而言);父变子随。@Link:父子双向联动(要慎用);@Provide/@Consume:跨层级依赖注入式状态分发;@Watch:监听指定字段改变时的副作用回调;@Builder:把一段 UI 结构抽成“可复用的 UI 生成函数”。
-
声明式 UI DSL
-
不写
<div/>,也不写 XML,而是在 ArkTS 里直接写 UI:Column() { Text(this.title).fontSize(20).fontWeight(FontWeight.Bold) } -
组件方法链是属性配置器;
build()返回的不是虚拟 DOM,而是被编译器编译为高效 UI 指令。
-
-
运行期特征
- ArkTS 编译产物由系统渲染引擎承接;状态变化驱动最小化 UI 刷新。
- 由于“语言-UI-状态”三位一体,语法限制比 TS 更强(比如组件字段必须受状态系统管理,随意引用外部可变变量会被限制/告警)。
是不是有点儿“写代码像写设计稿”的感觉?这就是 ArkTS 给我的第一印象:把“UI 是数据”的理念落到了语言层面。
类型系统:和 TypeScript 的“像”与“不像”
还能不能用 TS 的那些“家伙事儿”?
能,而且大多数时候用得很顺。ArkTS 基于 TS 的类型系统,常见的语法糖和能力都在:
- 基本类型:
string | number | boolean | null | undefined | symbol | bigint均可用。 - 类型组合:联合、交叉、字面量类型都 OK。
- 结构化类型:
interface、type、索引签名、泛型泛用。 - 函数类型:泛型函数、重载、
this参数类型。 - 类与可见性:
class、public/protected/private、readonly、abstract。 - 枚举:数值/字符串枚举可用;实际项目更推荐字面量联合替代。
- 类型守卫:
in、typeof、instanceof、用户自定义守卫。 - 工具类型:
Partial、Pick、Omit等常见工具类型依旧舒服。
哪些地方“有点不一样”?
-
组件状态字段必须显式纳入响应系统
- 在普通 TS 里,类字段想怎么变就怎么变;
- 在 ArkTS 组件里,影响 UI 的字段必须用装饰器(
@State、@Prop、@Link…)声明;不受管控的可变字段不会触发 UI 更新,编译器也会盯你。
-
UI DSL 的链式 API 需通过类型约束
- 例如
Text(...).fontSize(20).fontWeight(FontWeight.Bold):
每个链条都在类型上标注可用属性,非法链式调用会编译期报错。 - 这和 TS 在 React 里写 JSX 的类型体验类似,但 ArkTS 强制性更强。
- 例如
-
struct语义独立于 TSstruct不是 TS 的关键字,在 ArkTS 里它专用于组件定义;struct里有自己的生命周期与 UIbuild()约定,不能被随意实例化。
-
资源系统类型化
- 访问资源(文案、颜色、图片)常通过
$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 产物直连系统渲染 |
易错点与“反直觉”总结(都是我真踩过)
-
把业务变量写成普通字段
- 结果:UI 不刷新。
- 解决:影响 UI 的字段都改成
@State/@Prop/@Link等受管控字段。
-
在
build()里做副作用- 结果:重建频繁导致抖动或死循环。
- 解决:用
@Watch/ 生命周期钩子;build()只生成 UI。
-
@Link到处飞- 结果:数据流像绕口令。
- 解决:以单向为主,
@Link限定在输入控件等“小闭环”。
-
ForEach 的 key 不稳定
- 结果:滚动/勾选错乱,重用失败。
- 解决:使用业务稳定 ID。
-
过度依赖超重组件样式
- 结果:掉帧。
- 解决:阴影/模糊谨慎;动画尽量位移/透明度,避免反复布局。
-
生命周期混淆
- 结果:数据没拉到或泄漏。
- 解决:初始化放
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 的“三步走”
- 先把“状态是什么”写出来:把会影响 UI 的字段全部列成
@State或其变体; - 把“数据怎么流”说清楚:父 → 子用
@Prop,极少数场景用@Link;跨层级用@Provide/@Consume; - 把副作用挪走:用
@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()次数与耗时;对大列表做切片渲染。
“差异研究”的终极版图(总结)
-
语言层面把 UI 做成“一等公民”
- TS 世界里 UI 更多是框架能力(JSX/虚拟 DOM);
- ArkTS 把 UI DSL 和状态装饰器写进了“语言-编译器-运行时”的统一闭环。
-
响应式是显式契约,不是隐式约定
@State/@Prop/@Link/@Provide/@Consume/@Watch让依赖与边界看得见;- 对比 TS + React 的 Hook 心智,ArkTS 的“谁变了触发谁”更可控。
-
装饰器在 ArkTS 不是点缀,是地基
- TS 装饰器更多给库作者玩;
- ArkTS 装饰器是你写 UI 的必修课。
-
迁移成本不高,但使用姿势要换
- 语法手感 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 指令,按依赖重建。
这,就是真实可感的差异。
…
(未完待续)
更多推荐




所有评论(0)