• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Computed Decorator: Declaring Computed Properties
2<!--Kit: ArkUI-->
3<!--Subsystem: ArkUI-->
4<!--Owner: @liwenzhen3-->
5<!--Designer: @s10021109-->
6<!--Tester: @TerryTsao-->
7<!--Adviser: @zhang_yixin13-->
8
9When the same computation logic is repeatedly bound in the UI, the \@Computed decorator helps prevent redundant calculations. A computed property is evaluated only once when its dependent state variables change, addressing performance issues caused by repeated calculations in the UI. Example:
10
11```ts
12@Computed
13get sum() {
14  return this.count1 + this.count2 + this.count3;
15}
16Text(`${this.count1 + this.count2 + this.count3}`) // Calculate the sum of three counters.
17Text(`${this.count1 + this.count2 + this.count3}`) // Repeat the same calculation.
18Text(`${this.sum}`) // Read the cached value from the @Computed sum, avoiding redundant calculations.
19Text(`${this.sum}`) // Read the cached value from the @Computed sum, avoiding redundant calculations.
20```
21
22Before reading this topic, you are advised to read [\@ComponentV2](./arkts-new-componentV2.md), [\@ObservedV2 and \@Trace](./arkts-new-observedV2-and-trace.md), and [\@Local](./arkts-new-local.md).
23
24>**NOTE**
25>
26> The \@Computed decorator is supported since API version 12.
27>
28> This decorator can be used in atomic services since API version 12.
29
30## Overview
31
32\@Computed is a method decorator that decorates getter methods. It detects changes in the computed properties and ensures the calculation is performed only once when the properties change. Avoid modifying variables inside @Computed, as incorrect usage may lead to untracked data or application freezes. For details, see [Constraints](#constraints).
33
34For simple calculations, using @Computed is not recommended due to its inherent overhead. For complex computations, \@Computed provides significant performance benefits.
35
36## Decorator Description
37\@Computed syntax:
38
39```ts
40@Computed
41get varName(): T {
42    return value;
43}
44```
45
46| \@Computed Decorator| Description                                                 |
47| ------------------ | ----------------------------------------------------- |
48| Supported type          | Getter accessor.|
49| Initialization from the parent component    | Forbidden.|
50| Child component initialization    | \@Param. |
51| Execution time      | In \@ComponentV2, \@Computed is initialized when the custom component is created, triggering computation.<br>In @ObservedV2 decorated classes, \@Computed is initialized asynchronously after the class instance is created, triggering computation.<br>Recomputation occurs when state variables used in the \@Computed calculation are modified.|
52| Value assignment allowed      | No. @Computed decorated properties are read-only. For details, see [Constraints](#constraints).|
53
54## Constraints
55
56- \@Computed is a method decorator and can only decorate getter methods.
57
58  ```ts
59  @Computed
60  get fullName() { // Correct usage.
61    return this.firstName + ' ' + this.lastName;
62  }
63  @Computed val: number = 0; // Incorrect usage. An error is reported during compilation.
64  @Computed
65  func() { // Incorrect usage. An error is reported during compilation.
66  }
67  ```
68- Methods decorated with \@Computed only recompute during initialization or when the state variables used in their calculations change. Avoid performing logic operations other than data retrieval in \@Computed decorated getter methods, as shown in the example below.
69
70```ts
71@Entry
72@ComponentV2
73struct Page {
74  @Local firstName: string = 'Hua';
75  @Local lastName: string = 'Li';
76  @Local showFullNameRequestCount: number = 0;
77  private fullNameRequestCount: number = 0;
78
79  @Computed
80  get fullName() {
81    console.info(`fullName`);
82    // Avoid assignment logic in @Computed calculations, as @Computed is essentially a getter accessor for optimizing repeated computations.
83    // In this example, fullNameRequestCount only represents the number of @Computed recalculations, not the number of times fullName is accessed.
84    this.fullNameRequestCount++;
85    return this.firstName + ' ' + this.lastName;
86  }
87
88  build() {
89    Column() {
90      Text(`${this.fullName}`) // Obtain fullName once.
91      Text(`${this.fullName}`) // Obtain fullName again. fullName is obtained twice. However, no recomputation occurs, as the cached value is read.
92
93      // Clicking the button obtains the value of fullNameRequestCount.
94      Text(`count ${this.showFullNameRequestCount}`)
95      Button('get fullName').onClick(() => {
96        this.showFullNameRequestCount = this.fullNameRequestCount;
97      })
98    }
99  }
100}
101```
102
103- In \@Computed decorated getter methods, do not modify properties involved in the calculation to prevent infinite recomputation leading to application freezes.
104 In the example below, computing **fullName1** modifies **this.lastName**, which triggers recomputation of **fullName2**. During computation of **fullName2**, **this.firstName** is modified, causing **fullName1** to recompute again. This creates an infinite loop, eventually leading to application freezes.
105
106```ts
107@Entry
108@ComponentV2
109struct Page {
110  @Local firstName: string = 'Hua';
111  @Local lastName: string = 'Li';
112
113  @Computed
114  get fullName1() {
115    console.info(`fullName1`);
116    this.lastName += 'a'; // Incorrect usage. The properties involved in computation cannot be changed.
117    return this.firstName + ' ' + this.lastName;
118  }
119
120  @Computed
121  get fullName2() {
122    console.info(`fullName2`);
123    this.firstName += 'a'; // Incorrect usage. The properties involved in computation cannot be changed.
124    return this.firstName + ' ' + this.lastName;
125  }
126
127  build() {
128    Column() {
129      Text(`${this.fullName1}`)
130      Text(`${this.fullName2}`)
131    }
132  }
133}
134```
135
136- \@Computed cannot be used together with **!!** for two-way binding. Properties decorated with \@Computed are getter accessors. They are not synchronized by child components and cannot be assigned. Custom setter implementations for computed properties will not take effect and will result in a compile-time error.
137
138  ```ts
139  @ComponentV2
140  struct Child {
141    @Param double: number = 100;
142    @Event $double: (val: number) => void;
143
144    build() {
145      Button('ChildChange')
146        .onClick(() => {
147          this.$double(200);
148        })
149    }
150  }
151
152  @Entry
153  @ComponentV2
154  struct Index {
155    @Local count: number = 100;
156
157    @Computed
158    get double() {
159      return this.count * 2;
160    }
161
162    // Custom setters for @Computed decorated properties have no effect and cause compile-time errors.
163    set double(newValue : number) {
164      this.count = newValue / 2;
165    }
166
167    build() {
168      Scroll() {
169        Column({ space: 3 }) {
170          Text(`${this.count}`)
171          // Incorrect usage. @Computed decorated properties are read-only and cannot be used together with two-way binding.
172          Child({ double: this.double!! })
173        }
174      }
175    }
176  }
177  ```
178
179- \@Computed is a feature of state management V2 and can only be used in @ComponentV2 and @ObservedV2.
180- When using multiple \@Computed decorated properties together, avoid circular dependencies to prevent infinite loops during computation.
181
182  ```ts
183  @Local a : number = 1;
184  @Computed
185  get b() {
186    return this.a + ' ' + this.c;  // Incorrect usage. There is a circular dependency: b -> c -> b.
187  }
188  @Computed
189  get c() {
190    return this.a + ' ' + this.b; // Incorrect usage. There is a circular dependency: c -> b -> c.
191  }
192  ```
193
194## Use Scenarios
195### The \@Computed Decorated Getter is Evaluated Only Once Upon Property Change
1961. Using a computed property in a custom component
197
198- Clicking the first button changes the value of **lastName**, triggering a recomputation of the \@Computed decorated property **fullName**.
199- **this.fullName** is bound to two **Text** components. The **fullName** log shows that the computation occurs only once.
200- For the first two **Text** components, the **this.lastName +' '+ this.firstName** logic is evaluated twice.
201- If multiple UI elements require the same computed logic **this.lastName +' '+ this.firstName**, you can use a computed property to reduce redundant calculations.
202- Clicking the second button increments the value of **age**, but the UI remains unchanged. This is because **age** is not a state variable, and only changes to observed variables can trigger the recomputation of the \@Computed decorated property **fullName**.
203
204```ts
205@Entry
206@ComponentV2
207struct Index {
208  @Local firstName: string = 'Li';
209  @Local lastName: string = 'Hua';
210  age: number = 20; // Computed cannot be triggered.
211
212  @Computed
213  get fullName() {
214    console.info('---------Computed----------');
215    return this.firstName + ' ' + this.lastName + this.age;
216  }
217
218  build() {
219    Column() {
220      Text(this.lastName + ' ' + this.firstName)
221      Text(this.lastName + ' ' + this.firstName)
222      Divider()
223      Text(this.fullName)
224      Text(this.fullName)
225      Button('changed lastName').onClick(() => {
226        this.lastName += 'a';
227      })
228
229      Button('changed age').onClick(() => {
230        this.age++;  // Computed cannot be triggered.
231      })
232    }
233  }
234}
235```
236
237Computed properties inherently introduce performance overhead. In practical development, note the following:
238- For simple logic, avoid computed properties and compute directly.
239- If the logic is used only once in the view, skip the computed property and evaluate inline.
240
2412. Using a computed property in an \@ObservedV2 decorated class
242- Clicking the button changes the value of **lastName**, triggering the \@Computed decorated property **fullName** to recompute once.
243
244```ts
245@ObservedV2
246class Name {
247  @Trace firstName: string = 'Hua';
248  @Trace lastName: string = 'Li';
249
250  @Computed
251  get fullName() {
252    console.info('---------Computed----------');
253    return this.firstName + ' ' + this.lastName;
254  }
255}
256
257const name: Name = new Name();
258
259@Entry
260@ComponentV2
261struct Index {
262  name1: Name = name;
263
264  build() {
265    Column() {
266      Text(this.name1.fullName)
267      Text(this.name1.fullName)
268      Button('changed lastName').onClick(() => {
269        this.name1.lastName += 'a';
270      })
271    }
272  }
273}
274```
275
276### \@Monitor Can Listen for the Changes of the \@Computed Decorated Properties
277The following example shows how to convert **celsius** to **fahrenheit** and **kelvin** :
278- Clicking **-** decrements **celsius**, updates **fahrenheit**, then updates **kelvin**, which triggers **onKelvinMonitor**.
279- Clicking **+** increments **celsius**, updates **fahrenheit**, then updates **kelvin**, which triggers **onKelvinMonitor**.
280
281```ts
282@Entry
283@ComponentV2
284struct MyView {
285  @Local celsius: number = 20;
286
287  @Computed
288  get fahrenheit(): number {
289    return this.celsius * 9 / 5 + 32; // C -> F
290  }
291
292  @Computed
293  get kelvin(): number {
294    return (this.fahrenheit - 32) * 5 / 9 + 273.15; // F -> K
295  }
296
297  @Monitor('kelvin')
298  onKelvinMonitor(mon: IMonitor) {
299    console.log('kelvin changed from ' + mon.value()?.before + ' to ' + mon.value()?.now);
300  }
301
302  build() {
303    Column({ space: 20 }) {
304      Row({ space: 20 }) {
305        Button('-')
306          .onClick(() => {
307            this.celsius--;
308          })
309
310        Text(`Celsius ${this.celsius.toFixed(1)}`).fontSize(50)
311
312        Button('+')
313          .onClick(() => {
314            this.celsius++;
315          })
316      }
317
318      Text(`Fahrenheit ${this.fahrenheit.toFixed(2)}`).fontSize(50)
319      Text(`Kelvin ${this.kelvin.toFixed(2)}`).fontSize(50)
320    }
321    .width('100%')
322  }
323}
324```
325### \@Computed Decorated Properties Can Initialize \@Param
326The following example shows how to use an \@Computed decorated property to initialize \@Param.
327- Clicking **Button('-')** and **Button('+')** changes the value of **quantity**, which is decorated with \@Trace and can be observed when it is changed.
328- The change of **quantity** triggers the recomputation of **total** and **qualifiesForDiscount**.
329- The change of **total** and **qualifiesForDiscount** triggers the update of the **Text** component corresponding to the **Child** component.
330
331```ts
332@ObservedV2
333class Article {
334  @Trace quantity: number = 0;
335  unitPrice: number = 0;
336
337  constructor(quantity: number, unitPrice: number) {
338    this.quantity = quantity;
339    this.unitPrice = unitPrice;
340  }
341}
342
343@Entry
344@ComponentV2
345struct Index {
346  @Local shoppingBasket: Article[] = [new Article(1, 20), new Article(5, 2)];
347
348  @Computed
349  get total(): number {
350    return this.shoppingBasket.reduce((acc: number, item: Article) => acc + (item.quantity * item.unitPrice), 0);
351  }
352
353  @Computed
354  get qualifiesForDiscount(): boolean {
355    return this.total >= 100;
356  }
357
358  build() {
359    Column() {
360      Text(`Shopping List: `).fontSize(30)
361      ForEach(this.shoppingBasket, (item: Article) => {
362        Row() {
363          Text(`unitPrice: ${item.unitPrice}`)
364          Button('-').onClick(() => {
365            if (item.quantity > 0) {
366              item.quantity--;
367            }
368          })
369          Text(`quantity: ${item.quantity}`)
370          Button('+').onClick(() => {
371            item.quantity++;
372          })
373        }
374
375        Divider()
376      })
377      Child({ total: this.total, qualifiesForDiscount: this.qualifiesForDiscount })
378    }.alignItems(HorizontalAlign.Start)
379  }
380}
381
382@ComponentV2
383struct Child {
384  @Param total: number = 0;
385  @Param qualifiesForDiscount: boolean = false;
386
387  build() {
388    Row() {
389      Text(`Total: ${this.total} `).fontSize(30)
390      Text(`Discount: ${this.qualifiesForDiscount} `).fontSize(30)
391    }
392  }
393}
394```
395