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