一、@Watch 装饰器:状态变化的实时监听者
核心功能
- @Watch用于监听可观察状态变量 (如@State/@Prop/@Link)的变化,在变量值发生变动时触发回调函数。其本质是建立观察者模式,实现组件间状态同步
- @Watch适用于需要即时响应的状态变化场景,如按钮点击、输入框输入等交互操作
关键特性
- 监听原理:通过严格相等(
===
)算法判断状态变量是否更新(false时更新) - 初始化阶段:首次组件渲染时不会触发回调,仅后续状态变更时激活,避免初始渲染时的误判
- 异步安全:禁止在回调中使用
async/await
,确保渲染线程的及时响应 - 同步执行:在属性变更后立即触发,优先级高于 UI 渲染
- 链式更新:若回调中修改其他状态变量,会引发二次监听
- 节流防抖:高频操作场景添加100ms延迟执行
- 回调函数:接收一个参数
propName
,表示触发变化的属性名 - 条件过滤:在回调入口添加变更有效性判断
kotlin
@Watch('onDataUpdate')
private onDataUpdate(propName: string) {
if (this.prevValue !== this.currentValue) {
// 实际业务逻辑
}
}
- 异步操作规范: 避免在回调中使用async/await,推荐将异步操作封装至独立Service层,通过事件总线与状态管理解耦
- 触发条件:
- 值类型变化(如
string
→number
) - 数值变化(如
0
→1
) - 对象引用变化(如
new Object()
)
- 值类型变化(如
典型使用场景
- 电商购物车实时计价: 价格计算逻辑与UI更新解耦
typescript
@Observed //通过@Observed实现嵌套对象深度监听
class CartItem {
constructor(public id: number, public price: number) {}
}
@Component
struct CheckoutPanel {
//@Watch与@Link配合实现双向数据流
@Link @Watch('updateTotal') cartItems: CartItem[]
@State total: number = 0
updateTotal() {
this.total = this.cartItems.reduce((sum, item) => sum + item.price, 0)
if (this.total > 1000) this.total *= 0.8 // 满减逻辑
}
build() {
Column() {
ForEach(this.cartItems, item =>
Text(`商品${item.id}:¥${item.price}`)
)
Text(`实付:¥${this.total.toFixed(2)}`)
}
}
}
- 跨组件状态同步: 支持多页面实时响应主题变化
typescript
@Entry
@Component
struct AppRoot {
@Provide('theme') @Watch('themeChange') currentTheme: Theme = lightTheme
themeChange() {
Logger.log(`主题切换至${this.currentTheme.name}`)
}
}
@Component
struct SettingsPage {
@Consume('theme') theme: Theme
build() {
Toggle({ checked: this.theme.darkMode })
.onChange(value => this.theme.darkMode = value)
}
}
- 防循环陷阱:避免在回调中直接修改被监听的同一状态变量
typescript
@Component struct SafeCounter {
@State count: number = 0;
@Watch('onChange')
onChange() {
setTimeout(() => this.count++, 0); // 使用中间层避免直接修改
}
}
二、$$语法:动态模板字符串插值
基础语法
- 在 ArkTS 中,使用双美元符号
$$
实现字符串模板插值,将变量动态嵌入字符串中
ini
let name = "Alice";
let greeting = $$`Hello, ${name}!`;
// 输出: Hello, Alice!
- 与 UI 组件结合
typescript
// 简单插值
Text(`Hello, ${this.username}!`);
// 表达式支持
Text(`Price: $${(this.price * 0.9).toFixed(2)}`);
// 动态样式绑定
Row() {
Text(`Status: ${
this.status === 'success' ? '✅' : '❌'
}`)
.color(this.getStatusColor())
}
- 避免重复计算:将复杂表达式提取为方法
typescript
getFormattedPrice() {
return this.price.toFixed(2);
}
Text(`Price: $${this.getFormattedPrice()}`)
三、@Track 装饰器:对象属性级更新优化
核心功能
- 针对class对象的属性级更新优化,解决传统状态管理中全量刷新 的性能问题。通过标记特定属性,实现增量渲染。
- @Track推荐用于管理包含多个属性的复杂对象,特别是在频繁更新且需要优化渲染性能的场景
运行机制
- 白名单机制:仅跟踪被
@Track
标记的属性,避免未跟踪属性的全量刷新 - 必要标记原则:所有可能在UI中使用的class属性都应添加
@Track
- 属性级监听:基于JavaScript的
Proxy
实现属性访问拦截 - 增量渲染:仅更新被标记属性关联的 UI 节点
- 兼容性:与 @State、@Link 等装饰器无缝集成
- 避免混合模式:同一class对象中不应同时存在
@Track
与非@Track
属性 - 性能考量:对于包含大量属性的复杂对象,优先使用结构化数据(如Record)
典型用例
typescript
// 跟踪类定义
class Product {
@Track price: number = 100;
@Track stock: number = 50;
description: string = "Default Product";
}
// 组件使用
@Component struct ProductCard {
@State product: Product = new Product();
build() {
Column([
Text(`Price: $${this.product.price}`).fontSize(20),
Text(`Stock: ${this.product.stock}`).fontSize(20),
Button('Update Price').onClick(() => this.product.price += 10)
])
}
}
四、装饰器协同模式
@Watch + @Track 组合
typescript
class DataModel {
@Track name: string;
@Track age: number;
}
@Component struct DataView {
@State model: DataModel = new DataModel();
@Watch('name') onChangeName() {
console.log(`Name changed to ${this.model.name}`);
}
@Watch('age') onChangeAge() {
console.log(`Age changed to ${this.model.age}`);
}
}
$$语法与状态管理结合
typescript
@Component struct UserProfile {
@State user: { name: string; avatar: string } = { name: 'Alice', avatar: 'default' };
build() {
Column([
Text(`Welcome, ${this.user.name}!`),
Image(this.user.avatar)
.width(100)
.height(100)
])
}
}
五、总结与建议
-
@Watch 适用于需要即时响应的状态变化场景,如按钮点击、输入框输入等交互操作,使用原则:
- 回调函数保持纯粹(无副作用)
- 避免深度嵌套监听(超过 3 层需重构)
- 使用
debounce
处理高频事件(如输入框)
-
@Track 推荐用于管理包含多个属性的复杂对象,特别是在频繁更新且需要优化渲染性能的场景,使用原则:
- 所有 UI 可见属性必须标记
@Track
- 非 UI 属性禁止使用
@Track
- 复杂对象采用扁平化设计(如
@Track
单独字段)
- 所有 UI 可见属性必须标记
-
"$$"语法优化:
- 预计算复杂表达式
- 使用
computed
属性缓存结果
typescript@State username: string = 'User'; @Computed get displayName() { return this.username.toUpperCase(); } Text(`Hello, ${this.displayName}`)
六、常见问题
- 未触发回调:
- 检查
@Watch
参数是否与属性名完全匹配 - 确认状态变量是
@State
/@Prop
/@Link
装饰的
- 检查
- 渲染异常:
- 确保
@Track
属性在 UI 中合法使用 - 检查非
@Track
属性是否意外绑定到 UI
- 确保