1# \@Computed Decorator: Declaring Computed Properties 2<!--Kit: ArkUI--> 3<!--Subsystem: ArkUI--> 4<!--Owner: @liwenzhen3--> 5<!--Designer: @s10021109--> 6<!--Tester: @TerryTsao--> 7<!--Adviser: @zhang_yixin13--> 8 9When the same computation logic is repeatedly bound in the UI, the \@Computed decorator helps prevent redundant calculations. A computed property is evaluated only once when its dependent state variables change, addressing performance issues caused by repeated calculations in the UI. Example: 10 11```ts 12@Computed 13get sum() { 14 return this.count1 + this.count2 + this.count3; 15} 16Text(`${this.count1 + this.count2 + this.count3}`) // Calculate the sum of three counters. 17Text(`${this.count1 + this.count2 + this.count3}`) // Repeat the same calculation. 18Text(`${this.sum}`) // Read the cached value from the @Computed sum, avoiding redundant calculations. 19Text(`${this.sum}`) // Read the cached value from the @Computed sum, avoiding redundant calculations. 20``` 21 22Before 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). 23 24>**NOTE** 25> 26> The \@Computed decorator is supported since API version 12. 27> 28> This decorator can be used in atomic services since API version 12. 29 30## Overview 31 32\@Computed is a method decorator that decorates getter methods. It detects changes in the computed properties and ensures the calculation is performed only once when the properties change. Avoid modifying variables inside @Computed, as incorrect usage may lead to untracked data or application freezes. For details, see [Constraints](#constraints). 33 34For simple calculations, using @Computed is not recommended due to its inherent overhead. For complex computations, \@Computed provides significant performance benefits. 35 36## Decorator Description 37\@Computed syntax: 38 39```ts 40@Computed 41get varName(): T { 42 return value; 43} 44``` 45 46| \@Computed Decorator| Description | 47| ------------------ | ----------------------------------------------------- | 48| Supported type | Getter accessor.| 49| Initialization from the parent component | Forbidden.| 50| Child component initialization | \@Param. | 51| Execution time | In \@ComponentV2, \@Computed is initialized when the custom component is created, triggering computation.<br>In @ObservedV2 decorated classes, \@Computed is initialized asynchronously after the class instance is created, triggering computation.<br>Recomputation occurs when state variables used in the \@Computed calculation are modified.| 52| Value assignment allowed | No. @Computed decorated properties are read-only. For details, see [Constraints](#constraints).| 53 54## Constraints 55 56- \@Computed is a method decorator and can only decorate getter methods. 57 58 ```ts 59 @Computed 60 get fullName() { // Correct usage. 61 return this.firstName + ' ' + this.lastName; 62 } 63 @Computed val: number = 0; // Incorrect usage. An error is reported during compilation. 64 @Computed 65 func() { // Incorrect usage. An error is reported during compilation. 66 } 67 ``` 68- Methods decorated with \@Computed only recompute during initialization or when the state variables used in their calculations change. Avoid performing logic operations other than data retrieval in \@Computed decorated getter methods, as shown in the example below. 69 70```ts 71@Entry 72@ComponentV2 73struct Page { 74 @Local firstName: string = 'Hua'; 75 @Local lastName: string = 'Li'; 76 @Local showFullNameRequestCount: number = 0; 77 private fullNameRequestCount: number = 0; 78 79 @Computed 80 get fullName() { 81 console.info(`fullName`); 82 // Avoid assignment logic in @Computed calculations, as @Computed is essentially a getter accessor for optimizing repeated computations. 83 // In this example, fullNameRequestCount only represents the number of @Computed recalculations, not the number of times fullName is accessed. 84 this.fullNameRequestCount++; 85 return this.firstName + ' ' + this.lastName; 86 } 87 88 build() { 89 Column() { 90 Text(`${this.fullName}`) // Obtain fullName once. 91 Text(`${this.fullName}`) // Obtain fullName again. fullName is obtained twice. However, no recomputation occurs, as the cached value is read. 92 93 // Clicking the button obtains the value of fullNameRequestCount. 94 Text(`count ${this.showFullNameRequestCount}`) 95 Button('get fullName').onClick(() => { 96 this.showFullNameRequestCount = this.fullNameRequestCount; 97 }) 98 } 99 } 100} 101``` 102 103- In \@Computed decorated getter methods, do not modify properties involved in the calculation to prevent infinite recomputation leading to application freezes. 104 In the example below, computing **fullName1** modifies **this.lastName**, which triggers recomputation of **fullName2**. During computation of **fullName2**, **this.firstName** is modified, causing **fullName1** to recompute again. This creates an infinite loop, eventually leading to application freezes. 105 106```ts 107@Entry 108@ComponentV2 109struct Page { 110 @Local firstName: string = 'Hua'; 111 @Local lastName: string = 'Li'; 112 113 @Computed 114 get fullName1() { 115 console.info(`fullName1`); 116 this.lastName += 'a'; // Incorrect usage. The properties involved in computation cannot be changed. 117 return this.firstName + ' ' + this.lastName; 118 } 119 120 @Computed 121 get fullName2() { 122 console.info(`fullName2`); 123 this.firstName += 'a'; // Incorrect usage. The properties involved in computation cannot be changed. 124 return this.firstName + ' ' + this.lastName; 125 } 126 127 build() { 128 Column() { 129 Text(`${this.fullName1}`) 130 Text(`${this.fullName2}`) 131 } 132 } 133} 134``` 135 136- \@Computed cannot be used together with **!!** for two-way binding. Properties decorated with \@Computed are getter accessors. They are not synchronized by child components and cannot be assigned. Custom setter implementations for computed properties will not take effect and will result in a compile-time error. 137 138 ```ts 139 @ComponentV2 140 struct Child { 141 @Param double: number = 100; 142 @Event $double: (val: number) => void; 143 144 build() { 145 Button('ChildChange') 146 .onClick(() => { 147 this.$double(200); 148 }) 149 } 150 } 151 152 @Entry 153 @ComponentV2 154 struct Index { 155 @Local count: number = 100; 156 157 @Computed 158 get double() { 159 return this.count * 2; 160 } 161 162 // Custom setters for @Computed decorated properties have no effect and cause compile-time errors. 163 set double(newValue : number) { 164 this.count = newValue / 2; 165 } 166 167 build() { 168 Scroll() { 169 Column({ space: 3 }) { 170 Text(`${this.count}`) 171 // Incorrect usage. @Computed decorated properties are read-only and cannot be used together with two-way binding. 172 Child({ double: this.double!! }) 173 } 174 } 175 } 176 } 177 ``` 178 179- \@Computed is a feature of state management V2 and can only be used in @ComponentV2 and @ObservedV2. 180- When using multiple \@Computed decorated properties together, avoid circular dependencies to prevent infinite loops during computation. 181 182 ```ts 183 @Local a : number = 1; 184 @Computed 185 get b() { 186 return this.a + ' ' + this.c; // Incorrect usage. There is a circular dependency: b -> c -> b. 187 } 188 @Computed 189 get c() { 190 return this.a + ' ' + this.b; // Incorrect usage. There is a circular dependency: c -> b -> c. 191 } 192 ``` 193 194## Use Scenarios 195### The \@Computed Decorated Getter is Evaluated Only Once Upon Property Change 1961. Using a computed property in a custom component 197 198- Clicking the first button changes the value of **lastName**, triggering a recomputation of the \@Computed decorated property **fullName**. 199- **this.fullName** is bound to two **Text** components. The **fullName** log shows that the computation occurs only once. 200- For the first two **Text** components, the **this.lastName +' '+ this.firstName** logic is evaluated twice. 201- If multiple UI elements require the same computed logic **this.lastName +' '+ this.firstName**, you can use a computed property to reduce redundant calculations. 202- Clicking the second button increments the value of **age**, but the UI remains unchanged. This is because **age** is not a state variable, and only changes to observed variables can trigger the recomputation of the \@Computed decorated property **fullName**. 203 204```ts 205@Entry 206@ComponentV2 207struct Index { 208 @Local firstName: string = 'Li'; 209 @Local lastName: string = 'Hua'; 210 age: number = 20; // Computed cannot be triggered. 211 212 @Computed 213 get fullName() { 214 console.info('---------Computed----------'); 215 return this.firstName + ' ' + this.lastName + this.age; 216 } 217 218 build() { 219 Column() { 220 Text(this.lastName + ' ' + this.firstName) 221 Text(this.lastName + ' ' + this.firstName) 222 Divider() 223 Text(this.fullName) 224 Text(this.fullName) 225 Button('changed lastName').onClick(() => { 226 this.lastName += 'a'; 227 }) 228 229 Button('changed age').onClick(() => { 230 this.age++; // Computed cannot be triggered. 231 }) 232 } 233 } 234} 235``` 236 237Computed properties inherently introduce performance overhead. In practical development, note the following: 238- For simple logic, avoid computed properties and compute directly. 239- If the logic is used only once in the view, skip the computed property and evaluate inline. 240 2412. Using a computed property in an \@ObservedV2 decorated class 242- Clicking the button changes the value of **lastName**, triggering the \@Computed decorated property **fullName** to recompute once. 243 244```ts 245@ObservedV2 246class Name { 247 @Trace firstName: string = 'Hua'; 248 @Trace lastName: string = 'Li'; 249 250 @Computed 251 get fullName() { 252 console.info('---------Computed----------'); 253 return this.firstName + ' ' + this.lastName; 254 } 255} 256 257const name: Name = new Name(); 258 259@Entry 260@ComponentV2 261struct Index { 262 name1: Name = name; 263 264 build() { 265 Column() { 266 Text(this.name1.fullName) 267 Text(this.name1.fullName) 268 Button('changed lastName').onClick(() => { 269 this.name1.lastName += 'a'; 270 }) 271 } 272 } 273} 274``` 275 276### \@Monitor Can Listen for the Changes of the \@Computed Decorated Properties 277The following example shows how to convert **celsius** to **fahrenheit** and **kelvin** : 278- Clicking **-** decrements **celsius**, updates **fahrenheit**, then updates **kelvin**, which triggers **onKelvinMonitor**. 279- Clicking **+** increments **celsius**, updates **fahrenheit**, then updates **kelvin**, which triggers **onKelvinMonitor**. 280 281```ts 282@Entry 283@ComponentV2 284struct MyView { 285 @Local celsius: number = 20; 286 287 @Computed 288 get fahrenheit(): number { 289 return this.celsius * 9 / 5 + 32; // C -> F 290 } 291 292 @Computed 293 get kelvin(): number { 294 return (this.fahrenheit - 32) * 5 / 9 + 273.15; // F -> K 295 } 296 297 @Monitor('kelvin') 298 onKelvinMonitor(mon: IMonitor) { 299 console.log('kelvin changed from ' + mon.value()?.before + ' to ' + mon.value()?.now); 300 } 301 302 build() { 303 Column({ space: 20 }) { 304 Row({ space: 20 }) { 305 Button('-') 306 .onClick(() => { 307 this.celsius--; 308 }) 309 310 Text(`Celsius ${this.celsius.toFixed(1)}`).fontSize(50) 311 312 Button('+') 313 .onClick(() => { 314 this.celsius++; 315 }) 316 } 317 318 Text(`Fahrenheit ${this.fahrenheit.toFixed(2)}`).fontSize(50) 319 Text(`Kelvin ${this.kelvin.toFixed(2)}`).fontSize(50) 320 } 321 .width('100%') 322 } 323} 324``` 325### \@Computed Decorated Properties Can Initialize \@Param 326The following example shows how to use an \@Computed decorated property to initialize \@Param. 327- Clicking **Button('-')** and **Button('+')** changes the value of **quantity**, which is decorated with \@Trace and can be observed when it is changed. 328- The change of **quantity** triggers the recomputation of **total** and **qualifiesForDiscount**. 329- The change of **total** and **qualifiesForDiscount** triggers the update of the **Text** component corresponding to the **Child** component. 330 331```ts 332@ObservedV2 333class Article { 334 @Trace quantity: number = 0; 335 unitPrice: number = 0; 336 337 constructor(quantity: number, unitPrice: number) { 338 this.quantity = quantity; 339 this.unitPrice = unitPrice; 340 } 341} 342 343@Entry 344@ComponentV2 345struct Index { 346 @Local shoppingBasket: Article[] = [new Article(1, 20), new Article(5, 2)]; 347 348 @Computed 349 get total(): number { 350 return this.shoppingBasket.reduce((acc: number, item: Article) => acc + (item.quantity * item.unitPrice), 0); 351 } 352 353 @Computed 354 get qualifiesForDiscount(): boolean { 355 return this.total >= 100; 356 } 357 358 build() { 359 Column() { 360 Text(`Shopping List: `).fontSize(30) 361 ForEach(this.shoppingBasket, (item: Article) => { 362 Row() { 363 Text(`unitPrice: ${item.unitPrice}`) 364 Button('-').onClick(() => { 365 if (item.quantity > 0) { 366 item.quantity--; 367 } 368 }) 369 Text(`quantity: ${item.quantity}`) 370 Button('+').onClick(() => { 371 item.quantity++; 372 }) 373 } 374 375 Divider() 376 }) 377 Child({ total: this.total, qualifiesForDiscount: this.qualifiesForDiscount }) 378 }.alignItems(HorizontalAlign.Start) 379 } 380} 381 382@ComponentV2 383struct Child { 384 @Param total: number = 0; 385 @Param qualifiesForDiscount: boolean = false; 386 387 build() { 388 Row() { 389 Text(`Total: ${this.total} `).fontSize(30) 390 Text(`Discount: ${this.qualifiesForDiscount} `).fontSize(30) 391 } 392 } 393} 394``` 395