• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# \@Computed Decorator: Computed Property
2
3When \@Computed is used, the computation is performed only once when the value changes. It is mainly used to solve the performance problem caused by repeated computation when the UI reuses the property for multiple times.
4
5
6The change of a state variable can trigger the recomputing of its associated \@Computed. Before 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).
7
8>**NOTE**
9>
10>The \@Computed decorator is supported since API version 12.
11>
12
13## Overview
14
15\@Computed is a method decorator that decorates the **getter** method. \@Computed detects the change of the computed property. When this property changes, \@Computed is solved only once.
16For complex computing, \@Computed provides better performance.
17
18
19## Decorator Description
20\@Computed syntax:
21
22```ts
23@Computed
24get varName(): T {
25    return value;
26}
27```
28
29| \@Computed Method-Type Decorator| Description                                                 |
30| ------------------ | ----------------------------------------------------- |
31| Supported type          | The **getter** accessor.|
32| Initialization from the parent component     | Forbidden.|
33| Child component initialization     | \@Param. |
34| Execution time       | When \@ComponentV2 is initialized, the computed property will be triggered. When the computed value changes, the computed property will be also triggered.|
35|Value assignment allowed        | No. @Computed decorated properties are read-only. For details, see [Constraints](#constraints).|
36
37## Constraints
38
39- \@Computed is a method decorator, which can decorate only the **getter** method.
40
41  ```ts
42  @Computed
43  get fullName() { // Correct format.
44    return this.firstName + ' ' + this.lastName;
45  }
46  @Computed val: number = 0; // Incorrect format. An error is reported during compilation.
47  @Computed
48  func() { // Incorrect usage. An error is reported during compilation.
49  }
50  ```
51- In the **getter** method decorated by \@Computed, the properties involved in computation cannot be changed.
52
53  ```ts
54  @Computed
55  get fullName() {
56    this.lastName += 'a'; // Error. The properties involved in computation cannot be changed.
57    return this.firstName + ' ' + this.lastName;
58  }
59  ```
60
61- \@Computed cannot be used together with **!!**. That is, \@Computed decorates the **getter** accessor, which is not synchronized by the child components nor assigned a value. **setter** of the computed property implemented by you does not take effect, and an error is reported during compilation.
62
63  ```ts
64  @ComponentV2
65  struct Child {
66    @Param double: number = 100;
67    @Event $double: (val: number) => void;
68
69    build() {
70      Button('ChildChange')
71        .onClick(() => {
72          this.$double(200);
73        })
74    }
75  }
76
77  @Entry
78  @ComponentV2
79  struct Index {
80    @Local count: number = 100;
81
82    @Computed
83    get double() {
84      return this.count * 2;
85    }
86
87    // The @Computed decorated property is read-only. The setter implemented by you does not take effect, and an error is reported during compilation.
88    set double(newValue : number) {
89      this.count = newValue / 2;
90    }
91
92    build() {
93      Scroll() {
94        Column({ space: 3 }) {
95          Text(`${this.count}`)
96          // Incorrect format. The @Computed decorated property method is read-only and cannot be used together with two-way binding.
97          Child({ double: this.double!! })
98        }
99      }
100    }
101  }
102  ```
103
104- The capability provided by \@Computed for the status management V2 can be used only in \@ComponentV2 and \@ObservedV2.
105- Be cautious about loop solving when multiple \@Computed are used together.
106
107  ```ts
108  @Local a : number = 1;
109  @Computed
110  get b() {
111    return this.a + ' ' + this.c;  // Incorrect format. A loop b -> c -> b exists.
112  }
113  @Computed
114  get c() {
115    return this.a + ' ' + this.b; // Incorrect format. A loop c -> b -> c exists.
116  }
117  ```
118
119## Use Scenarios
120### \@Computed Decorated getter Accessor Is Solved Only Once Upon Property Change
1211. Using computed property in a custom component.
122
123- Click the first button to change the value of **lastName**, triggering **\@Computed fullName** recomputation.
124- The **this.fullName** is bound to two **Text** components. The **fullName** log shows that the computation occurs only once.
125- For the first two **Text** components, the **this.lastName +' '+ this.firstName** logic is solved twice.
126- If multiple places on the UI need to use the **this.lastName +' '+ this.firstName** computational logic, you can use the computed property to reduce the number of computation times.
127- Click the second button. The **age** increases automatically and the UI remains unchanged. Because **age** is not a state variable, only observed changes can trigger **\@Computed fullName** recomputation.
128
129```ts
130@Entry
131@ComponentV2
132struct Index {
133  @Local firstName: string = 'Li';
134  @Local lastName: string = 'Hua';
135  age: number = 20; // Computed cannot be triggered.
136
137  @Computed
138  get fullName() {
139    console.info("---------Computed----------");
140    return this.firstName + ' ' + this.lastName + this.age;
141  }
142
143  build() {
144    Column() {
145      Text(this.lastName + ' ' + this.firstName)
146      Text(this.lastName + ' ' + this.firstName)
147      Divider()
148      Text(this.fullName)
149      Text(this.fullName)
150      Button('changed lastName').onClick(() => {
151        this.lastName += 'a';
152      })
153
154      Button('changed age').onClick(() => {
155        this.age++;  // Computed cannot be triggered.
156      })
157    }
158  }
159}
160```
161
162Note that the computed property itself has performance overhead. In actual application development:
163- For the preceding simple computation, computed property is not needed.
164- If the computed property is used only once in the view, you can solve the problem directly.
165
1662. Using computed property in classes decorated by \@ObservedV2.
167- Click the button to change the value of **lastName** and the **\@Computed fullName** will be recomputed only once.
168
169```ts
170@ObservedV2
171class Name {
172  @Trace firstName: string = 'Li';
173  @Trace lastName: string = 'Hua';
174
175  @Computed
176  get fullName() {
177    console.info('---------Computed----------');
178    return this.firstName + ' ' + this.lastName;
179  }
180}
181
182const name: Name = new Name();
183
184@Entry
185@ComponentV2
186struct Index {
187  name1: Name = name;
188
189  build() {
190    Column() {
191      Text(this.name1.fullName)
192      Text(this.name1.fullName)
193      Button('changed lastName').onClick(() => {
194        this.name1.lastName += 'a';
195      })
196    }
197  }
198}
199```
200
201### \@Monitor can Listen for the Changes of the \@Computed Decorated Properties
202The following example shows how to solve **fahrenheit** and **kelvin** by using computed property.
203- Click "-" to run the logic **celsius--** -> **fahrenheit** -> **kelvin**. The change of **kelvin** triggers the **onKelvinMonitor**.
204- Click "+" to run the logic **celsius++** -> **fahrenheit** -> **kelvin**. The change of **kelvin** triggers the **onKelvinMonitor**.
205
206```ts
207@Entry
208@ComponentV2
209struct MyView {
210  @Local celsius: number = 20;
211
212  @Computed
213  get fahrenheit(): number {
214    return this.celsius * 9 / 5 + 32; // C -> F
215  }
216
217  @Computed
218  get kelvin(): number {
219    return (this.fahrenheit - 32) * 5 / 9 + 273.15; // F -> K
220  }
221
222  @Monitor("kelvin")
223  onKelvinMonitor(mon: IMonitor) {
224    console.log("kelvin changed from " + mon.value()?.before + " to " + mon.value()?.now);
225  }
226
227  build() {
228    Column({ space: 20 }) {
229      Row({ space: 20 }) {
230        Button('-')
231          .onClick(() => {
232            this.celsius--;
233          })
234
235        Text(`Celsius ${this.celsius.toFixed(1)}`).fontSize(50)
236
237        Button('+')
238          .onClick(() => {
239            this.celsius++;
240          })
241      }
242
243      Text(`Fahrenheit ${this.fahrenheit.toFixed(2)}`).fontSize(50)
244      Text(`Kelvin ${this.kelvin.toFixed(2)}`).fontSize(50)
245    }
246    .width('100%')
247  }
248}
249```
250### \@Computed Decorated Properties Initialize \@Param
251The following example uses \@Computed to initialize \@Param.
252- Click **Button('-')** and **Button('+')** to change the offering quantity. The **quantity** is decorated by \@Trace and can be observed when it is changed.
253- The change of **quantity** triggers the recomputation of **total** and **qualifiesForDiscount**. In this way, you can get a result of the total price of the offering and the available discounts.
254- The change of **total** and **qualifiesForDiscount** triggers the update of the **Text** component corresponding to the **Child** component.
255
256```ts
257@ObservedV2
258class Article {
259  @Trace quantity: number = 0;
260  unitPrice: number = 0;
261
262  constructor(quantity: number, unitPrice: number) {
263    this.quantity = quantity;
264    this.unitPrice = unitPrice;
265  }
266}
267
268@Entry
269@ComponentV2
270struct Index {
271  @Local shoppingBasket: Article[] = [new Article(1, 20), new Article(5, 2)];
272
273  @Computed
274  get total(): number {
275    return this.shoppingBasket.reduce((acc: number, item: Article) => acc + (item.quantity * item.unitPrice), 0);
276  }
277
278  @Computed
279  get qualifiesForDiscount(): boolean {
280    return this.total >= 100;
281  }
282
283  build() {
284    Column() {
285      Text(`Shopping List: `).fontSize(30)
286      ForEach(this.shoppingBasket, (item: Article) => {
287        Row() {
288          Text(`unitPrice: ${item.unitPrice}`)
289          Button('-').onClick(() => {
290            if (item.quantity > 0) {
291              item.quantity--;
292            }
293          })
294          Text(`quantity: ${item.quantity}`)
295          Button('+').onClick(() => {
296            item.quantity++;
297          })
298        }
299
300        Divider()
301      })
302      Child({ total: this.total, qualifiesForDiscount: this.qualifiesForDiscount })
303    }.alignItems(HorizontalAlign.Start)
304  }
305}
306
307@ComponentV2
308struct Child {
309  @Param total: number = 0;
310  @Param qualifiesForDiscount: boolean = false;
311
312  build() {
313    Row() {
314      Text(`Total: ${this.total} `).fontSize(30)
315      Text(`Discount: ${this.qualifiesForDiscount} `).fontSize(30)
316    }
317  }
318}
319```
320