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