ArkTS 进阶之道(30):@Track 精准观测边界——为啥 class 属性级观测只刷关联 UI 根因

本文是「ArkTS 进阶之道」系列第 30 篇,续「ArkUI 状态联动」深水区。上篇讲环境变量状态边界(Environment 设备环境查询只读不可写单向链)。本文讲精准观测边界:@Track class 属性级精准观测 ------根因在只刷关联 UI 避免冗余刷新。能力系列篇 30 讲过 @Track 怎么用,本文讲为哈 @Track 精准观测合法只刷关联 UI 不整体刷新------根因在属性级观测槽。

一、开篇:@Track 不是整体刷新,是 class 属性级精准观测

你写 React 时,精准观测是「依赖数组魔法」(useEffect 依赖数组控制触发范围):

typescript 复制代码
// React 精准观测:useEffect 依赖数组魔法
function useTrackExample() {
  const [str1, setStr1] = useState('str1-init')
  const [str2, setStr2] = useState('str2-init')

  // ✅ 依赖数组 [str1]:只 str1 变才触发
  useEffect(() => { console.log('str1 changed') }, [str1])
  // ✅ 依赖数组 [str2]:只 str2 变才触发
  useEffect(() => { console.log('str2 changed') }, [str2])
  // ⚠ 依赖数组 [str1, str2]:任一变都触发(冗余触发)
}
// React 精准观测:useEffect 依赖数组魔法(只刷关联副作用)

你写鸿蒙 ArkTS 时,@Track class 属性级精准观测------只刷关联 UI 避免冗余刷新:

typescript 复制代码
// ArkTS @Track class 属性级精准观测
@Observed
class TrackClass {
  @Track str1: string = 'str1-init'    // ✅ @Track 装饰,变化时只刷 str1 关联 UI
  @Track str2: string = 'str2-init'    // ✅ @Track 装饰,变化时只刷 str2 关联 UI
}

@Entry
@Component
struct Index {
  @State trackObj: TrackClass = new TrackClass()
  build() {
    Column() {
      Text(`${this.trackObj.str1}`)    // ✅ str1 变只刷这一行
      Text(`${this.trackObj.str2}`)    // ✅ str2 变只刷这一行
      Button('改 str1').onClick(() => { this.trackObj.str1 = 'changed' })
    }
  }
}
// @Track class 属性级精准观测:只刷关联 UI,避免冗余刷新

useEffect 依赖数组魔法 vs 属性级精准观测的区别:React 把精准观测当 useEffect 依赖数组魔法(依赖数组控制触发范围),ArkTS 把 @Track 当「属性级精准观测」(@Track 装饰属性变化只刷关联 UI)。根因不是 useEffect 依赖数组魔法是属性级精准观测------@Track class 属性级精准观测只刷关联 UI,避免冗余刷新。

二、根因:@Track 的属性级精准观测机制

鸿蒙 ArkUI 的 @Track 是属性级精准观测绑定------@Track 装饰属性变化只刷关联 UI,来自三重绑定机制。

机制 1:@Track class 属性级观测------不是整体刷新

@Track 荬饰器class 属性级观测------@Track 装饰的属性变化只触发该属性关联的 UI 更新:

typescript 复制代码
// ✅ @Observed + @Track:class 属性级精准观测
@Observed
class TrackClass {
  @Track str1: string = 'str1-init'    // ✅ @Track 装饰,变化时只刷 str1 关联 UI
  @Track str2: string = 'str2-init'    // ✅ @Track 装饰,变化时只刷 str2 关联 UI
}

// ⚠ 对比类:不用 @Track(V1 默认行为,整体刷新)
@Observed
class NoTrackClass {
  str1: string = 'str1-init'    // ⚠ 未装饰 @Track,变化时触发整个 class 关联 UI 刷新
  str2: string = 'str2-init'    // ⚠ 未装饰 @Track
}

@Track 装饰属性 vs 未装饰属性的区别:@Track 装饰属性(str1)变化只刷 str1 关联 UI,未装饰属性(str1)变化触发整个 class 关联 UI 都刷(冗余刷新)。根因不是整体刷新是属性级观测------@Track class 属性级观测只刷关联 UI,避免冗余刷新。

机制 2:@Track 只刷关联 UI------避免冗余刷新

@Track 荬饰器只刷关联 UI------@Track 装饰属性变化只刷该属性关联的 UI,其他 UI 不刷:

typescript 复制代码
// ✅ @Track 只刷关联 UI(避免冗余刷新)
@Entry
@Component
struct Index {
  @State trackObj: TrackClass = new TrackClass()

  build() {
    Column() {
      // ✅ str1 变只刷这一行,str2 行不刷
      Text(`str1 = ${this.trackObj.str1}`)
      // ✅ str2 变只刷这一行,str1 行不刷
      Text(`str2 = ${this.trackObj.str2}`)
      Button('改 str1').onClick(() => {
        this.trackObj.str1 = 'str1-changed'    // ✅ 只刷 str1 行,str2 行不刷
      })
    }
  }
}
// @Track 只刷关联 UI:str1 变只刷 str1 行,str2 行不刷(避免冗余刷新)

@Track 只刷关联 UI vs 不用 @Track 整体刷新的区别:@Track 只刷关联 UI(str1 变只刷 str1 行,str2 行不刷),不用 @Track 整体刷新(str1 变整个 class 关联 UI 都刷,str2 行也刷冗余)。根因不是整体刷新是属性级观测------@Track 只刷关联 UI,避免冗余刷新。

机制 3:@Track 限制条件------非 @Track 属性不能在 UI 中使用

@Track 荬饰器限制条件------如果 class 类中使用了 @Track 装饰器,则未被 @Track 装饰的属性不能在 UI 中使用:

typescript 复制代码
// ⚠ @Track 限制条件:非 @Track 属性不能在 UI 中使用
@Observed
class TrackClass {
  @Track str1: string = 'str1-init'    // ✅ @Track 装饰
  str2: string = 'str2-init'    // ⚠ 未装饰 @Track,不能在 UI 中使用
}

@Entry
@Component
struct Index {
  @State trackObj: TrackClass = new TrackClass()

  build() {
    Column() {
      // ✅ str1 被 @Track 装饰,可以在 UI 中使用
      Text(`str1 = ${this.trackObj.str1}`)
      // ⚠ str2 未被 @Track 装饰,在 UI 中使用会运行时报错
      // Text(`str2 = ${this.trackObj.str2}`)    // ❌ 运行时报错
    }
  }
}
// @Track 限制条件:class 中用了 @Track,非 @Track 属性不能在 UI 中使用(运行时报错)

@Track 限制条件 vs 未装饰属性可用的区别:@Track 限制条件(class 中用了 @Track,非 @Track 属性不能在 UI 中使用,运行时报错),未装饰属性可用(class 中没用 @Track,所有属性都能在 UI 中使用)。根因不是未装饰属性可用是 @Track 限制条件------@Track 限制条件非 @Track 属性不能在 UI 中使用,运行时报错。

三、真机配图:@Track 精准观测边界------只刷关联 UI

真机配图展示 @Track class 属性级精准观测只刷关联 UI:

  • @Track 精准观测区(只刷关联 UI):trackObj.str1 = str1-init / trackObj.str2 = str2-init,改 str1 只刷 str1 行,str2 行不刷
  • 不用 @Track 对比区(整体冗余刷新):noTrackObj.str1 = str1-init / noTrackObj.str2 = str2-init,改 str1 整个 class 关联 UI 都刷
  • 验证按钮区:改 trackObj.str1 = "str1-changed"(@Track 只刷 str1 行,str2 行不刷)/ 改 noTrackObj.str1 = "str1-changed"(不用 @Track,整个 class 关联 UI 都刷)

四、真解法:@Track 精准观测的三个场景

场景 1:@Track 避免冗余刷新(90% 场景首选,class 属性级精准观测)

@Track 避免冗余刷新用 @Observed + @Track 装饰 class 属性:

typescript 复制代码
// ✅ 场景 1:@Track 避免冗余刷新(class 属性级精准观测)
@Observed
class LogModel {
  @Track level: string = 'INFO'    // ✅ @Track 装饰,变化时只刷 level 关联 UI
  @Track message: string = 'init'    // ✅ @Track 装饰,变化时只刷 message 关联 UI
  @Track timestamp: number = 0    // ✅ @Track 装饰,变化时只刷 timestamp 关联 UI
}

@Entry
@Component
struct Index {
  @State log: LogModel = new LogModel()

  build() {
    Column() {
      Text(`level = ${this.log.level}`)    // ✅ level 变只刷这一行
      Text(`message = ${this.log.message}`)    // ✅ message 变只刷这一行
      Text(`timestamp = ${this.log.timestamp}`)    // ✅ timestamp 变只刷这一行
      Button('改 level').onClick(() => { this.log.level = 'ERROR' })
    }
  }
}
// @Track 避免冗余刷新:level 变只刷 level 行,message/timestamp 行不刷(避免冗余刷新)

场景 2:@Track 与 @Observed 配合(必须 @Observed 装饰 class)

@Track 与 @Observed 配合------@Track 必须在 @Observed 装饰的 class 中使用:

typescript 复制代码
// ✅ 场景 2:@Track 与 @Observed 配合(必须 @Observed 装饰 class)
@Observed    // ✅ @Observed 装饰 class
class UserModel {
  @Track name: string = 'HarmonyOS'    // ✅ @Track 装饰属性
  @Track age: number = 18    // ✅ @Track 装饰属性
}

@Entry
@Component
struct Index {
  @State user: UserModel = new UserModel()

  build() {
    Column() {
      Text(`name = ${this.user.name}`)    // ✅ name 变只刷这一行
      Text(`age = ${this.user.age}`)    // ✅ age 变只刷这一行
      Button('age + 1').onClick(() => { this.user.age++ })
    }
  }
}
// @Track 与 @Observed 配合:@Track 必须在 @Observed 装饰的 class 中使用

@Track 与 @Observed 配合 vs 单独使用 @Track 的区别:@Track 与 @Observed 配合(@Track 必须在 @Observed 装饰的 class 中使用),单独使用 @Track(@Track 不在 @Observed 装饰的 class 中,不生效)。根因不是单独使用 @Track 是 @Track 与 @Observed 配合------@Track 与 @Observed 配合必须 @Observed 装饰 class,否则 @Track 不生效。

场景 3:@Track vs @Trace 边界(V1 属性级观测 vs V2 属性级观测)

@Track vs @Trace 边界------@Track 是 V1 属性级观测装饰器(API 11+),@Trace 是 V2 属性级观测装饰器(API 12+):

typescript 复制代码
// ✅ 场景 3:@Track vs @Trace 边界(V1 属性级观测 vs V2 属性级观测)

// ✅ V1:@Track + @Observed(API 11+)
@Observed
class V1Model {
  @Track name: string = 'V1'    // ✅ @Track 装饰属性,V1 属性级观测
}

// ✅ V2:@Trace + @ObservedV2(API 12+)
@ObservedV2
class V2Model {
  @Trace name: string = 'V2'    // ✅ @Trace 装饰属性,V2 属性级观测
}

// ✅ 边界:
// @Track 是 V1 装饰器(API 11+),配合 @Observed,@Track 装饰属性变化只刷关联 UI
// @Trace 是 V2 装饰器(API 12+),配合 @ObservedV2,@Trace 装饰属性变化只刷关联 UI
// V1 用 @Track,V2 用 @Trace,不能混用(@Track 不在 @ObservedV2 中用,@Trace 不在 @Observed 中用)

@Track vs @Trace 边界的区别:@Track 是 V1 属性级观测装饰器(API 11+,配合 @Observed),@Trace 是 V2 属性级观测装饰器(API 12+,配合 @ObservedV2)。根因不是 V1 属性级观测是 @Track vs @Trace 边界------@Track 是 V1 装饰器配合 @Observed,@Trace 是 V2 装饰器配合 @ObservedV2,不能混用。

五、一句话哲学

写鸿蒙 ArkUI 记住 :@Track 不是整体刷新是 class 属性级精准观测------@Track 荬饰器 class 属性级观测只刷关联 UI,避免冗余刷新。根因不是整体刷新是属性级观测------@Track class 属性级观测(@Track 装饰属性变化只刷关联 UI)+ @Track 只刷关联 UI(str1 变只刷 str1 行,str2 行不刷,避免冗余刷新)+ @Track 限制条件(class 中用了 @Track,非 @Track 属性不能在 UI 中使用,运行时报错)+ @Track 与 @Observed 配合(必须 @Observed 装饰 class)+ @Track vs @Trace 边界(V1 用 @Track 配合 @Observed,V2 用 @Trace 配合 @ObservedV2,不能混用)。@Track 避免冗余刷新用属性级精准观测(首选,90% 场景),@Track 与 @Observed 配合必须 @Observed 装饰 class,@Track vs @Trace 边界选对装饰器(V1 用 @Track,V2 用 @Trace,不能混用)。class 属性级精准观测只刷关联 UI 避免冗余刷新是 ArkUI 状态联动深水区核心!

相关推荐
明月_清风1 小时前
🚀 OpenAI 数据代理架构全解析:从 600 PB 到自然语言的六层上下文工程
前端·后端·架构
明月_清风1 小时前
🚀 从 Foundry 到 AIP:Palantir 发生了什么变化?一篇文章全搞懂
前端·后端
Zane19941 小时前
闭包到底"闭"住了什么?一文讲透 LEGB 规则与循环里的闭包陷阱
后端·python
techdashen1 小时前
Go设计取舍之六: sync.Mutex正常模式与饥饿模式
开发语言·后端·golang
Zane19942 小时前
CAS 与原子类:Java 如何实现无锁编程
java·后端
颜进强2 小时前
前端看后端 13:什么是 Cookie 和 Session?
前端·后端
gitboyzcf2 小时前
mpegts.js解决Chrome无法播放7568×142分辨率报错 DOMException问题
前端·后端
程序员-Benothing2 小时前
Java ForkJoinPool 详解:从分治思想到高性能并行计算
java·开发语言·后端·面试·职场和发展
山水洛行3 小时前
都在聊Context Engineering图工程,可你说的图和他说的不是一个图
后端