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