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