鴻蒙開發(fā)筆記-10-其他狀態(tài)管理:@Watch裝飾器,$$語(yǔ)法,@Track裝飾器

一、@Watch 裝飾器:狀態(tài)變化的實(shí)時(shí)監(jiān)聽(tīng)者

核心功能
  • @Watch用于監(jiān)聽(tīng)可觀察狀態(tài)變量(如@State/@Prop/@Link)的變化,在變量值發(fā)生變動(dòng)時(shí)觸發(fā)回調(diào)函數(shù)。其本質(zhì)是建立觀察者模式,實(shí)現(xiàn)組件間狀態(tài)同步
  • @Watch適用于需要即時(shí)響應(yīng)的狀態(tài)變化場(chǎng)景,如按鈕點(diǎn)擊、輸入框輸入等交互操作
關(guān)鍵特性
  • 監(jiān)聽(tīng)原理:通過(guò)嚴(yán)格相等(===)算法判斷狀態(tài)變量是否更新(false時(shí)更新)
  • 初始化階段:首次組件渲染時(shí)不會(huì)觸發(fā)回調(diào),僅后續(xù)狀態(tài)變更時(shí)激活,避免初始渲染時(shí)的誤判
  • 異步安全:禁止在回調(diào)中使用async/await,確保渲染線程的及時(shí)響應(yīng)
  • 同步執(zhí)行:在屬性變更后立即觸發(fā),優(yōu)先級(jí)高于 UI 渲染
  • 鏈?zhǔn)礁拢喝艋卣{(diào)中修改其他狀態(tài)變量,會(huì)引發(fā)二次監(jiān)聽(tīng)
  • 節(jié)流防抖:高頻操作場(chǎng)景添加100ms延遲執(zhí)行
  • 回調(diào)函數(shù):接收一個(gè)參數(shù) propName,表示觸發(fā)變化的屬性名
  • 條件過(guò)濾:在回調(diào)入口添加變更有效性判斷
@Watch('onDataUpdate')
private onDataUpdate(propName: string) {
  if (this.prevValue !== this.currentValue) {
    // 實(shí)際業(yè)務(wù)邏輯
  }
}
  • 異步操作規(guī)范: 避免在回調(diào)中使用async/await,推薦將異步操作封裝至獨(dú)立Service層,通過(guò)事件總線與狀態(tài)管理解耦
  • 觸發(fā)條件:
    1. 值類型變化(如 stringnumber
    2. 數(shù)值變化(如 01
    3. 對(duì)象引用變化(如 new Object()
典型使用場(chǎng)景
  • 電商購(gòu)物車實(shí)時(shí)計(jì)價(jià): 價(jià)格計(jì)算邏輯與UI更新解耦
@Observed //通過(guò)@Observed實(shí)現(xiàn)嵌套對(duì)象深度監(jiān)聽(tīng)
class CartItem {
  constructor(public id: number, public price: number) {}
}

@Component
struct CheckoutPanel {
  //@Watch與@Link配合實(shí)現(xiàn)雙向數(shù)據(jù)流
  @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(`實(shí)付:¥${this.total.toFixed(2)}`)
    }
  }
}
  • 跨組件狀態(tài)同步: 支持多頁(yè)面實(shí)時(shí)響應(yīng)主題變化
 @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)
  }
}
  • 防循環(huán)陷阱:避免在回調(diào)中直接修改被監(jiān)聽(tīng)的同一狀態(tài)變量
@Component struct SafeCounter {
    @State count: number = 0;
    @Watch('onChange')
    onChange() {
      setTimeout(() => this.count++, 0); // 使用中間層避免直接修改
    }
}

二、$$語(yǔ)法:動(dòng)態(tài)模板字符串插值

基礎(chǔ)語(yǔ)法

  • 在 ArkTS 中,使用雙美元符號(hào) $$ 實(shí)現(xiàn)字符串模板插值,將變量動(dòng)態(tài)嵌入字符串中
let name = "Alice";
let greeting = $$`Hello, ${name}!`;
// 輸出: Hello, Alice!
  • 與 UI 組件結(jié)合
// 簡(jiǎn)單插值
Text(`Hello, ${this.username}!`);
// 表達(dá)式支持
Text(`Price: $${(this.price * 0.9).toFixed(2)}`);

// 動(dòng)態(tài)樣式綁定
Row() {
  Text(`Status: ${
    this.status === 'success' ? '?' : '?'
  }`)
    .color(this.getStatusColor())
}
  • 避免重復(fù)計(jì)算:將復(fù)雜表達(dá)式提取為方法
getFormattedPrice() {
    return this.price.toFixed(2);
}

Text(`Price: $${this.getFormattedPrice()}`)

三、@Track 裝飾器:對(duì)象屬性級(jí)更新優(yōu)化

核心功能
  • 針對(duì)class對(duì)象的屬性級(jí)更新優(yōu)化,解決傳統(tǒng)狀態(tài)管理中全量刷新的性能問(wèn)題。通過(guò)標(biāo)記特定屬性,實(shí)現(xiàn)增量渲染。
  • @Track推薦用于管理包含多個(gè)屬性的復(fù)雜對(duì)象,特別是在頻繁更新且需要優(yōu)化渲染性能的場(chǎng)景
運(yùn)行機(jī)制
  • 白名單機(jī)制:僅跟蹤被@Track標(biāo)記的屬性,避免未跟蹤屬性的全量刷新
  • 必要標(biāo)記原則:所有可能在UI中使用的class屬性都應(yīng)添加@Track
  • 屬性級(jí)監(jiān)聽(tīng):基于JavaScript的Proxy實(shí)現(xiàn)屬性訪問(wèn)攔截
  • 增量渲染:僅更新被標(biāo)記屬性關(guān)聯(lián)的 UI 節(jié)點(diǎn)
  • 兼容性:與 @State、@Link 等裝飾器無(wú)縫集成
  • 避免混合模式:同一class對(duì)象中不應(yīng)同時(shí)存在@Track與非@Track屬性
  • 性能考量:對(duì)于包含大量屬性的復(fù)雜對(duì)象,優(yōu)先使用結(jié)構(gòu)化數(shù)據(jù)(如Record)
典型用例
// 跟蹤類定義
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)
    ])
  }
}

四、裝飾器協(xié)同模式

@Watch + @Track 組合

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}`);
  }
}

$$語(yǔ)法與狀態(tài)管理結(jié)合

@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)
    ])
  }
}

五、總結(jié)與建議

  • @Watch 適用于需要即時(shí)響應(yīng)的狀態(tài)變化場(chǎng)景,如按鈕點(diǎn)擊、輸入框輸入等交互操作,使用原則:
    • 回調(diào)函數(shù)保持純粹(無(wú)副作用)
    • 避免深度嵌套監(jiān)聽(tīng)(超過(guò) 3 層需重構(gòu))
    • 使用 debounce 處理高頻事件(如輸入框)
  • @Track 推薦用于管理包含多個(gè)屬性的復(fù)雜對(duì)象,特別是在頻繁更新且需要優(yōu)化渲染性能的場(chǎng)景,使用原則:
    • 所有 UI 可見(jiàn)屬性必須標(biāo)記 @Track
    • 非 UI 屬性禁止使用 @Track
    • 復(fù)雜對(duì)象采用扁平化設(shè)計(jì)(如 @Track 單獨(dú)字段)
  • “$$”語(yǔ)法優(yōu)化:
    • 預(yù)計(jì)算復(fù)雜表達(dá)式
    • 使用 computed 屬性緩存結(jié)果
    @State username: string = 'User';
    @Computed get displayName() {
      return this.username.toUpperCase();
    }
    
    Text(`Hello, ${this.displayName}`)
    

六、常見(jiàn)問(wèn)題

  • 未觸發(fā)回調(diào):
    • 檢查 @Watch 參數(shù)是否與屬性名完全匹配
    • 確認(rèn)狀態(tài)變量是 @State/@Prop/@Link 裝飾的
  • 渲染異常:
    • 確保 @Track 屬性在 UI 中合法使用
    • 檢查非 @Track 屬性是否意外綁定到 UI

我是今陽(yáng),如果想要進(jìn)階和了解更多的干貨,歡迎關(guān)注微信公眾號(hào) “今陽(yáng)說(shuō)” 接收我的最新文章

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容