文章目录
-
- 前言
- [先建立一个直觉:UI 是状态的快照](#先建立一个直觉:UI 是状态的快照)
- 哪些变量"改了会变",哪些不会
- [V1 和 V2 状态模型的差别](#V1 和 V2 状态模型的差别)
- 为什么有时候"变了却不刷新"
- 常见误区(小白必踩)
- 完整可运行示例:计数器讲透「数据驱动视图」
- 写在最后
前言
很多刚接触 ArkUI 的人都有一个疑问:我为啥改了个变量,界面就自己变了? 明明没写"刷新"代码啊。
这背后就是所谓的"状态驱动 UI"。这篇文章我不跟你扯源码,就用大白话把这套机制讲明白,顺便告诉你哪些变量"改了会变"、哪些"改了白改"。

先建立一个直觉:UI 是状态的快照
把界面想象成一张照片,而状态就是拍照时的参数。参数一变,你希望照片立刻重拍一张------这就是声明式 UI 的核心思想:UI = f(状态)。
在 ArkUI 里,只有被特定装饰器 标记过的变量,框架才会"盯着它"。你改了它,框架就自动重新执行 build(),生成新的 UI。
代码实现
typescript
@Entry
@ComponentV2
struct Counter {
@Local count: number = 0 // 框架盯着这个变量
build() {
Column() {
Text('当前计数:' + this.count).fontSize(24)
Button('加一')
.onClick(() => {
this.count += 1 // 改了被盯的变量
})
}
}
}


关键点:
@Local count被框架"注册"为响应式状态。- 点击按钮
this.count += 1,框架检测到count变了,于是自动重新跑build()。 - 新的
build()读到的count已经是新值,Text 自然显示新数字。
这就是"改数据视图就变"的全部秘密:不是你手动刷新,是框架在变量变化时替你重画。
哪些变量"改了会变",哪些不会
这是新手最容易懵的地方。看对比:
核心代码
typescript
@ComponentV2
struct Demo {
@Local a: number = 0 // 改了 → 界面变
normalB: number = 0 // 改了 → 界面不变!
build() {
Column() {
Text('a=' + this.a)
Text('b=' + this.normalB)
Button('改 a').onClick(() => this.a++)
Button('改 b').onClick(() => this.normalB++)
}
}
}
normalB 就是个普通变量,框架根本不盯着它,你改一百次界面也不会动。所以想让界面跟着变的变量,必须用状态装饰器 (@Local、@State、@Param 等)。
V1 和 V2 状态模型的差别
HarmonyOS 有新旧两套状态管理。老的是 @State(V1),新的是 @Local(V2,ArkUI 状态管理 V2)。
| 对比 | V1(@State 等) | V2(@Local 等) |
|---|---|---|
| 观察粒度 | 整个对象 | 精确到属性 |
| 深层属性更新 | 需 @Observed + @ObjectLink |
原生支持深层监听 |
| 性能 | 一般 | 更优 |
新项目我强烈建议直接用 V2(@Local / @Param / @Provider / @Consumer) 。深层对象改个字段就能触发更新,不用再包一层
@Observed,省心太多。
为什么有时候"变了却不刷新"
你大概率会碰到这种情况:数组 push 了新元素,界面没更新。常见原因:
- 用的是普通变量而非状态变量。
- 直接改数组里对象的某个字段,但状态装饰器没监听到(V1 尤其容易踩)。
- 在
build()之外改了状态却没触发------其实只要装饰器对,框架会处理。
V2 里用 @Local 配合正常赋值基本都能解决;V1 里深层更新记得用 @Observed。
常见误区(小白必踩)
| 误区 | 说明 |
|---|---|
| 用普通变量当状态 | normalB 改了一百次界面也不动,因为它没被装饰器标记,框架根本不盯着它。 |
V1 深层更新忘 @Observed |
老 @State 改对象里某个字段不刷新,得包 @Observed + @ObjectLink。新项目直接用 V2 省心。 |
在 build() 里改状态 |
build 可能多次执行,在里面改状态会触发重复渲染甚至死循环。 |
下面这段代码可以直接复制到 DevEco Studio 里运行。建议你边读边敲,改一改文末「动手改一改」里的参数,亲眼看看效果。
完整可运行示例:计数器讲透「数据驱动视图」
这个例子是理解响应式的最小闭环。点按钮,数字变,界面自动刷新------你不用碰 DOM。
完整示例
typescript
// @State 数据变化 → 框架自动重渲染绑定它的 UI
@Entry
@Component
struct Counter {
@State count: number = 0 // 唯一的真相来源
build() {
Column({ space: 20 }) {
Text('当前计数:' + this.count) // 绑定 count
.fontSize(28)
.fontWeight(FontWeight.Bold)
Button('点我 +1')
.onClick(() => {
this.count += 1 // 只改数据,不用手动刷新界面
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
你会看到什么 :屏幕中央一个大数字「当前计数:0」和一个按钮。每点一次按钮,数字 +1,且只有这个数字重绘,按钮没动。
动手改一改:
- 把
this.count += 1改成this.count -= 1,变成减法计数器。 - 再加一个
@State msg: string = '你好',点按钮同时改msg,观察两段文字一起更新。 - 把
Text('当前计数:' + this.count)里的this.count删掉改成固定字符串,再点按钮------数字不再变,因为你切断了绑定。这能帮你理解「为什么必须绑定」。
写在最后
状态驱动不是魔法,它就是"框架替你监听变量、变了就重画"这么简单。你只要记住两条:第一,想变就加装饰器;第二,新项目上 V2。把 Counter 例子跑一遍,点按钮看着数字跳,那种"哦~原来如此"的感觉就到位了。
下一篇我专门讲 ForEach 的"唯一键"大坑,那个坑能让你调试到怀疑人生,提前有个心理准备。