• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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