到底是 TS 的远房亲戚,还是 UI 的狠角色?”——一口气讲透 ArkTS 语法、范式与响应式脑回路!
开篇语
哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛
今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。
我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!
前言
说真的,第一次写 ArkTS,我的心理活动是:啊这,不就是 TypeScript 换了件新外套吗?结果一写 UI,状态像猫一样说变就变,@State、@Prop、@Link、@Provide/@Consume 在屏幕上跳康康舞👀。ArkTS并不是把 TS 搬到端侧那么简单,它把UI 声明式 + 响应式状态系统揉进语言/框架语义里,再配上资源系统、编译期装饰器和端侧约束,做出来的是“像 TS,又不止 TS”的开发体验。下面我们从语言特性 → 与 TypeScript 的差异 → 函数式与响应式编程模式一路梳理,顺手给到可跑的代码片段与避坑心法。
目录
- 为什么 ArkTS 不是“换皮 TS”
- ArkTS 语法特性,一屏看懂
- 与 TypeScript 的异同(别被语法糖迷了眼)
- 函数式与响应式编程:在 ArkUI 里跑得顺才叫真本事
- 进阶实战:三段代码掌握状态共享、派生状态与副作用
- 性能与工程化心法:别在
build()里挖坑 - 小结与加餐:常见踩坑清单
1) 为什么 ArkTS 不是“换皮 TS”
核心观点:ArkTS 面向端侧 UI 与应用框架(ArkUI/ArkCompiler),把状态与界面的映射做成“一等公民”。这意味着:
- UI = 函数(状态) 的理念被语言与装饰器加持,调用链天然响应式;
- 编译器参与更深:装饰器、资源、路由、生命周期能在编译阶段“看见并优化”;
- 运行时约束更严格:端侧环境为了性能与安全,对动态特性更保守(例如不鼓励
eval、反射滥用)。
一句话:用 TS 的手感,写“端上更像框架内核语言”的东西。
2) ArkTS 语法特性,一屏看懂
2.1 组件与装饰器:@Entry、@Component、@State …
// xxx.ets
@Entry
@Component
struct CounterPage {
@State count: number = 0 // 本地可变状态
@State step: number = 1
// 生命周期(常用片段)
aboutToAppear() {
// 页面可见前:读取缓存、拉配置
}
build() {
Column({ space: 16 }) {
Text(`Count: ${this.count}`).fontSize(22).fontWeight(600)
Row({ space: 12 }) {
Button(`+${this.step}`).onClick(() => this.count += this.step)
Button('Reset').type(ButtonType.Normal).onClick(() => {
this.count = 0
})
}
Slider({ value: this.step, min: 1, max: 10 })
.onChange((v: number) => this.step = Math.floor(v))
}
.padding(20)
}
}
读法:build() 里就是声明式 UI,视图树由 Column/Row/Text/Button 等函数语义化描述;任何 @State 变化都会触发精准重渲染(差量更新)。
2.2 跨组件状态传递:@Prop / @Link / @Provide / @Consume
@Prop:父传子只读快照;@Link:父子共享同一状态引用(双向联动);@Provide / @Consume:类似依赖注入,上层提供,下层任何后代都能消费,不需要层层传参。
// 父
@Entry
@Component
struct App {
@State theme: 'light'|'dark' = 'light'
@Provide('themeKey') providedTheme: string = this.theme
build() {
Column() {
ThemeToggle({ })
ContentArea()
}
}
}
@Component
struct ThemeToggle {
@Consume('themeKey') theme!: string
build() {
Row() {
Text(`Theme: ${this.theme}`)
Button('Switch').onClick(() => {
// 直接改 consume 的来源(共享引用)
this.theme = this.theme === 'light' ? 'dark' : 'light'
})
}
}
}
2.3 资源系统与单位:$r('app.string.xxx')、vp/fp/px
- 统一访问字符串/颜色/尺寸/图片等资源:
$r('app.string.hello'); - UI 单位:
vp(与屏幕密度无关的视觉像素)、fp(字体像素)、px(物理像素)。
心法:布局用vp,文字用fp,像素级控制再谈px。
2.4 异步与任务:async/await、定时、轻量任务
ArkTS 支持 Promise/async/await。端侧 I/O、动画、定时器都建议避开阻塞,在生命周期钩子或事件回调里异步化处理。
3) 与 TypeScript 的异同(别被语法糖迷了眼)
| 维度 | TypeScript | ArkTS |
|---|---|---|
| 语言定位 | JS 的类型超集,面向广义前后端 | TS 手感 + 端侧 UI/状态语义,与 ArkUI、编译器深度耦合 |
| 组件模型 | React/Vue/Svelte 等库提供 | 语言级装饰器 + 框架一体化(@Entry/@Component/@State 等) |
| 状态系统 | 各框架自带(React Hooks、Vue 响应式等) | 官方内建:@State/@Link/@Provide/@Consume/@Prop、精细化渲染 |
| 资源/样式 | 由框架/构建工具约定 | 内建资源系统 + 单位体系(vp/fp/px) |
| 运行时能力 | 浏览器/Node,自由度高 | 端侧约束更严,更强调可预测与性能(谨慎动态特性) |
| 编译与优化 | tsc + bundler + 框架优化 | ArkCompiler 参与更深,装饰器/路由/资源可被分析与优化 |
| API 生态 | npm 世界 | HarmonyOS 能力集成(权限、硬件、系统服务) |
易混点:
class/struct语义:ArkTS 组件常见struct … build()书写范式(也可见到 class 风格 API),以组件生命周期与状态为中心。- 装饰器并非任意 TS 装饰器能无缝复用;ArkTS 的装饰器有特定编译期含义。
- 不要滥用 any/反射:端侧更希望静态可分析、可裁剪。
4) 函数式与响应式编程:在 ArkUI 里跑得顺才叫真本事
4.1 函数式味道:数据不可变、纯函数组件化
- 纯函数思想:给定状态→渲染确定 UI;
- 不可变更新:状态更新倾向新对象(帮助脏检查与最小重绘);
- 高阶函数:
map/filter/reduce处理列表渲染更稳。
@Component
struct TodoList {
@State todos: { id:number; title:string; done:boolean }[] = []
toggle(id: number) {
// 不可变更新:创建新数组
this.todos = this.todos.map(t => t.id === id ? { ...t, done: !t.done } : t)
}
build() {
List() {
ForEach(this.todos, (t) => {
ListItem() {
Row({ space: 12 }) {
Checkbox({ checked: t.done }).onChange(() => this.toggle(t.id))
Text(t.title).decoration(t.done ? {type: TextDecorationType.LineThrough} : {})
}
}
})
}
}
}
4.2 响应式内核:状态图与精确更新
- 任一
@State/@Link数值变化 → 框架计算受影响的视图子树 → 局部重渲染; - 派生状态建议用计算函数或
@Watch观察,避免在build()里做重逻辑。
@Component
struct PricePanel {
@State count: number = 1
@State unit: number = 12.5
total(): number { return Number((this.count * this.unit).toFixed(2)) }
@Watch('count')
onCountChanged() {
// 轻量副作用:例如打点、缓存
}
build() {
Column({ space: 8 }) {
Text(`Unit: $${this.unit}`)
Text(`Count: ${this.count}`)
Text(`Total: $${this.total()}`).fontWeight(700)
Slider({ value: this.count, min: 1, max: 10 }).onChange(v => this.count = Math.floor(v))
}.padding(16)
}
}
口诀:数据改,UI动;副作用,轻松控;重计算,别放 build。
5) 进阶实战:三段代码掌握状态共享、派生状态与副作用
5.1 全局主题与本地覆盖(@Provide/@Consume + 局部状态)
@Entry
@Component
struct Root {
@State theme: 'light' | 'dark' = 'light'
@Provide('theme') appTheme: string = this.theme
build() {
Column({ space: 12 }) {
Button('Toggle Theme').onClick(() => {
this.theme = this.theme === 'light' ? 'dark' : 'light'
})
CardArea()
FooterBar()
}.padding(16)
}
}
@Component
struct CardArea {
@Consume('theme') theme!: string
@State localHighContrast: boolean = false
build() {
Column({ space: 8 }) {
Row({ space: 8 }) {
Text(`Theme from root: ${this.theme}`)
Switch({ checked: this.localHighContrast })
.onChange((on) => this.localHighContrast = on)
}
// 局部覆盖样式
Card().backgroundColor(this.localHighContrast ? '#000' : (this.theme === 'dark' ? '#222' : '#fff'))
.width('100%').height(80)
}
}
}
5.2 列表派生与 memo 化(避免无谓渲染)
@Component
struct SearchableList {
@State keyword: string = ''
@State items: string[] = ['ArkTS', 'TypeScript', 'ArkUI', 'Harmony', 'State', 'Reactive']
// 简易 memo:输入相同就重用
private _cache: { k: string; out: string[] } | null = null
filtered(): string[] {
if (!this._cache || this._cache.k !== this.keyword) {
const out = this.items.filter(x => x.toLowerCase().includes(this.keyword.toLowerCase()))
this._cache = { k: this.keyword, out }
}
return this._cache.out
}
build() {
Column({ space: 10 }) {
TextInput({ placeholder: 'Type to search…' })
.onChange((v: string) => this.keyword = v)
List() {
ForEach(this.filtered(), (name) => ListItem() { Text(name) })
}.cachedCount(20) // 大列表缓存优化
}.padding(16)
}
}
5.3 副作用与异步串联(生命周期 + 取消)
@Component
struct RemotePanel {
@State loading: boolean = false
@State data: { title: string; body: string } | null = null
private cancelled = false
aboutToAppear() {
this.fetchData()
}
aboutToDisappear() {
// 标记取消,避免卸载后 setState
this.cancelled = true
}
async fetchData() {
this.loading = true
try {
const res = await fetchSomehow('/api/detail') // 替换为端侧请求能力
if (!this.cancelled) this.data = res
} finally {
if (!this.cancelled) this.loading = false
}
}
build() {
if (this.loading) {
return Column() { LoadingProgress() }.height('100%').justifyContent(FlexAlign.Center)
}
if (!this.data) {
return Column() { Text('No data') }.height('100%').justifyContent(FlexAlign.Center)
}
return Column({ space: 8 }) {
Text(this.data.title).fontSize(20).fontWeight(700)
Text(this.data.body).fontSize(16)
}.padding(16)
}
}
6) 性能与工程化心法:别在 build() 里挖坑
build()只做“拼界面”:重计算/请求/写缓存都放到生命周期或事件回调。- 最小化状态:把能算出来的用派生函数算,不要塞进
@State。 - 不可变更新:数组/对象建议返回新引用,帮助框架判断差异。
- 列表优化:
ForEach配合稳定 key /cachedCount;分页/瀑布流尽量增量渲染。 - 分层状态:全局用
@Provide,跨层@Consume,兄弟用@Linkor 事件,避免大状态洪水。 - 副作用可取消:涉及异步的页面,记得在
aboutToDisappear做取消标记。 - 资源与单位:布局用
vp,文字fp,矢量/九宫优先,图片按devicePixelRatio管理。 - 严格类型:端侧更需要“可分析”,少用 any,尽量定死模型类型与接口契约。
7) 小结与加餐:常见踩坑清单
- 状态循环更新:
@Watch里又改回监听的@State,形成死循环?→ 条件判断 + 节流。 - 误把
@Prop当可写:@Prop是只读快照,双向联动用@Link。 - 把重逻辑塞进
build():页面抖动、掉帧,移动端更敏感。 - 滥用全局状态:
@Provide别成“全局黑洞”,要有边界与层级。 - 动态特性过多:
eval/反射等不利于编译优化与裁剪,端侧慎用。 - 资源直写常量:跨主题/多语言时全改一遍很痛,统一
$r()。 - 长列表刷新:没做缓存与 key,滚一滚 CPU 就给你“上香”。
TL;DR(一句话)
ArkTS = TS 的类型手感 + 端侧声明式 UI + 状态为王的响应式内核。写好它的秘诀是:最小状态、不可变更新、把副作用关进笼子。当你把“数据 → 视图”这条链打磨顺滑,ArkTS 就会像台油门线绷紧的摩托,轻一点就窜起来,爽!🏍️💨
参考练习任务(自行扩展)
- 把
TodoList改成分组与搜索并做分页懒加载。 - 为
RemotePanel加错误重试 + 指数退避,并在aboutToDisappear取消未完成请求。 - 用
@Provide/@Consume做多主题(浅色/深色/高对比),并抽出样式函数。
… …
文末
好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。
… …
学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!
wished for you successed !!!
⭐️若喜欢我,就请关注我叭。
⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。
版权声明:本文由作者原创,转载请注明出处,谢谢支持!
- 点赞
- 收藏
- 关注作者
评论(0)