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