1# \@Link Decorator: Two-Way Synchronization Between Parent and Child Components 2 3 4An \@Link decorated variable creates two-way synchronization with a variable of its parent component. 5 6 7> **NOTE** 8> 9> This decorator can be used in ArkTS widgets since API version 9. 10> 11> This decorator can be used in atomic services since API version 11. 12 13## Overview 14 15An \@Link decorated variable in a child component shares the same value with a variable in its parent component. 16 17 18## Restrictions 19 20- The @Link decorator cannot be used in custom components decorated by [\@Entry](https://gitee.com/openharmony/docs/blob/master/en/application-dev/quick-start/arkts-create-custom-components.md#basic-structure-of-a-custom-component). 21 22 23## Rules of Use 24 25| \@Link Decorator | Description | 26| ---------------------------------------- | ------------------------------------------------------------ | 27| Decorator parameters | None. | 28| Synchronization type | Two-way:<br>from an \@State, \@StorageLink, or \@Link decorated variable in the parent component to this variable; and the other way around. | 29| Allowed variable types | Object, class, string, number, Boolean, enum, and array of these types.<br>Date type.<br>(Applicable to API version 11 or later) Map and Set types. <br>The union types defined by the ArkUI framework, including Length, ResourceStr, and ResourceColor.<br>The type must be specified and must be the same as that of the counterpart variable of the parent component.<br>For details about the scenarios of supported types, see [Observed Changes](#observed-changes).<br>**any** is not supported.<br>(Applicable to API version 11 and later versions) Union type of the preceding types, for example, **string \| number**, **string \| undefined** or **ClassA \| null**. For details, see [Union Type @Link](#union-type-link).<br>**NOTE**<br>When **undefined** or **null** is used, you are advised to explicitly specify the type to pass the TypeScript type check. For example, **@Link a: string \| undefined = undefined**. | 30| Initial value for the decorated variable | Forbidden. | 31 32 33## Variable Transfer/Access Rules 34 35| Transfer/Access | Description | 36| ---------- | ---------------------------------------- | 37| Initialization and update from the parent component| Mandatory. A two-way synchronization relationship can be established with the @State, @StorageLink, or \@Link decorated variable in the parent component. An @Link decorated variable can be initialized from an [\@State](./arkts-state.md), @Link, [\@Prop](./arkts-prop.md), [\@Provide](./arkts-provide-and-consume.md), [\@Consume](./arkts-provide-and-consume.md), [\@ObjectLink](./arkts-observed-and-objectlink.md), [\@StorageLink](./arkts-appstorage.md#storagelink), [\@StorageProp](./arkts-appstorage.md#storageprop), [\@LocalStorageLink](./arkts-localstorage.md#localstoragelink), or [\@LocalStorageProp](./arkts-localstorage.md#localstorageprop) decorated variable in the parent component.<br>Since API version 9, the syntax is **Comp({ aLink: this.aState })** for initializing an \@Link decorated variable in the child component from an @State decorated variable in its parent component. The **Comp({aLink: $aState})** syntax is also supported.| 38| Child component initialization | Supported; can be used to initialize a regular variable or \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.| 39| Access | Private, accessible only within the component. | 40 41 **Figure 1** Initialization rule 42 43 44 45 46## Observed Changes and Behavior 47 48 49### Observed Changes 50 51- When the decorated variable is of the Boolean, string, or number type, its value change can be observed. For details, see [Example for @Link with Simple and Class Types](#example-for-link-with-simple-and-class-types). 52 53- When the decorated variable is of the class or Object type, its value change and value changes of all its attributes, that is, the attributes that **Object.keys(observedObject)** returns, can be observed. For details, see [Example for @Link with Simple and Class Types](#example-for-link-with-simple-and-class-types). 54 55- When the decorated variable is of the array type, the addition, deletion, and updates of array items can be observed. For details, see [Array Type \@Link](#array-type-link). 56 57- When the decorated variable is of the Date type, the overall value assignment of the Date object can be observed, and the following APIs can be called to update Date attributes: **setFullYear**, **setMonth**, **setDate**, **setHours**, **setMinutes**, **setSeconds**, **setMilliseconds**, **setTime**, **setUTCFullYear**, **setUTCMonth**, **setUTCDate**, **setUTCHours**, **setUTCMinutes**, **setUTCSeconds**, and **setUTCMilliseconds**. 58 59```ts 60@Component 61struct DateComponent { 62 @Link selectedDate: Date; 63 64 build() { 65 Column() { 66 Button(`child increase the year by 1`).onClick(() => { 67 this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1) 68 }) 69 Button('child update the new date') 70 .margin(10) 71 .onClick(() => { 72 this.selectedDate = new Date('2023-09-09') 73 }) 74 DatePicker({ 75 start: new Date('1970-1-1'), 76 end: new Date('2100-1-1'), 77 selected: this.selectedDate 78 }) 79 } 80 81 } 82} 83 84@Entry 85@Component 86struct ParentComponent { 87 @State parentSelectedDate: Date = new Date('2021-08-08'); 88 89 build() { 90 Column() { 91 Button('parent increase the month by 1') 92 .margin(10) 93 .onClick(() => { 94 this.parentSelectedDate.setMonth(this.parentSelectedDate.getMonth() + 1) 95 }) 96 Button('parent update the new date') 97 .margin(10) 98 .onClick(() => { 99 this.parentSelectedDate = new Date('2023-07-07') 100 }) 101 DatePicker({ 102 start: new Date('1970-1-1'), 103 end: new Date('2100-1-1'), 104 selected: this.parentSelectedDate 105 }) 106 107 DateComponent({ selectedDate:this.parentSelectedDate }) 108 } 109 } 110} 111``` 112 113- When the decorated variable is **Map**, value changes of **Map** can be observed. In addition, you can call the **set**, **clear**, and **delete** APIs of **Map** to update its value. For details, see [Decorating Variables of the Map Type](#decorating-variables-of-the-map-type). 114 115- When the decorated variable is **Set**, value changes of **Set** can be observed. In addition, you can call the **add**, **clear**, and **delete** APIs of **Set** to update its value. For details, see [Decorating Variables of the Set Type](#decorating-variables-of-the-set-type). 116 117### Framework Behavior 118 119An \@Link decorated variable shares the lifecycle of its owning component. 120 121To understand the value initialization and update mechanism of the \@Link decorated variable, it is necessary to consider the parent component and the initial render and update process of the child component that owns the \@Link decorated variable (in this example, the \@State decorated variable in the parent component is used). 122 1231. Initial render: The execution of the parent component's **build()** function creates a new instance of the child component. The initialization process is as follows: 124 1. An \@State decorated variable of the parent component must be specified to initialize the child component's \@Link decorated variable. The child component's \@Link decorated variable value and its source variable are kept in sync (two-way data synchronization). 125 2. The \@State state variable wrapper class of the parent component is passed to the child component through the build function. After obtaining the \@State state variable of the parent component, the \@Link wrapper class of the child component registers the **this** pointer to the current \@Link wrapper class with the \@State variable of the parent component. 126 1272. Update of the \@Link source: When the state variable in the parent component is updated, the \@Link decorated variable in the related child component is updated. Processing steps: 128 1. As indicated in the initial rendering step, the child component's \@Link wrapper class registers the current **this** pointer with the parent component. When the \@State decorated variable in the parent component is changed, all system components (**elementid**) and state variables (such as the \@Link wrapper class) that depend on the parent component are traversed and updated. 129 2. After the \@Link wrapper class is updated, all system components (**elementId**) that depend on the \@Link decorated variable in the child component are notified of the update. In this way, the parent component has the state data of the child components synchronized. 130 1313. Update of \@Link: After the \@Link decorated variable in the child component is updated, the following steps are performed (the \@State decorated variable in the parent component is used): 132 1. After the \@Link decorated variable is updated, the **set** method of the \@State wrapper class in the parent component is called to synchronize the updated value back to the parent component. 133 2. The \@Link in the child component and \@State in the parent component traverse the dependent system components and update the corresponding UI. In this way, the \@Link decorated variable in the child component is synchronized back to the \@State decorated variable in the parent component. 134 135 136## Usage Scenarios 137 138 139### Example for @Link with Simple and Class Types 140 141In the following example, after **Parent View: Set yellowButton** and **Parent View: Set GreenButton** of the parent component **ShufflingContainer** are clicked, the change in the parent component is synchronized to the child components. 142 143 1. After buttons of the child components **GreenButton** and **YellowButton** are clicked, the child components (@Link decorated variables) change accordingly. Due to the two-way synchronization relationship between @Link and @State, the changes are synchronized to the parent component. 144 145 2. When a button in the parent component **ShufflingContainer** is clicked, the parent component (@State decorated variable) changes, and the changes are synchronized to the child components, which are then updated accordingly. 146 147```ts 148class GreenButtonState { 149 width: number = 0; 150 151 constructor(width: number) { 152 this.width = width; 153 } 154} 155 156@Component 157struct GreenButton { 158 @Link greenButtonState: GreenButtonState; 159 160 build() { 161 Button('Green Button') 162 .width(this.greenButtonState.width) 163 .height(40) 164 .backgroundColor('#64bb5c') 165 .fontColor('#FFFFFF, 90%') 166 .onClick(() => { 167 if (this.greenButtonState.width < 700) { 168 // Update the attribute of the class. The change can be observed and synchronized back to the parent component. 169 this.greenButtonState.width += 60; 170 } else { 171 // Update the class. The change can be observed and synchronized back to the parent component. 172 this.greenButtonState = new GreenButtonState(180); 173 } 174 }) 175 } 176} 177 178@Component 179struct YellowButton { 180 @Link yellowButtonState: number; 181 182 build() { 183 Button('Yellow Button') 184 .width(this.yellowButtonState) 185 .height(40) 186 .backgroundColor('#f7ce00') 187 .fontColor('#FFFFFF, 90%') 188 .onClick(() => { 189 // The change of the decorated variable of a simple type in the child component can be synchronized back to the parent component. 190 this.yellowButtonState += 40.0; 191 }) 192 } 193} 194 195@Entry 196@Component 197struct ShufflingContainer { 198 @State greenButtonState: GreenButtonState = new GreenButtonState(180); 199 @State yellowButtonProp: number = 180; 200 201 build() { 202 Column() { 203 Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) { 204 // Simple type @Link in the child component synchronized from @State in the parent component. 205 Button('Parent View: Set yellowButton') 206 .width(312) 207 .height(40) 208 .margin(12) 209 .fontColor('#FFFFFF, 90%') 210 .onClick(() => { 211 this.yellowButtonProp = (this.yellowButtonProp < 700) ? this.yellowButtonProp + 40 : 100; 212 }) 213 // Class type @Link in the child component synchronized from @State in the parent component. 214 Button('Parent View: Set GreenButton') 215 .width(312) 216 .height(40) 217 .margin(12) 218 .fontColor('#FFFFFF, 90%') 219 .onClick(() => { 220 this.greenButtonState.width = (this.greenButtonState.width < 700) ? this.greenButtonState.width + 100 : 100; 221 }) 222 // Initialize the class type @Link. 223 GreenButton({ greenButtonState: $greenButtonState }).margin(12) 224 // Initialize the simple type @Link. 225 YellowButton({ yellowButtonState: $yellowButtonProp }).margin(12) 226 } 227 } 228 } 229} 230``` 231 232 233 234### Array Type \@Link 235 236 237```ts 238@Component 239struct Child { 240 @Link items: number[]; 241 242 build() { 243 Column() { 244 Button(`Button1: push`) 245 .margin(12) 246 .width(312) 247 .height(40) 248 .fontColor('#FFFFFF, 90%') 249 .onClick(() => { 250 this.items.push(this.items.length + 1); 251 }) 252 Button(`Button2: replace whole item`) 253 .margin(12) 254 .width(312) 255 .height(40) 256 .fontColor('#FFFFFF, 90%') 257 .onClick(() => { 258 this.items = [100, 200, 300]; 259 }) 260 } 261 } 262} 263 264@Entry 265@Component 266struct Parent { 267 @State arr: number[] = [1, 2, 3]; 268 269 build() { 270 Column() { 271 Child({ items: $arr }) 272 .margin(12) 273 ForEach(this.arr, 274 (item: number) => { 275 Button(`${item}`) 276 .margin(12) 277 .width(312) 278 .height(40) 279 .backgroundColor('#11a2a2a2') 280 .fontColor('#e6000000') 281 }, 282 (item: ForEachInterface) => item.toString() 283 ) 284 } 285 } 286} 287``` 288 289 290 291As described above, the ArkUI framework can observe the addition, deletion, and replacement of array items. It should be noted that, in the preceding example, the type of the \@Link and \@State decorated variables is the same: number[]. It is not allowed to define the \@Link decorated variable in the child component as type number (**\@Link item: number**), and create child components for each array item in the \@State decorated array in the parent component. [\@Prop](arkts-prop.md) or [\@Observed](./arkts-observed-and-objectlink.md) should be used depending on application semantics. 292 293### Decorating Variables of the Map Type 294 295> **NOTE** 296> 297> Since API version 11, \@Link supports the Map type. 298 299In this example, the **value** variable is of the Map<number, string> type. When the button is clicked, the value of **message** changes, and the UI is re-rendered. 300 301```ts 302@Component 303struct Child { 304 @Link value: Map<number, string> 305 306 build() { 307 Column() { 308 ForEach(Array.from(this.value.entries()), (item: [number, string]) => { 309 Text(`${item[0]}`).fontSize(30) 310 Text(`${item[1]}`).fontSize(30) 311 Divider() 312 }) 313 Button('child init map').onClick(() => { 314 this.value = new Map([[0, "a"], [1, "b"], [3, "c"]]) 315 }) 316 Button('child set new one').onClick(() => { 317 this.value.set(4, "d") 318 }) 319 Button('child clear').onClick(() => { 320 this.value.clear() 321 }) 322 Button('child replace the first one').onClick(() => { 323 this.value.set(0, "aa") 324 }) 325 Button('child delete the first one').onClick(() => { 326 this.value.delete(0) 327 }) 328 } 329 } 330} 331 332 333@Entry 334@Component 335struct MapSample2 { 336 @State message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]]) 337 338 build() { 339 Row() { 340 Column() { 341 Child({ value: this.message }) 342 } 343 .width('100%') 344 } 345 .height('100%') 346 } 347} 348``` 349 350### Decorating Variables of the Set Type 351 352> **NOTE** 353> 354> Since API version 11, \@Link supports the Set type. 355 356In this example, the **message** variable is of the Set\<number\> type. When the button is clicked, the value of **message** changes, and the UI is re-rendered. 357 358```ts 359@Component 360struct Child { 361 @Link message: Set<number> 362 363 build() { 364 Column() { 365 ForEach(Array.from(this.message.entries()), (item: [number, string]) => { 366 Text(`${item[0]}`).fontSize(30) 367 Divider() 368 }) 369 Button('init set').onClick(() => { 370 this.message = new Set([0, 1, 2, 3, 4]) 371 }) 372 Button('set new one').onClick(() => { 373 this.message.add(5) 374 }) 375 Button('clear').onClick(() => { 376 this.message.clear() 377 }) 378 Button('delete the first one').onClick(() => { 379 this.message.delete(0) 380 }) 381 } 382 .width('100%') 383 } 384} 385 386 387@Entry 388@Component 389struct SetSample1 { 390 @State message: Set<number> = new Set([0, 1, 2, 3, 4]) 391 392 build() { 393 Row() { 394 Column() { 395 Child({ message: this.message }) 396 } 397 .width('100%') 398 } 399 .height('100%') 400 } 401} 402``` 403 404### Using Two-Way Synchronization Mechanism to Change Local Variables 405 406Use [\@Watch](./arkts-watch.md) to change local variables during two-way synchronization. 407 408In the following example, the \@State decorated variable **sourceNumber** is modified in \@Watch of \@Link to implement variable synchronization between parent and child components. However, the local modification of the \@State decorated variable **memberMessage** does not affect the variable change in the parent component. 409 410```ts 411@Entry 412@Component 413struct Parent { 414 @State sourceNumber: number = 0; 415 416 build() { 417 Column() { 418 Text(`sourceNumber of the parent component: ` + this.sourceNumber) 419 Child({ sourceNumber: this.sourceNumber }) 420 Button('sourceNumber is changed in the parent component') 421 .onClick(() => { 422 this.sourceNumber++; 423 }) 424 } 425 .width('100%') 426 .height('100%') 427 } 428} 429 430@Component 431struct Child { 432 @State memberMessage: string = 'Hello World'; 433 @Link @Watch('onSourceChange') sourceNumber: number; 434 435 onSourceChange() { 436 this.memberMessage = this.sourceNumber.toString(); 437 } 438 439 build() { 440 Column() { 441 Text(this.memberMessage) 442 Text(`sourceNumber of the child component: ` + this.sourceNumber.toString()) 443 Button('memberMessage is changed in the child component') 444 .onClick(() => { 445 this.memberMessage = 'Hello memberMessage'; 446 }) 447 } 448 } 449} 450``` 451 452## Union Type @Link 453 454@Link supports **undefined**, **null**, and union types. In the following example, the type of **name** is string | undefined. If the attribute or type of **name** is changed when the button in the parent component **Index** is clicked, the change will be synced to the child component. 455 456```ts 457@Component 458struct Child { 459 @Link name: string | undefined 460 461 build() { 462 Column() { 463 464 Button('Child change name to Bob') 465 .onClick(() => { 466 this.name = "Bob" 467 }) 468 469 Button('Child change animal to undefined') 470 .onClick(() => { 471 this.name = undefined 472 }) 473 474 }.width('100%') 475 } 476} 477 478@Entry 479@Component 480struct Index { 481 @State name: string | undefined = "mary" 482 483 build() { 484 Column() { 485 Text(`The name is ${this.name}`).fontSize(30) 486 487 Child({ name: this.name }) 488 489 Button('Parents change name to Peter') 490 .onClick(() => { 491 this.name = "Peter" 492 }) 493 494 Button('Parents change name to undefined') 495 .onClick(() => { 496 this.name = undefined 497 }) 498 } 499 } 500} 501``` 502 503## FAQs 504 505### Incorrect Type of \@Link Decorated State Variable 506 507When using \@Link to decorate a state variable in a child component, ensure that the variable type is the same as the source type, and the source is a state variable decorated by a decorator such as \@State. 508 509[Negative example] 510 511```ts 512@Observed 513class ClassA { 514 public c: number = 0; 515 516 constructor(c: number) { 517 this.c = c; 518 } 519} 520 521@Component 522struct LinkChild { 523 @Link testNum: number; 524 525 build() { 526 Text(`LinkChild testNum ${this.testNum}`) 527 } 528} 529 530@Entry 531@Component 532struct Parent { 533 @State testNum: ClassA = new ClassA(1); 534 535 build() { 536 Column() { 537 Text(`Parent testNum ${this.testNum.c}`) 538 .onClick(() => { 539 this.testNum.c += 1; 540 }) 541 // The type of the @Link decorated variable must be the same as that of the @State decorated data source. 542 LinkChild({ testNum: this.testNum.c }) 543 } 544 } 545} 546``` 547 548In the example, the type of **\@Link testNum: number** and the initialization from the parent component **LinkChild ({testNum:this.testNum.c})** are incorrect. The data source of \@Link must be a decorated state variable. The \@Link decorated variables must be of the same type as the data source, for example, \@Link: T and \@State: T. Therefore, the value should be changed to **\@Link testNum: ClassA**, and the initialization from the parent component should be **LinkChild({testNum: this.testNum})**. 549 550[Positive example] 551 552```ts 553@Observed 554class ClassA { 555 public c: number = 0; 556 557 constructor(c: number) { 558 this.c = c; 559 } 560} 561 562@Component 563struct LinkChild { 564 @Link testNum: ClassA; 565 566 build() { 567 Text(`LinkChild testNum ${this.testNum?.c}`) 568 .onClick(() => { 569 this.testNum.c += 1; 570 }) 571 } 572} 573 574@Entry 575@Component 576struct Parent { 577 @State testNum: ClassA = new ClassA(1); 578 579 build() { 580 Column() { 581 Text(`Parent testNum ${this.testNum.c}`) 582 .onClick(() => { 583 this.testNum.c += 1; 584 }) 585 // The type of the @Link decorated variable must be the same as that of the @State decorated data source. 586 LinkChild({ testNum: this.testNum }) 587 } 588 } 589} 590``` 591 592<!--no_check--> 593