ArkTS 进阶之道(10):@Watch 状态监听——从回调时机理解响应式边界

📅 2026/7/29 14:07:30
ArkTS 进阶之道(10):@Watch 状态监听——从回调时机理解响应式边界
ArkTS 进阶之道10Watch 状态监听——从回调时机理解响应式边界本文是「ArkTS 进阶之道」系列第 10 篇「ArkUI 状态哲学」阶段收尾。前三篇讲状态绑定机制State 单组件内部篇 56→ Prop/Link 父子传值篇 57→ Provide/Consume 跨层传值篇 58——都是数据流绑定。本文讲状态变化的监听回调Watch 荬饰器——根因在响应式回调时机机制Watch 编译期绑状态变的回调钩子状态变自动调不用手调。能力系列篇 16 讲过 Watch 怎么用本文讲为哈状态变自动调——根因在响应式回调时机。一、开篇Watch 不是手调方法是状态变的响应式回调钩子你写 TypeScript/React 时状态变监听是「魔法」React useEffect 手写依赖数组监听状态变// React useEffect 手写依赖监听 const [count, setCount] useState(0) useEffect(() { console.log(count 变为 ${count}) ← 手写 useEffect 依赖数组 [count] }, [count]) setCount(1) // count 变触发 useEffect 回调手写依赖监听你写鸿蒙 ArkTS 时Watch 荬饰器响应式回调钩子——不用手写 useEffect 依赖数组// ArkTS Watch 响应式回调钩子 Entry Component struct Index { State Watch(onCountChange) count: number 0 // Watch 绑状态变回调 onCountChange(): void { console.log(count 变为 ${this.count}) // 状态变自动调不用手调 } build() { Button(点我) .onClick(() { this.count }) // count 变触发 Watch 自动调 onCountChange } }魔法 vs 钩子的区别React 把状态监听当「魔法」你手写 useEffect 依赖数组ArkTS 把 Watch 当「响应式回调钩子」荬饰器编译期绑状态变回调状态变自动调。根因不是魔法是钩子——Watch 编译期给 State 绑回调方法名状态变触发自动调绑的方法不用手调。二、根因Watch 的响应式回调时机机制鸿蒙 ArkUI 的 Watch 是响应式回调钩子——编译期给 State 绑回调方法名状态变触发自动调绑的方法来自三重回调时机机制。机制 1Watch 编译期绑回调方法名——状态变触发自动调Watch 荬饰器编译期绑回调方法名——给 State 绑一个回调方法名状态变触发自动调这个方法Entry Component struct Index { State Watch(onCountChange) count: number 0 // Watch 绑 onCountChange 方法名 onCountChange(): void { console.log(count 变触发自动调${this.count}) // 回调方法状态变自动调 } build() { Button(点我) .onClick(() { this.count }) // count 变触发自动调 onCountChange } }编译期绑方法名Watch(onCountChange)荬饰器编译期给 Statecount绑回调方法名onCountChange——赋值this.count触发自动调this.onCountChange()方法。绑方法名不用手写 useEffect 依赖数组荬饰器编译期绑。根因不是手调是编译期绑的响应式钩子。机制 2状态变触发自动调——赋值就调不用手调Watch 荬饰器状态变触发自动调——赋值操作拦截成「赋值 自动调绑的回调方法」Entry Component struct Index { State Watch(onCountChange) count: number 0 State Watch(onNameChange) name: string 初始名 // 多个 Watch 各绑各 onCountChange(): void { console.log(count 变自动调${this.count}) } onNameChange(): void { console.log(name 变自动调${this.name}) } build() { Button(改 count) .onClick(() { this.count }) // count 变触发自动调 onCountChange Button(改 name) .onClick(() { this.name 新名 }) // name 变触发自动调 onNameChange } }赋值拦截自动调Watchcount赋值this.count被荬饰器拦截成「赋值 自动调 onCountChange」Watchname赋值this.name 新名被拦截成「赋值 自动调 onNameChange」。赋值就调不用手调荬饰器绑的响应式钩子触发自动调。对比普通方法要手调this.manualMethod()Watch 荬饰器赋值就自动调。机制 3回调时机是状态变后——响应式边界在赋值触发Watch 荬饰器回调时机是状态变后——赋值操作完成后触发回调响应式边界在赋值触发Entry Component struct Index { State Watch(onCountChange) count: number 0 onCountChange(): void { // 回调时机count 赋值完成后调状态变后 console.log(回调调时 count 已变 ${this.count}) // this.count 已是新值 } build() { Button(点我) .onClick(() { this.count // ① 赋值 count // ② 赋值完成后自动调 onCountChangethis.count 已是新值 // ③ 回调里 this.count 读到的是新值状态变后 }) } }回调时机状态变后Watch 回调时机是赋值完成后调——this.count赋值完成后自动调onCountChange回调里this.count读到的是新值状态变后。响应式边界在赋值触发——赋值操作完成 → 触发 Watch 回调 → 回调里读到新值。根因不是同步监听是赋值完成后的响应式回调时机。三、真机配图Watch 状态变自动调 vs 普通方法手动调对比证据初始态Watch count0、Watch name初始名、普通方法手动调次数0 均初始值点调三按钮后Watch 自动调 onCountChange/onNameChange 日志显示、普通方法手动调次数1 对比证据齐对比证据点 Watch 监 count 按钮后自动调 onCountChange日志显示「Watch 自动调count 变为 1」不用手调点 Watch 监 name 改按钮后自动调 onNameChange日志显示「Watch 自动调name 变为 名字1」不用手调点普通方法按钮后手动调 manualMethod次数1 要手调。Watch 状态变自动调不是手调是响应式回调钩子——编译期绑方法名 赋值拦截自动调 回调时机状态变后三重机制。四、真解法Watch 用法的三个场景场景 1状态变自动调回调90% 场景首选替代手调Entry Component struct Index { State Watch(onCountChange) count: number 0 // Watch 绑状态变回调 onCountChange(): void { console.log(count 变为 ${this.count}) // 状态变自动调不用手调 } build() { Button(点我) .onClick(() { this.count }) // count 变触发自动调 onCountChange } }为哈能跑Watch 编译期绑回调方法名状态变触发自动调绑的方法。首选这个90% 的场景状态变要回调用 Watch 自动调就够。要写「状态变要执行副作用日志/缓存/通知」时用这个——不用手调赋值就自动调绑的回调方法。场景 2多状态各绑各回调每个 State 绑自己的 WatchEntry Component struct Index { State Watch(onCountChange) count: number 0 // count 绑 onCountChange State Watch(onNameChange) name: string 初始名 // name 绑 onNameChange State Watch(onListChange) list: string[] [] // list 绑 onListChange onCountChange(): void { console.log(count 变${this.count}) } onNameChange(): void { console.log(name 变${this.name}) } onListChange(): void { console.log(list 变${this.list.length}) } build() { Column() { Button(改 count).onClick(() { this.count }) // 触发 onCountChange Button(改 name).onClick(() { this.name 新名 }) // 触发 onNameChange Button(改 list).onClick(() { this.list.push(新) }) // 触发 onListChange } } }为哈能跑每个 State 绑自己的 Watch 回调方法名——count 变触发 onCountChangename 变触发 onNameChangelist 变触发 onListChange。要写「多状态各自有副作用」时用这个——每个 Watch 各绑各状态变各触发各的回调不用手调判断哪个状态变。场景 3Watch vs 普通方法选型自动调 vs 手调边界Entry Component struct Index { State Watch(onCountChange) count: number 0 // Watch 自动调状态变触发 State log: string onCountChange(): void { this.log Watch 自动调count${this.count} // 响应式副作用自动调 } manualMethod(): void { this.log 普通方法手动调 // 命令式副作用要手调 } build() { Column() { Button(改 countWatch 自动调) .onClick(() { this.count }) // Watch 自动调 onCountChange Button(手调普通方法) .onClick(() { this.manualMethod() }) // 普通方法要手调 } } }为哈能跑Watch vs 普通方法选型按副作用触发方式——Watch 响应式自动调状态变触发适合「状态变的副作用」普通方法命令式手调要手调适合「按钮点击的一次性动作」。要写「状态变的副作用日志/缓存/通知」时用 Watch 自动调要写「按钮点击的一次性动作」时用普通方法手调——按副作用触发方式选型响应式自动调 vs 命令式手调边界。五、一句话哲学Watch 不是手调方法是状态变的响应式回调钩子。ArkUI 的 Watch 荬饰器编译期给 State 绑回调方法名状态变触发自动调绑的方法不用手调。根因不是手调是响应式回调钩子——Watch 编译期绑方法名回调方法名绑定 状态变触发自动调赋值拦截自动调 回调时机状态变后响应式边界在赋值触发。对比普通方法要手调Watch 荬饰器赋值就自动调是响应式回调钩子的直接证据。状态哲学阶段收官串讲State 单组件内部篇 56赋值就刷 UI 依赖追踪→ Prop/Link 父子传值篇 57单向 vs 双向数据流绑定→ Provide/Consume 跨层传值篇 58键值注册跨层绑定→ Watch 状态监听篇 59响应式回调钩子——四篇讲清 ArkUI 状态哲学数据流绑定State/Prop/Link/Provide/Consume 状态变回调Watch根因都是编译期绑追踪/回调。响应式边界总结State 赋值就刷 UI数据流绑追踪范围 Watch 赋值就调回调回调钩子绑方法名——赋值触发的两个响应式边界①数据流边界UI 重渲②回调边界副作用执行。Watch 补上 State 没管的副作用边界——赋值不只刷 UI 还调 Watch 回调执行副作用完整响应式链路赋值 → 刷 UIState 调回调Watch。下一阶段预告ArkUI 渲染哲学篇 60-62——从状态哲学数据流回调到渲染哲学build 重渲/条件渲染/循环渲染换角度讲 ArkUI 渲染机制。能力系列回链能力系列篇本文进阶点篇 16 Watch 用法响应式回调时机根因篇 15 Provide/Consume 用法上一篇键值注册跨层绑定篇 17 条件渲染用法下阶段预告渲染哲学真机 demo 完整代码// 篇 59 demoWatch 状态监听回调时机 vs 普通方法调用对比 // 对比Watch 荬饰器状态变自动调 vs 普通方法手动调 Entry Component struct Index { // ✅ Watch 荬饰器监听 State 变化自动调回调 State Watch(onCountChange) count: number 0 State Watch(onNameChange) name: string 初始名 State log: string (未操作) State methodCallCount: number 0 // 普通方法手动调次数对比 // ✅ Watch 回调状态变自动调不用手调 onCountChange(): void { this.log Watch 自动调count 变为 ${this.count}不用手调 } onNameChange(): void { this.log Watch 自动调name 变为 ${this.name}不用手调 } // ❌ 普通方法手动调对比证据 manualMethod(): void { this.methodCallCount this.log 普通方法手动调第 ${this.methodCallCount} 次要手调 } build() { Column({ space: 12 }) { Text(篇 59 配图Watch 状态监听回调时机) .fontSize(18).fontWeight(FontWeight.Bold).margin({ top: 20, bottom: 8 }) Text(Watch 状态变自动调 vs 普通方法手动调对比证据) .fontSize(12).fontColor(#888).margin({ bottom: 16 }) Column({ space: 6 }) { Text(State Watch count ${this.count}).fontSize(15).fontWeight(FontWeight.Bold) Text(State Watch name ${this.name}).fontSize(15).fontWeight(FontWeight.Bold) Text(普通方法手动调次数 ${this.methodCallCount}).fontSize(14).fontColor(#999) Text(日志${this.log}).fontSize(12).fontColor(#333).margin({ top: 4 }) } .width(92%).padding(12).backgroundColor(#f5f5f5).borderRadius(8) Button(Watch 监 count自动调 onCountChange) .width(92%).height(44).fontSize(14) .onClick(() { this.count // ✅ Watch 自动调 onCountChange不用手调 }) Button(Watch 监 name 改自动调 onNameChange) .width(92%).height(44).fontSize(14) .onClick(() { this.name 名字${this.count} // ✅ Watch 自动调 onNameChange不用手调 }) Button(普通方法手动调 manualMethod要手调) .width(92%).height(44).fontSize(14) .onClick(() { this.manualMethod() // ❌ 普通方法要手调对比证据 }) } .width(100%).height(100%).alignItems(HorizontalAlign.Center) } }写鸿蒙 ArkUI 记住Watch 不是手调方法是状态变的响应式回调钩子——Watch 荬饰器编译期给 State 绑回调方法名状态变触发自动调绑的方法不用手调。根因不是手调是响应式回调钩子——Watch 编译期绑方法名回调方法名绑定 状态变触发自动调赋值拦截自动调 回调时机状态变后响应式边界在赋值触发。状态变要副作用日志/缓存/通知用 Watch 自动调首选多状态各绑各 Watch 各触发各回调按钮一次性动作用普通方法手调。赋值不只刷 UI 还调 Watch 回调执行副作用是 ArkUI 状态哲学核心收尾