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>State management V2 is still under development, and some features may be incomplete or not always work as expected.
10
11## Overview
12
13\@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.
14For complex computing, \@Computed provides better performance.
15
16
17## Decorator Description
18\@Computed syntax:
19
20```ts
21@Computed get 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
33## Constraints
34
35- When \@Computed is used to decorate the **getter** method, the computed property cannot be changed in this method.
36
37```ts
38@Computed
39get fullName() {
40  this.lastName += 'a'; // error
41  return this.firstName + ' ' + this.lastName;
42}
43```
44- \@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.
45
46
47```ts
48@Computed
49get fullName() {
50  return this.firstName + ' ' + this.lastName;
51}
52
53Child({ fullName: this.fullName!! }) // error
54```
55- The capability provided by \@Computed for the status management V2 can be used only in \@ComponentV2 and \@ObservedV2.
56- Be cautious about loop solving when multiple \@Computed are used together.
57
58```ts
59@Local a : number = 1;
60@Computed
61get b() {
62  return this.a + ' ' + this.c;  // error: b -> c -> b
63}
64@Computed
65get c() {
66  return this.a + ' ' + this.b; // error: c -> b -> c
67}
68```
69
70## Use Scenarios
71### When the computed property changes, the **getter** accessor decorated by \@Computed is solved only once.
721. Using Computed Property in a Custom Component
73
74- Click the first button to change the value of **lastName**, triggering **\@Computed fullName** recomputation.
75- The **this.fullName** is bound to two **Text** components. The **fullName** log shows that the computation occurs only once.
76- For the first two **Text** components, the **this.lastName +' '+ this.firstName** logic is solved twice.
77- 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.
78- 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.
79
80```ts
81@Entry
82@ComponentV2
83struct Index {
84  @Local firstName: string = 'Li';
85  @Local lastName: string = 'Hua';
86  age: number = 20; // cannot trigger Computed
87
88  @Computed
89  get fullName() {
90    console.info("---------Computed----------");
91    return this.firstName + ' ' + this.lastName + this.age;
92  }
93
94  build() {
95    Column() {
96      Text(this.lastName + ' ' + this.firstName)
97      Text(this.lastName + ' ' + this.firstName)
98      Divider()
99      Text(this.fullName)
100      Text(this.fullName)
101      Button('changed lastName').onClick(() => {
102        this.lastName += 'a';
103      })
104
105      Button('changed age').onClick(() => {
106        this.age++;  // cannot trigger Computed
107      })
108    }
109  }
110}
111```
112
113Note that the computed property itself has performance overhead. In actual application development:
114- For the preceding simple computation, computed property is not needed.
115- If the computed property is used only once in the view, you can solve the problem directly.
116
1172. Using Computed Property in Classes Decorated by \@ObservedV2
118- Click the button to change the value of **lastName** and the **\@Computed fullName** will be recomputed only once.
119
120```ts
121@ObservedV2
122class Name {
123  @Trace firstName: string = 'Li';
124  @Trace lastName: string = 'Hua';
125
126  @Computed
127  get fullName() {
128    console.info('---------Computed----------');
129    return this.firstName + ' ' + this.lastName;
130  }
131}
132
133const name: Name = new Name();
134
135@Entry
136@ComponentV2
137struct Index {
138  name1: Name = name;
139
140  build() {
141    Column() {
142      Text(this.name1.fullName)
143      Text(this.name1.fullName)
144      Button('changed lastName').onClick(() => {
145        this.name1.lastName += 'a';
146      })
147    }
148  }
149}
150```
151
152### \@Monitor can Listen for the Changes of the \@Computed Decorated Properties
153The following example shows how to solve **fahrenheit** and **kelvin** by using computed property.
154- Click "-" to run the logic **celsius--** -> **fahrenheit** -> **kelvin**. The change of **kelvin** triggers the **onKelvinMonitor**.
155- Click "+" to run the logic **celsius++** -> **fahrenheit** -> **kelvin**. The change of **kelvin** triggers the **onKelvinMonitor**.
156
157```ts
158@Entry
159@ComponentV2
160struct MyView {
161  @Local celsius: number = 20;
162
163  @Computed
164  get fahrenheit(): number {
165    return this.celsius * 9 / 5 + 32; // C -> F
166  }
167
168  @Computed
169  get kelvin(): number {
170    return (this.fahrenheit - 32) * 5 / 9 + 273.15; // F -> K
171  }
172
173  @Monitor("kelvin")
174  onKelvinMonitor(mon: IMonitor) {
175    console.log("kelvin changed from " + mon.value()?.before + " to " + mon.value()?.now);
176  }
177
178  build() {
179    Column({ space: 20 }) {
180      Row({ space: 20 }) {
181        Button('-')
182          .onClick(() => {
183            this.celsius--;
184          })
185
186        Text(`Celsius ${this.celsius.toFixed(1)}`).fontSize(50)
187
188        Button('+')
189          .onClick(() => {
190            this.celsius++;
191          })
192      }
193
194      Text(`Fahrenheit ${this.fahrenheit.toFixed(2)}`).fontSize(50)
195      Text(`Kelvin ${this.kelvin.toFixed(2)}`).fontSize(50)
196    }
197    .width('100%')
198  }
199}
200```
201### \@Computed Decorated Properties Initialize \@Param
202The following example shows how \@Computed Initialize \@Param.
203- Click **Button('-')** and **Button('+')** to change the offering quantity. The **quantity** is decorated by \@Trace and can be observed when it is changed.
204- 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.
205- The change of **total** and **qualifiesForDiscount** triggers the update of the **Text** component corresponding to the **Child** component.
206
207```ts
208@ObservedV2
209class Article {
210  @Trace quantity: number = 0;
211  unitPrice: number = 0;
212
213  constructor(quantity: number, unitPrice: number) {
214    this.quantity = quantity;
215    this.unitPrice = unitPrice;
216  }
217}
218
219@Entry
220@ComponentV2
221struct Index {
222  @Local shoppingBasket: Article[] = [new Article(1, 20), new Article(5, 2)];
223
224  @Computed
225  get total(): number {
226    return this.shoppingBasket.reduce((acc: number, item: Article) => acc + (item.quantity * item.unitPrice), 0);
227  }
228
229  @Computed
230  get qualifiesForDiscount(): boolean {
231    return this.total >= 100;
232  }
233
234  build() {
235    Column() {
236      Text(`Shopping List: `).fontSize(30)
237      ForEach(this.shoppingBasket, (item: Article) => {
238        Row() {
239          Text(`unitPrice: ${item.unitPrice}`)
240          Button('-').onClick(() => {
241            if (item.quantity > 0) {
242              item.quantity--;
243            }
244          })
245          Text(`quantity: ${item.quantity}`)
246          Button('+').onClick(() => {
247            item.quantity++;
248          })
249        }
250
251        Divider()
252      })
253      Child({ total: this.total, qualifiesForDiscount: this.qualifiesForDiscount })
254    }.alignItems(HorizontalAlign.Start)
255  }
256}
257
258@ComponentV2
259struct Child {
260  @Param total: number = 0;
261  @Param qualifiesForDiscount: boolean = false;
262
263  build() {
264    Row() {
265      Text(`Total: ${this.total} `).fontSize(30)
266      Text(`Discount: ${this.qualifiesForDiscount} `).fontSize(30)
267    }
268
269  }
270}
271```
272