1# \@Watch Decorator: Getting Notified of State Variable Changes 2 3 4\@Watch is used to listen for state variables. If your application needs watch for value changes of a state variable, you can decorate the variable with \@Watch. 5 6 7In addition, \@Watch can only listen for changes that can be observed. 8 9Before reading this topic, you are advised to read [\@State](./arkts-state.md) to have a understanding of the basic observation capabilities of state management. 10 11> **NOTE** 12> 13> Since API version 9, this decorator is supported in ArkTS widgets. 14> 15> This decorator can be used in atomic services since API version 11. 16 17## Overview 18 19An application can request to be notified whenever the value of the \@Watch decorated variable changes. The \@Watch callback is called when the value change has occurred. \@Watch uses strict equality (===) to determine whether a value is updated in the ArkUI framework. If **false** is return, the \@Monitor decorated callback is triggered. 20 21 22## Decorator Description 23 24| \@Watch Decorator| Description | 25| -------------- | ---------------------------------------- | 26| Decorator parameters | Mandatory. Constant string, which is quoted. Reference to a (string) => void custom component member function.| 27| Custom component variables that can be decorated | All decorated state variables. Regular variables cannot be watched. | 28| Order of decorators | The order of decorators does not affect the actual functions. You can determine it as required. It is recommended that the [\@State](./arkts-state.md), [\@Prop](./arkts-prop.md), and [\@Link](./arkts-link.md) decorators be placed before the \@Watch decorator, to keep the overall style consistent.| 29| Called when| The variable changes and is assigned a value. For details, see [Time for \@Watch to be Called](#time-for-watch-to-be-called).| 30 31## Syntax 32 33| Type | Description | 34| ---------------------------------------- | ---------------------------------------- | 35| (changedPropertyName? : string) => void | This function is a member function of the custom component. **changedPropertyName** indicates the name of the watched attribute.<br>It is useful when you use the same function as a callback to several watched attributes.<br>It takes the attribute name as a string input parameter and returns nothing.| 36 37 38## Observed Changes and Behavior 39 401. \@Watch callback is triggered when a change of a state variable (including the change of a key in [AppStorage](./arkts-appstorage.md) and [LocalStorage](./arkts-localstorage.md) that are bound in a two-way manner) is observed. 41 422. The \@Watch callback is executed synchronously after the variable change in the custom component. 43 443. If the \@Watch callback mutates other watched variables, their variable @Watch callbacks in the same and other custom components as well as state updates are triggered. 45 464. A \@Watch function is not called upon custom component variable initialization, because initialization is not considered as variable mutation. A \@Watch function is called upon change of the custom component variable. 47 48 49## Restrictions 50 51- Pay attention to the risk of infinite loops. Loops can be caused by the \@Watch callback directly or indirectly mutating the same variable. To avoid loops, do not mutate the \@Watch decorated state variable inside the callback handler. 52 53- Pay attention to performance. The attribute value update function delays component re-render (see the preceding behavior description). The callback should only perform quick computations. 54 55- Calling **async await** from an \@Watch function is not recommended, because asynchronous behavior may cause performance issues of re-rendering. 56 57- The \@Watch parameter is mandatory and must be of the string type. Otherwise, an error will be reported during compilation. 58 59```ts 60// Incorrect format. An error is reported during compilation. 61@State @Watch() num: number = 10; 62@State @Watch(change) num: number = 10; 63 64// Correct format. 65@State @Watch('change') num: number = 10; 66change() { 67 console.log(`xxx`); 68} 69``` 70 71- The parameters in \@Watch must be declared method names. Otherwise, an error will be reported during compilation. 72 73```ts 74// Incorrect format. No function with the corresponding name is available, and an error is reported during compilation. 75@State @Watch('change') num: number = 10; 76onChange() { 77 console.log(`xxx`); 78} 79 80// Correct format. 81@State @Watch('change') num: number = 10; 82change() { 83 console.log(`xxx`); 84} 85``` 86 87- Common variables cannot be decorated by \@Watch. Otherwise, an error will be reported during compilation. 88 89```ts 90// Incorrect format. 91@Watch('change') num: number = 10; 92change() { 93 console.log(`xxx`); 94} 95 96// Correct format. 97@State @Watch('change') num: number = 10; 98change() { 99 console.log(`xxx`); 100} 101``` 102 103 104## Use Scenarios 105 106### \@Watch and Custom Component Update 107 108This example is used to clarify the processing steps of custom component updates and \@Watch. **count** is decorated by \@State in **CountModifier** and \@Prop in **TotalView**. 109 110 111```ts 112@Component 113struct TotalView { 114 @Prop @Watch('onCountUpdated') count: number = 0; 115 @State total: number = 0; 116 // @Watch callback 117 onCountUpdated(propName: string): void { 118 this.total += this.count; 119 } 120 121 build() { 122 Text(`Total: ${this.total}`) 123 } 124} 125 126@Entry 127@Component 128struct CountModifier { 129 @State count: number = 0; 130 131 build() { 132 Column() { 133 Button('add to basket') 134 .onClick(() => { 135 this.count++ 136 }) 137 TotalView({ count: this.count }) 138 } 139 } 140} 141``` 142 143The procedure is as follows: 144 1451. The click event **Button.onClick** of the **CountModifier** custom component increases the value of **count**. 146 1472. In response to the change of the @State decorated variable **count**, \@Prop in the child component **TotalView** is updated, and its **\@Watch('onCountUpdated')** callback is invoked, which updates the **total** variable in **TotalView**. 148 1493. The **Text** component in the child component **TotalView** is re-rendered. 150 151 152### Combination of \@Watch and \@Link 153 154This example illustrates how to watch an \@Link decorated variable in a child component. 155 156 157```ts 158class PurchaseItem { 159 static NextId: number = 0; 160 public id: number; 161 public price: number; 162 163 constructor(price: number) { 164 this.id = PurchaseItem.NextId++; 165 this.price = price; 166 } 167} 168 169@Component 170struct BasketViewer { 171 @Link @Watch('onBasketUpdated') shopBasket: PurchaseItem[]; 172 @State totalPurchase: number = 0; 173 174 updateTotal(): number { 175 let total = this.shopBasket.reduce((sum, i) => sum + i.price, 0); 176 // A discount is provided when the amount exceeds 100 euros. 177 if (total >= 100) { 178 total = 0.9 * total; 179 } 180 return total; 181 } 182 // @Watch callback 183 onBasketUpdated(propName: string): void { 184 this.totalPurchase = this.updateTotal(); 185 } 186 187 build() { 188 Column() { 189 ForEach(this.shopBasket, 190 (item: PurchaseItem) => { 191 Text(`Price: ${item.price.toFixed(2)} €`) 192 }, 193 (item: PurchaseItem) => item.id.toString() 194 ) 195 Text(`Total: ${this.totalPurchase.toFixed(2)} €`) 196 } 197 } 198} 199 200@Entry 201@Component 202struct BasketModifier { 203 @State shopBasket: PurchaseItem[] = []; 204 205 build() { 206 Column() { 207 Button('Add to basket') 208 .onClick(() => { 209 this.shopBasket.push(new PurchaseItem(Math.round(100 * Math.random()))) 210 }) 211 BasketViewer({ shopBasket: $shopBasket }) 212 } 213 } 214} 215``` 216 217The procedure is as follows: 218 2191. **Button.onClick** of the **BasketModifier** component adds an item to **BasketModifier shopBasket**. 220 2212. The value of the \@Link decorated variable **BasketViewer shopBasket** changes. 222 2233. The state management framework calls the \@Watch callback **BasketViewer onBasketUpdated** to update the value of **BasketViewer TotalPurchase**. 224 2254. Because \@Link decorated shopBasket changes (a new item is added), the **ForEach** component executes the item Builder to render and build the new item. Because the @State decorated **totalPurchase** variable changes, the **Text** component is also re-rendered. Re-rendering happens asynchronously. 226 227### Time for \@Watch to be Called 228 229To show the \@Watch callback is called when the state variable changes, this example uses the \@Link and \@ObjectLink decorators in the child component to observe different state objects. You can change the state variable in the parent component and observe the calling sequence of the \@Watch callback to learn the relationship between the time for calling, value assignment, and synchronization. 230 231```ts 232@Observed 233class Task { 234 isFinished: boolean = false; 235 236 constructor(isFinished : boolean) { 237 this.isFinished = isFinished; 238 } 239} 240 241@Entry 242@Component 243struct ParentComponent { 244 @State @Watch('onTaskAChanged') taskA: Task = new Task(false); 245 @State @Watch('onTaskBChanged') taskB: Task = new Task(false); 246 247 onTaskAChanged(changedPropertyName: string): void { 248 console.log(`Property of this parent component task is changed: ${changedPropertyName}`); 249 } 250 251 onTaskBChanged(changedPropertyName: string): void { 252 console.log(`Property of this parent component task is changed: ${changedPropertyName}`); 253 } 254 255 build() { 256 Column() { 257 Text(`Parent component task A state: ${this.taskA.isFinished ? 'Finished' : 'Unfinished'}`) 258 Text(`Parent component task B state: ${this.taskB.isFinished ? 'Finished' : 'Unfinished'}`) 259 ChildComponent({ taskA: this.taskA, taskB: this.taskB }) 260 Button('Switch Task State') 261 .onClick(() => { 262 this.taskB = new Task(!this.taskB.isFinished); 263 this.taskA = new Task(!this.taskA.isFinished); 264 }) 265 } 266 } 267} 268 269@Component 270struct ChildComponent { 271 @ObjectLink @Watch('onObjectLinkTaskChanged') taskB: Task; 272 @Link @Watch('onLinkTaskChanged') taskA: Task; 273 274 onObjectLinkTaskChanged(changedPropertyName: string): void { 275 console.log(`Property of @ObjectLink associated task of the child component is changed: ${changedPropertyName}`); 276 } 277 278 onLinkTaskChanged(changedPropertyName: string): void { 279 console.log(`Property of @Link associated task of the child component is changed: ${changedPropertyName}`); 280 } 281 282 build() { 283 Column() { 284 Text(`Child component task A state: ${this.taskA.isFinished ? 'Finished' : 'Unfinished'}`) 285 Text(`Child component task B state: ${this.taskB.isFinished ? 'Finished' : 'Unfinished'}`) 286 } 287 } 288} 289``` 290 291The procedure is as follows: 292 2931. When you click the button to switch the task state, the parent component updates **taskB** associated with \@ObjectLink and **taskA** associated with \@Link. 294 2952. The following information is displayed in sequence in the log: 296 ``` 297 Property of this parent component task is changed: taskB 298 Property of this parent component task is changed: taskA 299 Property of @Link associated task of the child component is changed: taskA 300 Property of @ObjectLink associated task of the child component is changed: taskB 301 ``` 302 3033. The log shows that the calling sequence of the parent component is the same as the change sequence, but the calling sequence of \@Link and \@ObjectLink in the child component is different from the variable update sequence in the parent component. This is because the variables of the parent component are updated in real time, but \@Link and \@ObjectLink in the child component obtain the updated data at different time. The \@Link associated state is updated synchronously, therefore, state change immediately calls the \@Watch callback. The update of \@ObjectLink associated state depends on the synchronization of the parent component. The \@Watch callback is called only when the parent component updates and passes the updated variables to the child component. Therefore, the calling sequence is slightly later than that of \@Link. 304 3054. This behavior meets the expectation. The \@Watch callback is invoked based on the actual state variable change time. Similarly, \@Prop may behave similarly to \@ObjectLink, and the time for its callback to be invoked is slightly later. 306 307### Using changedPropertyName for Different Logic Processing 308 309The following example shows how to use **changedPropertyName** in the \@Watch function for different logic processing. 310 311 312```ts 313@Entry 314@Component 315struct UsePropertyName { 316 @State @Watch('countUpdated') apple: number = 0; 317 @State @Watch('countUpdated') cabbage: number = 0; 318 @State fruit: number = 0; 319 // @Watch callback 320 countUpdated(propName: string): void { 321 if (propName == 'apple') { 322 this.fruit = this.apple; 323 } 324 } 325 326 build() { 327 Column() { 328 Text(`Number of apples: ${this.apple.toString()}`).fontSize(30) 329 Text(`Number of cabbages: ${this.cabbage.toString()}`).fontSize(30) 330 Text(`Total number of fruits: ${this.fruit.toString()}`).fontSize(30) 331 Button('Add apples') 332 .onClick(() => { 333 this.apple++; 334 }) 335 Button('Add cabbages') 336 .onClick(() => { 337 this.cabbage++; 338 }) 339 } 340 } 341} 342``` 343 344The procedure is as follows: 345 3461. Click **Button('Add apples')**, the value of **apple** changes. 347 3482. The state management framework calls the \@Watch function **countUpdated** and the value of state variable **apple** is changed; the **if** logic condition is met, the value of **fruit** changes. 349 3503. **Text**s bound to **apple** and **fruit** are rendered again. 351 3524. Click **Button('Add cabbages')**, the value of **cabbage** changes. 353 3545. The state management framework calls the \@Watch function **countUpdated** and the value of state variable **cabbage** is changed; the **if** logic condition is not met, the value of **fruit** does not change. 355 3566. **Text** bound to **cabbage** is rendered again. 357