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