• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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> Since API version 9, this decorator is supported in ArkTS widgets.
10
11
12## Overview
13
14An \@Link decorated variable in a child component shares the same value with a variable in its parent component.
15
16
17## Restrictions
18
19- The \@Link decorator cannot be used in custom components decorated by \@Entry.
20
21
22## Rules of Use
23
24| \@Link Decorator                                            | Description                                                        |
25| ------------------------------------------------------------ | ------------------------------------------------------------ |
26| Decorator parameters                                                  | None.                                                          |
27| 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.|
28| 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. For details about the scenarios of supported types, see [Observed Changes](#observed-changes).<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**. The union types defined by the AkrUI framework, including Length, ResourceStr, and ResourceColor, are supported.<br>The type must be specified and must be the same as that of the counterpart variable of the parent component.<br>**any** is not supported.|                                                              |
29| Initial value for the decorated variable                                          | Forbidden.                                        |
30
31
32## Variable Transfer/Access Rules
33
34| Transfer/Access     | Description                                      |
35| ---------- | ---------------------------------------- |
36| 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, \@Link, \@Prop, \@Provide, \@Consume, \@ObjectLink, \@StorageLink, \@StorageProp, \@LocalStorageLink, or \@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.|
37| Child component initialization  | Supported; can be used to initialize a regular variable or \@State, \@Link, \@Prop, or \@Provide decorated variable in the child component.|
38| Access | Private, accessible only within the component.                          |
39
40  **Figure 1** Initialization rule
41
42![en-us_image_0000001502092556](figures/en-us_image_0000001502092556.png)
43
44
45## Observed Changes and Behavior
46
47
48### Observed Changes
49
50- 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).
51
52- 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).
53
54- 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).
55
56- 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**.
57
58```ts
59@Component
60struct DateComponent {
61  @Link selectedDate: Date;
62
63  build() {
64    Column() {
65      Button(`child increase the year by 1`).onClick(() => {
66        this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1)
67      })
68      Button('child update the new date')
69        .margin(10)
70        .onClick(() => {
71          this.selectedDate = new Date('2023-09-09')
72        })
73      DatePicker({
74        start: new Date('1970-1-1'),
75        end: new Date('2100-1-1'),
76        selected: this.selectedDate
77      })
78    }
79
80  }
81}
82
83@Entry
84@Component
85struct ParentComponent {
86  @State parentSelectedDate: Date = new Date('2021-08-08');
87
88  build() {
89    Column() {
90      Button('parent increase the month by 1')
91        .margin(10)
92        .onClick(() => {
93          this.parentSelectedDate.setMonth(this.parentSelectedDate.getMonth() + 1)
94        })
95      Button('parent update the new date')
96        .margin(10)
97        .onClick(() => {
98          this.parentSelectedDate = new Date('2023-07-07')
99        })
100      DatePicker({
101        start: new Date('1970-1-1'),
102        end: new Date('2100-1-1'),
103        selected: this.parentSelectedDate
104      })
105
106      DateComponent({ selectedDate:this.parentSelectedDate })
107    }
108  }
109}
110```
111
112- 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).
113
114- 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).
115
116### Framework Behavior
117
118An \@Link decorated variable shares the lifecycle of its owning component.
119
120To 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).
121
1221. 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:
123   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).
124   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.
125
1262. 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:
127   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.
128   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.
129
1303. 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):
131   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.
132   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.
133
134
135## Usage Scenarios
136
137
138### Example for @Link with Simple and Class Types
139
140In 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.
141
142  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.
143
144  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.
145
146```ts
147class GreenButtonState {
148  width: number = 0;
149
150  constructor(width: number) {
151    this.width = width;
152  }
153}
154
155@Component
156struct GreenButton {
157  @Link greenButtonState: GreenButtonState;
158
159  build() {
160    Button('Green Button')
161      .width(this.greenButtonState.width)
162      .height(40)
163      .backgroundColor('#64bb5c')
164      .fontColor('#FFFFFF, 90%')
165      .onClick(() => {
166        if (this.greenButtonState.width < 700) {
167          // Update the attribute of the class. The change can be observed and synchronized back to the parent component.
168          this.greenButtonState.width += 60;
169        } else {
170          // Update the class. The change can be observed and synchronized back to the parent component.
171          this.greenButtonState = new GreenButtonState(180);
172        }
173      })
174  }
175}
176
177@Component
178struct YellowButton {
179  @Link yellowButtonState: number;
180
181  build() {
182    Button('Yellow Button')
183      .width(this.yellowButtonState)
184      .height(40)
185      .backgroundColor('#f7ce00')
186      .fontColor('#FFFFFF, 90%')
187      .onClick(() => {
188        // The change of the decorated variable of a simple type in the child component can be synchronized back to the parent component.
189        this.yellowButtonState += 40.0;
190      })
191  }
192}
193
194@Entry
195@Component
196struct ShufflingContainer {
197  @State greenButtonState: GreenButtonState = new GreenButtonState(180);
198  @State yellowButtonProp: number = 180;
199
200  build() {
201    Column() {
202      Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
203        // Simple type @Link in the child component synchronized from @State in the parent component.
204        Button('Parent View: Set yellowButton')
205          .width(312)
206          .height(40)
207          .margin(12)
208          .fontColor('#FFFFFF, 90%')
209          .onClick(() => {
210            this.yellowButtonProp = (this.yellowButtonProp < 700) ? this.yellowButtonProp + 40 : 100;
211          })
212        // Class type @Link in the child component synchronized from @State in the parent component.
213        Button('Parent View: Set GreenButton')
214          .width(312)
215          .height(40)
216          .margin(12)
217          .fontColor('#FFFFFF, 90%')
218          .onClick(() => {
219            this.greenButtonState.width = (this.greenButtonState.width < 700) ? this.greenButtonState.width + 100 : 100;
220          })
221        // Initialize the class type @Link.
222        GreenButton({ greenButtonState: $greenButtonState }).margin(12)
223        // Initialize the simple type @Link.
224        YellowButton({ yellowButtonState: $yellowButtonProp }).margin(12)
225      }
226    }
227  }
228}
229```
230
231![Video-link-UsageScenario-one](figures/Video-link-UsageScenario-one.gif)
232
233### Array Type \@Link
234
235
236```ts
237@Component
238struct Child {
239  @Link items: number[];
240
241  build() {
242    Column() {
243      Button(`Button1: push`)
244        .margin(12)
245        .width(312)
246        .height(40)
247        .fontColor('#FFFFFF, 90%')
248        .onClick(() => {
249          this.items.push(this.items.length + 1);
250        })
251      Button(`Button2: replace whole item`)
252        .margin(12)
253        .width(312)
254        .height(40)
255        .fontColor('#FFFFFF, 90%')
256        .onClick(() => {
257          this.items = [100, 200, 300];
258        })
259    }
260  }
261}
262
263@Entry
264@Component
265struct Parent {
266  @State arr: number[] = [1, 2, 3];
267
268  build() {
269    Column() {
270      Child({ items: $arr })
271        .margin(12)
272      ForEach(this.arr,
273        (item: number) => {
274          Button(`${item}`)
275            .margin(12)
276            .width(312)
277            .height(40)
278            .backgroundColor('#11a2a2a2')
279            .fontColor('#e6000000')
280        },
281        (item: ForEachInterface) => item.toString()
282      )
283    }
284  }
285}
286```
287
288![Video-link-UsageScenario-two](figures/Video-link-UsageScenario-two.gif)
289
290As 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 should be used depending on application semantics.
291
292### Decorating Variables of the Map Type
293
294> **NOTE**
295>
296> Since API version 11, \@Link supports the Map type.
297
298In 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.
299
300```ts
301@Component
302struct Child {
303  @Link value: Map<number, string>
304
305  build() {
306    Column() {
307      ForEach(Array.from(this.value.entries()), (item: [number, string]) => {
308        Text(`${item[0]}`).fontSize(30)
309        Text(`${item[1]}`).fontSize(30)
310        Divider()
311      })
312      Button('child init map').onClick(() => {
313        this.value = new Map([[0, "a"], [1, "b"], [3, "c"]])
314      })
315      Button('child set new one').onClick(() => {
316        this.value.set(4, "d")
317      })
318      Button('child clear').onClick(() => {
319        this.value.clear()
320      })
321      Button('child replace the first one').onClick(() => {
322        this.value.set(0, "aa")
323      })
324      Button('child delete the first one').onClick(() => {
325        this.value.delete(0)
326      })
327    }
328  }
329}
330
331
332@Entry
333@Component
334struct MapSample2 {
335  @State message: Map<number, string> = new Map([[0, "a"], [1, "b"], [3, "c"]])
336
337  build() {
338    Row() {
339      Column() {
340        Child({ value: this.message })
341      }
342      .width('100%')
343    }
344    .height('100%')
345  }
346}
347```
348
349### Decorating Variables of the Set Type
350
351> **NOTE**
352>
353> Since API version 11, \@Link supports the Set type.
354
355In 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.
356
357```ts
358@Component
359struct Child {
360  @Link message: Set<number>
361
362  build() {
363    Column() {
364      ForEach(Array.from(this.message.entries()), (item: [number, string]) => {
365        Text(`${item[0]}`).fontSize(30)
366        Divider()
367      })
368      Button('init set').onClick(() => {
369        this.message = new Set([0, 1, 2, 3, 4])
370      })
371      Button('set new one').onClick(() => {
372        this.message.add(5)
373      })
374      Button('clear').onClick(() => {
375        this.message.clear()
376      })
377      Button('delete the first one').onClick(() => {
378        this.message.delete(0)
379      })
380    }
381    .width('100%')
382  }
383}
384
385
386@Entry
387@Component
388struct SetSample1 {
389  @State message: Set<number> = new Set([0, 1, 2, 3, 4])
390
391  build() {
392    Row() {
393      Column() {
394        Child({ message: this.message })
395      }
396      .width('100%')
397    }
398    .height('100%')
399  }
400}
401```
402
403## Union Type @Link
404
405@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.
406
407```ts
408@Component
409struct Child {
410  @Link name: string | undefined
411
412  build() {
413    Column() {
414
415      Button('Child change name to Bob')
416        .onClick(() => {
417          this.name = "Bob"
418        })
419
420      Button('Child change animal to undefined')
421        .onClick(() => {
422          this.name = undefined
423        })
424
425    }.width('100%')
426  }
427}
428
429@Entry
430@Component
431struct Index {
432  @State name: string | undefined = "mary"
433
434  build() {
435    Column() {
436      Text(`The name is  ${this.name}`).fontSize(30)
437
438      Child({ name: this.name })
439
440      Button('Parents change name to Peter')
441        .onClick(() => {
442          this.name = "Peter"
443        })
444
445      Button('Parents change name to undefined')
446        .onClick(() => {
447          this.name = undefined
448        })
449    }
450  }
451}
452```
453
454## FAQs
455
456### Incorrect Type of \@Link Decorated State Variable
457
458When 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.
459
460[Nonexample]
461
462```ts
463@Observed
464class ClassA {
465  public c: number = 0;
466
467  constructor(c: number) {
468    this.c = c;
469  }
470}
471
472@Component
473struct LinkChild {
474  @Link testNum: number;
475
476  build() {
477    Text(`LinkChild testNum ${this.testNum}`)
478  }
479}
480
481@Entry
482@Component
483struct Parent {
484  @State testNum: ClassA[] = [new ClassA(1)];
485
486  build() {
487    Column() {
488      Text(`Parent testNum ${this.testNum[0].c}`)
489        .onClick(() => {
490          this.testNum[0].c += 1;
491        })
492      // The type of the @Link decorated variable must be the same as that of the @State decorated data source.
493      LinkChild({ testNum: this.testNum[0].c })
494    }
495  }
496}
497```
498
499In 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: $testNum})**.
500
501[Example]
502
503```ts
504@Observed
505class ClassA {
506  public c: number = 0;
507
508  constructor(c: number) {
509    this.c = c;
510  }
511}
512
513@Component
514struct LinkChild {
515  @Link testNum: ClassA[];
516
517  build() {
518    Text(`LinkChild testNum ${this.testNum[0]?.c}`)
519  }
520}
521
522@Entry
523@Component
524struct Parent {
525  @State testNum: ClassA[] = [new ClassA(1)];
526
527  build() {
528    Column() {
529      Text(`Parent testNum ${this.testNum[0].c}`)
530        .onClick(() => {
531          this.testNum[0].c += 1;
532        })
533      // The type of the @Link decorated variable must be the same as that of the @State decorated data source.
534      LinkChild({ testNum: $testNum })
535    }
536  }
537}
538```
539
540<!--no_check-->
541