1# \@Local Decorator: Representing the Internal State of Components 2 3You can use \@Local, a variable decorator in state management V2, to observe the variable changes in custom components decorated by \@ComponentV2. 4 5>**NOTE** 6> 7>The \@Local 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\@Local indicates the internal state of a component, which enables the variables in the custom component to observe changes: 14 15- Variables decorated by \@Local cannot be initialized externally. They must be initialized inside the component. 16 17- When a variable decorated by \@Local changes, the component that uses the variable is re-rendered. 18 19- \@Local can observe basic types such as number, boolean, string, object, and class and built-in types such as Array, Set, Map, and Date. 20 21- \@Local can observe only the variable it decorates. If the decorated variable is of the simple type, it can observe value changes to the variable; if the decorated variable is of the object type, it can observe value changes to the entire object; if the decorated variable is of the array type, it can observe changes of the entire array and its items; if the decorated variable is of the built-in types, such as Array, Set, Map, and Date, it can observe changes caused by calling the APIs. For details, see [Observed Changes](#observed-changes). 22 23- \@Local supports null, undefined, and union types. 24 25## Limitations of the \@State decorator in State Management V1 26 27State management V1 uses the [\@State](arkts-state.md) decorator to define state variables in a class. However, because the \@State decorator allows variables to be initialized externally, it cannot accurately express the semantics that the internal state of the component cannot be modified externally. 28 29```ts 30class ComponentInfo { 31 name: string; 32 count: number; 33 message: string; 34 constructor(name: string, count: number, message: string) { 35 this.name = name; 36 this.count = count; 37 this.message = message; 38 } 39} 40@Component 41struct Child { 42 @State componentInfo: ComponentInfo = new ComponentInfo("Child", 1, "Hello World"); 43 44 build() { 45 Column() { 46 Text(`componentInfo.message is ${this.componentInfo.message}`) 47 } 48 } 49} 50@Entry 51@Component 52struct Index { 53 build() { 54 Column() { 55 Child({componentInfo: new ComponentInfo("Unknown", 0, "Error")}) 56 } 57 } 58} 59``` 60 61In the preceding code, the initialization of the **Child** component can pass in a new value to overwrite the local value of **componentInfo** that the component wants to use as an internal state variable. However, the **Child** component cannot detect that **componentInfo** has been initialized externally, which is inconvenient for managing the internal state of the component. This is where \@Local, a decorator that represents the internal state of components, comes into the picture. 62 63## Decorator Description 64 65| \@Local Variable Decorator| Description| 66| ------------------- | ------------------------------------------------------------ | 67| Decorator parameters| None.| 68| Allowed variable types| Basic types, such as object, class, string, number, boolean, and enum, and built-in types such as Array, Date, Map, and Set. null, undefined, and union types.| 69| Initial value for the decorated variable| Local initialization is required. External initialization is not allowed.| 70 71## Variable Passing 72 73| Passing Rules | Description | 74| -------------- | --------------------------------------------------------- | 75| Initialization from the parent component| Variables decorated by \@Local can only be initialized locally.| 76| Child component initialization | Variables decorated by \@Local can initialize variables decorated by \@Param in the child components. | 77 78## Observed Changes 79 80Variables decorated by \@Local are observable. When a decorated variable changes, the UI component bound to the variable will be re-rendered. 81 82- When the decorated variable is of boolean, string, or number type, value changes to the variable can be observed. 83 84 ```ts 85 @Entry 86 @ComponentV2 87 struct Index { 88 @Local count: number = 0; 89 @Local message: string = "Hello"; 90 @Local flag: boolean = false; 91 build() { 92 Column() { 93 Text(`${this.count}`) 94 Text(`${this.message}`) 95 Text(`${this.flag}`) 96 Button("change Local") 97 .onClick(()=>{ 98 // When @Local decorates a simple type, it can observe value changes to the variable. 99 this.count++; 100 this.message += " World"; 101 this.flag = !this.flag; 102 }) 103 } 104 } 105 } 106 ``` 107 108- When the decorated variable is of a class object type, only the overall value changes to the class object can be observed. To observe value changes to the member properties in the class object, you'll need the \@ObservedV2 and \@Trace decorators. Note that \@Local cannot be used together with the instance objects of the \@Observed decorated class. 109 110 ```ts 111 class RawObject { 112 name: string; 113 constructor(name: string) { 114 this.name = name; 115 } 116 } 117 @ObservedV2 118 class ObservedObject { 119 @Trace name: string; 120 constructor(name: string) { 121 this.name = name; 122 } 123 } 124 @Entry 125 @ComponentV2 126 struct Index { 127 @Local rawObject: RawObject = new RawObject("rawObject"); 128 @Local observedObject: ObservedObject = new ObservedObject("observedObject"); 129 build() { 130 Column() { 131 Text(`${this.rawObject.name}`) 132 Text(`${this.observedObject.name}`) 133 Button("change object") 134 .onClick(() => { 135 // Value changes to the class object can be observed. 136 this.rawObject = new RawObject("new rawObject"); 137 this.observedObject = new ObservedObject("new observedObject"); 138 }) 139 Button("change name") 140 .onClick(() => { 141 // @Local does not have the capability of observing class object property. Therefore, value changes of rawObject.name cannot be observed. 142 this.rawObject.name = "new rawObject name"; 143 // The name property of ObservedObject is decorated by @Trace. Therefore, value changes of observedObject.name can be observed. 144 this.observedObject.name = "new observedObject name"; 145 }) 146 } 147 } 148 } 149 ``` 150 151- When the decorated variable is of a simple array type, changes of the entire array or its items can be observed. 152 153 ```ts 154 @Entry 155 @ComponentV2 156 struct Index { 157 @Local numArr: number[] = [1,2,3,4,5]; 158 @Local dimensionTwo: number[][] = [[1,2,3],[4,5,6]]; 159 160 build() { 161 Column() { 162 Text(`${this.numArr[0]}`) 163 Text(`${this.numArr[1]}`) 164 Text(`${this.numArr[2]}`) 165 Text(`${this.dimensionTwo[0][0]}`) 166 Text(`${this.dimensionTwo[1][1]}`) 167 Button("change array item") 168 .onClick(() => { 169 this.numArr[0]++; 170 this.numArr[1] += 2; 171 this.dimensionTwo[0][0] = 0; 172 this.dimensionTwo[1][1] = 0; 173 }) 174 Button("change whole array") 175 .onClick(() => { 176 this.numArr = [5,4,3,2,1]; 177 this.dimensionTwo = [[7,8,9],[0,1,2]]; 178 }) 179 } 180 } 181 } 182 ``` 183 184- When the decorated variable is of a nested type or an object array, changes of lower-level object properties cannot be observed. Observation of these lower-level object properties requires use of \@ObservedV2 and \@Trace decorators. 185 186 ```ts 187 @ObservedV2 188 class Region { 189 @Trace x: number; 190 @Trace y: number; 191 constructor(x: number, y: number) { 192 this.x = x; 193 this.y = y; 194 } 195 } 196 @ObservedV2 197 class Info { 198 @Trace region: Region; 199 @Trace name: string; 200 constructor(name: string, x: number, y: number) { 201 this.name = name; 202 this.region = new Region(x, y); 203 } 204 } 205 @Entry 206 @ComponentV2 207 struct Index { 208 @Local infoArr: Info[] = [new Info("Ocean", 28, 120), new Info("Mountain", 26, 20)]; 209 @Local originInfo: Info = new Info("Origin", 0, 0); 210 build() { 211 Column() { 212 ForEach(this.infoArr, (info: Info) => { 213 Row() { 214 Text(`name: ${info.name}`) 215 Text(`region: ${info.region.x}-${info.region.y}`) 216 } 217 }) 218 Row() { 219 Text(`Origin name: ${this.originInfo.name}`) 220 Text(`Origin region: ${this.originInfo.region.x}-${this.originInfo.region.y}`) 221 } 222 Button("change infoArr item") 223 .onClick(() => { 224 // Because the name property is decorated by @Trace, it can be observed. 225 this.infoArr[0].name = "Win"; 226 }) 227 Button("change originInfo") 228 .onClick(() => { 229 // Because the originInfo variable is decorated by @Local, it can be observed. 230 this.originInfo = new Info("Origin", 100, 100); 231 }) 232 Button("change originInfo region") 233 .onClick(() => { 234 // Because the x and y properties are decorated by @Trace, it can be observed. 235 this.originInfo.region.x = 25; 236 this.originInfo.region.y = 25; 237 }) 238 } 239 } 240 } 241 ``` 242 243- When the decorated variable is of a built-in type, you can observe the overall value changes of the variable and the changes caused by calling the APIs listed below. 244 245 | Type | Observable APIs | 246 | ----- | ------------------------------------------------------------ | 247 | Array | push, pop, shift, unshift, splice, copyWithin, fill, reverse, sort| 248 | Date | setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds | 249 | Map | set, clear, delete | 250 | Set | add, clear, delete | 251 252## Constraints 253 254The \@Local decorator has the following constraints: 255 256- The \@Local decorator can be used only in custom components decorated by \@ComponentV2. 257 258 ```ts 259 @ComponentV2 260 struct CompA { 261 @Local message: string = "Hello World"; // Correct usage. 262 build() { 263 } 264 } 265 @Component 266 struct CompB { 267 @Local message: string = "Hello World"; // Incorrect usage. 268 build() { 269 } 270 } 271 ``` 272 273- The variable decorated by \@Local indicates the internal state of the component and cannot be initialized externally. 274 275 ```ts 276 @ComponentV2 277 struct CompA { 278 @Local message: string = "Hello World"; 279 build() { 280 } 281 } 282 @ComponentV2 283 struct CompB { 284 build() { 285 CompA({ message: "Hello" }) // Incorrect usage. 286 } 287 } 288 ``` 289 290## Comparison Between \@Local and \@State 291 292The following table compares the usage and functions of \@Local and \@State. 293 294| | \@State | \@Local | 295| ------------------ | ---------------------------- | --------------------------------- | 296| Parameter | None. | None. | 297| Initialization from the parent component | Optional. | External initialization is not allowed. | 298| Observation capability| Variables and top-level member properties can be observed, but lower-level member properties cannot.| The variable itself can be observed. Lower-level observation requires use of \@Trace decorator.| 299| Data Transfer| It can be used as a data source to synchronize with the state variables in a child component.| It can be used as a data source to synchronize with the state variables in a child component.| 300 301## Use Scenarios 302 303### Observing Overall Changes of Objects 304 305When a class object and its properties are decorated by \@ObservedV2 and \@Trace, properties in the class object can be observed. However, value changes of the class object itself cannot be observed and do not initiate UI re-renders. In this case, you can use \@Local to decorate the object to observe the changes. 306 307```ts 308@ObservedV2 309class Info { 310 @Trace name: string; 311 @Trace age: number; 312 constructor(name: string, age: number) { 313 this.name = name; 314 this.age = age; 315 } 316} 317@Entry 318@ComponentV2 319struct Index { 320 info1: Info = new Info("Tom", 25); 321 @Local info2: Info = new Info("Tom", 25); 322 build() { 323 Column() { 324 Text(`info1: ${this.info1.name}-${this.info1.age}`) // Text1 325 Text(`info2: ${this.info2.name}-${this.info2.age}`) // Text2 326 Button("change info1&info2") 327 .onClick(() => { 328 this.info1 = new Info("Lucy", 18); // Text1 is not updated. 329 this.info2 = new Info("Lucy", 18); // Text2 is updated. 330 }) 331 } 332 } 333} 334``` 335 336### Decorating Variables of the Date Type 337 338When the decorated object is of the**Date** type, the overall value changes of **Date** can be observed. In addition, you can call the following APIs to update **Date** properties: **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, and **setUTCMilliseconds**. 339 340```ts 341@Entry 342@ComponentV2 343struct DatePickerExample { 344 @Local selectedDate: Date = new Date('2021-08-08'); 345 346 build() { 347 Column() { 348 Button('set selectedDate to 2023-07-08') 349 .margin(10) 350 .onClick(() => { 351 this.selectedDate = new Date('2023-07-08'); 352 }) 353 Button('increase the year by 1') 354 .margin(10) 355 .onClick(() => { 356 this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1); 357 }) 358 Button('increase the month by 1') 359 .margin(10) 360 .onClick(() => { 361 this.selectedDate.setMonth(this.selectedDate.getMonth() + 1); 362 }) 363 Button('increase the day by 1') 364 .margin(10) 365 .onClick(() => { 366 this.selectedDate.setDate(this.selectedDate.getDate() + 1); 367 }) 368 DatePicker({ 369 start: new Date('1970-1-1'), 370 end: new Date('2100-1-1'), 371 selected: this.selectedDate 372 }) 373 }.width('100%') 374 } 375} 376``` 377 378### Decorating Variables of the Map Type 379 380When the decorated object is of the **Map** type, the overall value changes of **Map** can be observed. In addition, you can call the set, clear, and delete interfaces to update the data in **Map**. 381 382```ts 383@Entry 384@ComponentV2 385struct MapSample { 386 @Local message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]); 387 388 build() { 389 Row() { 390 Column() { 391 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 392 Text(`${item[0]}`).fontSize(30) 393 Text(`${item[1]}`).fontSize(30) 394 Divider() 395 }) 396 Button('init map').onClick(() => { 397 this.message = new Map([[0, "a"], [1, "b"], [3, "c"]]); 398 }) 399 Button('set new one').onClick(() => { 400 this.message.set(4, "d"); 401 }) 402 Button('clear').onClick(() => { 403 this.message.clear(); 404 }) 405 Button('replace the first one').onClick(() => { 406 this.message.set(0, "aa"); 407 }) 408 Button('delete the first one').onClick(() => { 409 this.message.delete(0); 410 }) 411 } 412 .width('100%') 413 } 414 .height('100%') 415 } 416} 417``` 418 419### Decorating Variables of the Set Type 420 421When the decorated object is **Set**, the overall value changes of **Set** can be observed. In addition, you can call the add, clear, and delete interfaces to update the data in **Set**. 422 423```ts 424@Entry 425@ComponentV2 426struct SetSample { 427 @Local message: Set<number> = new Set([0, 1, 2, 3, 4]); 428 429 build() { 430 Row() { 431 Column() { 432 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 433 Text(`${item[0]}`).fontSize(30) 434 Divider() 435 }) 436 Button('init set').onClick(() => { 437 this.message = new Set([0, 1, 2, 3, 4]); 438 }) 439 Button('set new one').onClick(() => { 440 this.message.add(5); 441 }) 442 Button('clear').onClick(() => { 443 this.message.clear(); 444 }) 445 Button('delete the first one').onClick(() => { 446 this.message.delete(0); 447 }) 448 } 449 .width('100%') 450 } 451 .height('100%') 452 } 453} 454``` 455 456### Union Type 457 458\@Local supports null, undefined, and union types. In the following example, the **count** type is **number | undefined**. If you click to change the **count** type, the UI will be re-rendered accordingly. 459 460```ts 461@Entry 462@ComponentV2 463struct Index { 464 @Local count: number | undefined = 10; 465 466 build() { 467 Column() { 468 Text(`count(${this.count})`) 469 Button("change to undefined") 470 .onClick(() => { 471 this.count = undefined; 472 }) 473 Button("change to number") 474 .onClick(() => { 475 this.count = 10; 476 }) 477 } 478 } 479} 480``` 481 482## FAQs 483 484### Repeated Value Changes to State Variables by Complex Constants Trigger Re-rendering 485 486```ts 487@Entry 488@ComponentV2 489struct Index { 490 list: string[][] = [['a'], ['b'], ['c']]; 491 @Local dataObjFromList: string[] = this.list[0]; 492 493 @Monitor("dataObjFromList") 494 onStrChange(monitor: IMonitor) { 495 console.log("dataObjFromList has changed"); 496 } 497 498 build() { 499 Column() { 500 Button('change to self').onClick(() => { 501 // The new value is the same as the locally initialized value. 502 this.dataObjFromList = this.list[0]; 503 }) 504 } 505 } 506} 507``` 508 509In the preceding example, each time you click Button('change to self'), the same constant of the **Array** type is assigned to a state variable of the same type, triggering re-rendering. This is because in state management V2, a proxy is added to Date, Map, Set, and Array that use state variable decorators such as @Trace and @Local to observe changes invoked by APIs. 510**dataObjFromList** is of a **Proxy** type but **list[0]** is of an **Array** type. As a result, when **list[0]** is assigned to **dataObjFromList**, the value changes trigger re-rendering. 511To avoid unnecessary value changes and re-renders, use [UIUtils.getTarget()](./arkts-new-getTarget.md) to obtain the original value and determine whether the original and new values are the same. If they are the same, do not perform value changes. 512 513Example of Using **UIUtils.getTarget()** 514 515```ts 516import { UIUtils } from '@ohos.arkui.StateManagement'; 517 518@Entry 519@ComponentV2 520struct Index { 521 list: string[][] = [['a'], ['b'], ['c']]; 522 @Local dataObjFromList: string[] = this.list[0]; 523 524 @Monitor("dataObjFromList") 525 onStrChange(monitor: IMonitor) { 526 console.log("dataObjFromList has changed"); 527 } 528 529 build() { 530 Column() { 531 Button('change to self').onClick(() => { 532 // Obtain the original value and compare it with the new value. 533 if (UIUtils.getTarget(this.dataObjFromList) !== this.list[0]) { 534 this.dataObjFromList = this.list[0]; 535 } 536 }) 537 } 538 } 539} 540``` 541