1# \@Track装饰器:class对象属性级更新 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @jiyujia926--> 5<!--Designer: @s10021109--> 6<!--Tester: @TerryTsao--> 7<!--Adviser: @zhang_yixin13--> 8 9 10\@Track应用于class对象的属性级更新。\@Track装饰的属性变化时,只会触发该属性关联的UI更新。 11 12 13在阅读本文档之前,建议开发者对状态管理基本观察能力有基本的了解。建议提前阅读:[\@State](./arkts-state.md)。 14 15> **说明:** 16> 17> 从API version 11开始,该装饰器支持在ArkTS卡片中使用。 18> 19> 从API version 12开始,该装饰器支持在原子化服务中使用。 20 21 22## 概述 23 24\@Track是class对象的属性装饰器。当一个class对象是状态变量时,\@Track装饰的属性发生变化,只会触发该属性关联的UI更新;如果class类中使用了\@Track装饰器,则未被\@Track装饰器装饰的属性不能在UI中使用,如果使用,会发生运行时报错。 25 26## class属性级更新说明 27 28状态管理V1中\@State等装饰器默认支持观察第一层属性的变化,第一层属性的变化虽然可以触发更新,但无法做到类属性级的观察,如下面例子展示了这一限制: 29 30```ts 31class Info { 32 name: string = 'Jack'; 33 age: number = 12; 34} 35 36@Entry 37@Component 38struct Index { 39 @State info: Info = new Info(); 40 41 // 借助getFontSize的日志打印,可以分辨哪个组件触发了渲染 42 getFontSize(id: number): number { 43 console.info(`Component ${id} render`); 44 return 30; 45 } 46 47 build() { 48 Column() { 49 Text(`name: ${this.info.name}`) 50 .fontSize(this.getFontSize(1)) 51 Text(`age: ${this.info.age}`) 52 .fontSize(this.getFontSize(2)) 53 54 // 点击当前Button,可以发现当前虽然仅改变了name属性 55 // 但是依旧会触发两个Text的刷新 56 // Text(`age: ${this.info.age}`)是冗余刷新 57 Button('change name').onClick(() => { 58 this.info.name = 'Jane'; 59 }) 60 61 // 点击当前Button,可以发现当前虽然仅改变了age属性 62 // 但是依旧会触发两个Text的刷新 63 // Text(`name: ${this.info.name}`)是冗余刷新 64 Button('change age').onClick(() => { 65 this.info.age++; 66 }) 67 } 68 .height('100%') 69 .width('100%') 70 } 71} 72``` 73 74> **说明:** 75> 76> 当UI刷新时,会执行组件的属性设置方法,利用这一机制可以通过观察`getFontSize`方法是否被调用来判断当前组件是否刷新。 77 78- UI首次渲染完成,观察到输出如下日志: 79``` 80Component 1 render 81Component 2 render 82``` 83- 当点击`Button('change name')`时,即使只修改了`info.name`,观察日志发现两个Text组件仍会重新渲染。组件```Text(age: ${this.info.age})```并未使用`name`属性,但仍因为`info.name`改变刷新,因此这次刷新是冗余的。日志输出如下: 84``` 85Component 1 render 86Component 2 render 87``` 88- 同理,点击`Button('change age')`,也会触发```Text(`name: ${this.info.name}`)```的刷新。日志输出如下: 89``` 90Component 1 render 91Component 2 render 92``` 93 94造成上述冗余刷新的根本原因是:状态管理V1中\@State等装饰器无法精准观察类属性的访问与变更。为了实现类对象属性的精准观察,引入\@Track装饰器。 95 96 97## 装饰器说明 98 99| \@Track变量装饰器 | 说明 | 100| ------------------ | -------------------- | 101| 装饰器参数 | 无 | 102| 可装饰的变量 | class对象的非静态成员属性。 | 103 104 105 106## 观察变化和行为表现 107 108当一个class对象是状态变量时,\@Track装饰的属性发生变化,该属性关联的UI触发更新。 109 110> **说明:** 111> 112> 当class对象中没有一个属性被标记\@Track,行为与原先保持不变。\@Track没有深度观测的功能。 113 114使用\@Track装饰器可以避免冗余刷新。 115 116```ts 117class LogTrack { 118 @Track str1: string; 119 @Track str2: string; 120 121 constructor(str1: string) { 122 this.str1 = str1; 123 this.str2 = 'World'; 124 } 125} 126 127class LogNotTrack { 128 str1: string; 129 str2: string; 130 131 constructor(str1: string) { 132 this.str1 = str1; 133 this.str2 = '世界'; 134 } 135} 136 137@Entry 138@Component 139struct AddLog { 140 @State logTrack: LogTrack = new LogTrack('Hello'); 141 @State logNotTrack: LogNotTrack = new LogNotTrack('你好'); 142 143 isRender(index: number) { 144 console.log(`Text ${index} is rendered`); 145 return 50; 146 } 147 148 build() { 149 Row() { 150 Column() { 151 Text(this.logTrack.str1) // Text1 152 .fontSize(this.isRender(1)) 153 .fontWeight(FontWeight.Bold) 154 Text(this.logTrack.str2) // Text2 155 .fontSize(this.isRender(2)) 156 .fontWeight(FontWeight.Bold) 157 Button('change logTrack.str1') 158 .onClick(() => { 159 this.logTrack.str1 = 'Bye'; 160 }) 161 Text(this.logNotTrack.str1) // Text3 162 .fontSize(this.isRender(3)) 163 .fontWeight(FontWeight.Bold) 164 Text(this.logNotTrack.str2) // Text4 165 .fontSize(this.isRender(4)) 166 .fontWeight(FontWeight.Bold) 167 Button('change logNotTrack.str1') 168 .onClick(() => { 169 this.logNotTrack.str1 = '再见'; 170 }) 171 } 172 .width('100%') 173 } 174 .height('100%') 175 } 176} 177``` 178 179在上面的示例中: 180 1811. 类LogTrack中的属性均被\@Track装饰器装饰,点击按钮"change logTrack.str1",此时Text1刷新,Text2不刷新,只有一条日志输出,避免了冗余刷新。 182 ```ts 183 Text 1 is rendered 184 ``` 185 1862. 类logNotTrack中的属性均未被\@Track装饰器装饰,点击按钮"change logNotTrack.str1",此时Text3、Text4均会刷新,有两条日志输出,存在冗余刷新。 187 ```ts 188 Text 3 is rendered 189 Text 4 is rendered 190 ``` 191 192## 限制条件 193 194- 如果class类中使用了\@Track装饰器,那么该class类中非\@Track装饰的属性不能在\@Component UI中使用,包括不能绑定在组件上、不能用于初始化子组件,错误的使用将导致运行时报错,详见[在UI中使用非\@Track装饰的属性发生运行时报错](#在ui中使用非track装饰的属性发生运行时报错);可以在非UI中使用非\@Track装饰的属性,如事件回调函数中、生命周期函数中等。 195 196- API version 18及以后,\@Track使用在\@ComponentV2的UI中,不会引起运行时报错,但依旧不会刷新。具体例子见[常见场景](./arkts-v1-v2-mixusage.md#observed装饰的class)。 197 198- 建议开发者不要混用包含\@Track的class对象和不包含\@Track的class对象,如联合类型中、类继承中等。 199 200 201## 使用场景 202 203### \@Track和自定义组件更新 204 205以下示例展示组件更新和\@Track的处理步骤。对象log是\@State装饰的状态变量,logInfo是\@Track的成员属性,其余成员属性都是非\@Track装饰的,而且也不准备在UI中更新它们的值。 206 207 208```ts 209class Log { 210 @Track logInfo: string; 211 owner: string; 212 id: number; 213 time: Date; 214 location: string; 215 reason: string; 216 217 constructor(logInfo: string) { 218 this.logInfo = logInfo; 219 this.owner = 'OH'; 220 this.id = 0; 221 this.time = new Date(); 222 this.location = 'CN'; 223 this.reason = 'NULL'; 224 } 225} 226 227@Entry 228@Component 229struct AddLog { 230 @State log: Log = new Log('origin info.'); 231 232 build() { 233 Row() { 234 Column() { 235 Text(this.log.logInfo) 236 .fontSize(50) 237 .fontWeight(FontWeight.Bold) 238 .onClick(() => { 239 // 没有被@Track装饰的属性可以在点击事件中使用。 240 console.log('owner: ' + this.log.owner + 241 ' id: ' + this.log.id + 242 ' time: ' + this.log.time + 243 ' location: ' + this.log.location + 244 ' reason: ' + this.log.reason); 245 this.log.time = new Date(); 246 this.log.id++; 247 248 this.log.logInfo += ' info.'; 249 }) 250 } 251 .width('100%') 252 } 253 .height('100%') 254 } 255} 256``` 257 258处理步骤: 259 2601. AddLog自定义组件的Text.onClick点击事件自增字符串' info.'。 261 2622. 由于\@State log变量的\@Track属性logInfo更改,Text重新渲染。 263 264## 常见问题 265 266### 在UI中使用非\@Track装饰的属性发生运行时报错 267 268在UI中使用非\@Track装饰的属性,运行时会报错。 269 270```ts 271class Person { 272 // id被@Track装饰 273 @Track id: number; 274 // age未被@Track装饰 275 age: number; 276 277 constructor(id: number, age: number) { 278 this.id = id; 279 this.age = age; 280 } 281} 282 283@Entry 284@Component 285struct Parent { 286 @State parent: Person = new Person(2, 30); 287 288 build() { 289 // 没有被@Track装饰的属性不可以在UI中使用,运行时会报错 290 Text(`Parent id is: ${this.parent.id} and Parent age is: ${this.parent.age}`) 291 } 292} 293``` 294