目录

@Link Decorator: Implementing Two-Way Synchronization Between Parent and Child Components

A variable decorated by @Link in a child component establishes two-way data binding with its corresponding data source in the parent component.

Before reading this topic, you are advised to understand the basic usage of @State. For best practices, see State Management Best Practices. For common issues, see State Management FAQs.

This decorator can be used in ArkTS widgets since API version 9.

This decorator can be used in atomic services since API version 11.

Overview

An @Link decorated variable in a child component shares the same value with a variable in its parent component.

Usage Rules

@Link Decorator Description
Parameters N/A
Synchronization type

Two-way:

The state variable in the parent component can be synchronized with the child component @Link in a two-way manner. Changes to either will be synchronized to the other.

Allowed variable types

Object, class, string, number, Boolean, enum, and array of these types.

API version 10 and later: Date type.

API version 11 and later: Map, Set, undefined, null, union types defined by the ArkUI framework, for example, Length, ResourceStr, and ResourceColor. For details, see Using Union Types.

For details about the scenarios of supported types, see Observed Changes.

Disallowed variable types Function.
Initial value for the decorated variable Local initialization is forbidden.

Variable Transfer/Access Rules

Transfer/Access Description
Initialization and update from the parent component

Mandatory.

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, thereby establishing two-way binding.

- Since API version 9, the syntax for initializing the child component @Link from the parent component @State is Comp({ aLink: this.aState }), and Comp({aLink: $aState}) is also supported.

Child component initialization Supported; can be used to initialize a regular variable or @State, @Link, @Prop, or @Provide decorated variable in the child component.
Access from outside the component Private, accessible only within the component.

Figure 1 Initialization rules

Observed Changes and Behavior

[h2]Observed Changes

[h2]Framework Behavior

An @Link decorated variable shares the lifecycle of its owning component.

To 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).

  1. Initial render: The execution of the parent component's build() creates an instance of the child component. The initialization process is as follows:

    1. An @State decorated variable of the parent component is specified to initialize the child component's @Link decorated variable. The child component's @Link decorated variable value remains synchronized with its source variable, enabling two-way data synchronization.

    2. The parent component's @State wrapper variable instance is passed to the child component through the child's constructor. When the child component's @Link wrapper instance receives a reference to the parent's @State variable, it registers itself with that parent @State variable.

  2. 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. Procedure:

    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 parent component's @State variable is changed, all built-in components and state variables (such as the @Link wrapper instance) that depend on it are traversed and updated.

    2. After the @Link wrapper instance is updated, all built-in components 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.

  3. 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):

    1. After the @Link decorated variable is updated, the set method of the parent component's @State wrapper instance is called to synchronize the new value back to the parent component.

    2. The @Link in the child component and @State in the parent component each update their respective dependent UI components through independent dependency traversals, achieving synchronized state while maintaining proper UI consistency.

Constraints

  1. You are not advised to use the @Link decorator in custom components decorated with @Entry. Otherwise, a warning will be reported during compilation. If the custom component is used as the root node of a page, a runtime error will be reported.

  2. Variables decorated with @Link do not support local initialization. Any attempt to perform local initialization on them will result in a compilation error.

    // Incorrect usage. An error is reported during compilation.
    @Link count: number = 10;
    
    // Correct usage.
    @Link count: number;
  3. The type of a variable decorated with @Link must match the type of its data source. The compilation fails if the types do not match. In addition, the data source must be a state variable. Otherwise, the framework throws a runtime error.

    Since API version 23, validation for @Link data source errors has been added, and runtime errors have been changed to compile-time errors. For details, see Common UI-Related App Crash Issues.

    Incorrect Usage

    class Info {
      value: string = 'Hello';
    }
    
    class Cousin {
      name: string = 'Hello';
    }
    
    @Component
    struct Child {
      // Incorrect usage 1: The type of the @Link decorated variable is different from that of the @State decorated variable.
      @Link test: Cousin;
      // Incorrect usage 2: non-state variable of the data source
      @Link testStr: string;
    
      build() {
        Column() {
          Text(this.test.name)
          Text(this.testStr)
        }
      }
    }
    
    @Entry
    @Component
    struct LinkExample {
      @State info: Info = new Info();
    
      build() {
        Column() {
          Child({
            // Incorrect usage 1: The type of the @Link decorated variable is different from that of the @State decorated variable.
            test: this.info,
            // Incorrect usage 2: The data source is not a state variable.
            testStr: this.info.value
          })
        }
      }
    }

    Correct Usage

    class LinkInfo {
      public value: string = 'Hello';
    }
    
    @Component
    struct LinkChild {
      // In the child component, use @Link to decorate the test variable of the LinkInfo type.
      @Link test: LinkInfo;
    
      build() {
        Text(this.test.value)
          .fontSize(20)
          .margin(10)
      }
    }
    
    @Entry
    @Component
    struct LinkExample {
      @State info: LinkInfo = new LinkInfo();
    
      build() {
        Column() {
          // In the parent component, use the @State-decorated info variable to initialize the test variable of the LinkChild component.
          LinkChild({test: this.info})
        }
        .width('100%')
      }
    }

  4. A variable decorated by @Link can only be initialized by a state variable, not by a regular variable. Otherwise, a compilation error occurs.

    Incorrect Usage

    @Component
    struct Child {
      @Link message: string;
    
      build() {
        Text(`${this.message}`).margin('20%')
      }
    }
    
    @Entry
    @Component
    struct LinkExample {
      message: string = 'Hello';
    
      build() {
        Column() {
          // Incorrect usage. Regular variables cannot initialize @Link decorated variables.
          Child({ message: this.message })
        }
      }
    }

    Correct Usage

    @Component
    struct LinkChild2 {
      @Link message: string;
    
      build() {
        Text(`${this.message}`).margin('20%')
      }
    }
    
    @Entry
    @Component
    struct LinkExample2 {
      @State message: string = 'Hello';
    
      build() {
        Column() {
          // Correct usage.
          LinkChild2({ message: this.message })
        }
      }
    }

  5. @Link does not support decorating variables of the Function type. Before API version 23, an error occurs at runtime in the app.

    Since API version 23, relevant validation has been added during app compilation. Decorating a Function type variable with @Link triggers an ERROR, and the @Link decorator should be removed from the Function type variable in the code.

Use Scenarios

[h2]Decorating Variables of the Map Type

Since API version 11, @Link supports the Map type.

In 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.

@Component
struct MapSampleChild {
  @Link value: Map<number, string>;

  build() {
    Column() {
      ForEach(Array.from(this.value.entries()), (item: [number, string]) => {
        Text(`${item[0]}`)
          .fontSize(30)
          .margin(10)
        Text(`${item[1]}`)
          .fontSize(30)
          .margin(10)
        Divider()
      })
      // The Map type of the child component can be synchronized back to the parent component.
      Button('child init map')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.value = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
        })
      Button('child set new one')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.value.set(4, 'd');
        })
      Button('child clear')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.value.clear();
        })
      Button('child replace the first one')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.value.set(0, 'aa');
        })
      Button('child delete the first one')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.value.delete(0);
        })
    }
    .width('100%')
  }
}


@Entry
@Component
struct MapSample {
  @State message: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);

  build() {
    Row() {
      Column() {
        MapSampleChild({ value: this.message })
      }
      .width('100%')
    }
    .height('100%')
  }
}

[h2]Decorating Variables of the Set Type

Since API version 11, @Link supports the Set type.

In 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.

@Component
struct SetSampleChild {
  @Link message: Set<number>;

  build() {
    Column() {
      ForEach(Array.from(this.message.entries()), (item: [number, number]) => {
        Text(`${item[0]}`)
          .fontSize(30)
          .margin(10)
        Divider()
      })
      // The Set type in the child component can be synchronized back to the parent component.
      Button('init set')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.message = new Set([0, 1, 2, 3, 4]);
        })
      Button('set new one')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.message.add(5);
        })
      Button('clear')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.message.clear();
        })
      Button('delete the first one')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.message.delete(0);
        })
    }
    .width('100%')
  }
}


@Entry
@Component
struct SetSample {
  @State message: Set<number> = new Set([0, 1, 2, 3, 4]);

  build() {
    Row() {
      Column() {
        SetSampleChild({ message: this.message })
      }
      .width('100%')
    }
    .height('100%')
  }
}

[h2]Decorating Variables of the Date Type

In this example, the selectedDate variable is of the Date type. After the button is clicked, the value of selectedDate changes, and the UI is re-rendered.

@Component
struct DateComponent {
  @Link selectedDate: Date;

  build() {
    Column() {
      // The Date type of the child component can be synchronized back to the parent component.
      Button(`child increase the year by 1`)
        .width(300)
        .margin(10)
        .onClick(() => {
          this.selectedDate.setFullYear(this.selectedDate.getFullYear() + 1);
        })
      Button('child update the new date')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.selectedDate = new Date('2023-09-09');
        })
      DatePicker({
        start: new Date('1970-1-1'),
        end: new Date('2100-1-1'),
        selected: this.selectedDate
      })
    }
    .width('100%')
  }
}

@Entry
@Component
struct ParentComponent {
  @State parentSelectedDate: Date = new Date('2021-08-08');

  build() {
    Column() {
      Button('parent increase the month by 1')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.parentSelectedDate.setMonth(this.parentSelectedDate.getMonth() + 1);
        })
      Button('parent update the new date')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.parentSelectedDate = new Date('2023-07-07');
        })
      DatePicker({
        start: new Date('1970-1-1'),
        end: new Date('2100-1-1'),
        selected: this.parentSelectedDate
      })

      DateComponent({ selectedDate:this.parentSelectedDate })
    }
    .width('100%')
  }
}

[h2]Using the Two-Way Synchronization Mechanism to Change Local Variables

Use @Watch to change local variables during two-way synchronization.

In the following example, a @State decorated variable memberMessage is modified inside the @Watch callback of @Link to achieve variable synchronization between parent and child components. However, local modifications to the @State decorated variable memberMessage do not affect the variable in the parent component.

@Entry
@Component
struct ChangeVariables {
  @State sourceNumber: number = 0;

  build() {
    Column() {
      Text(`sourceNumber of the parent component:` + this.sourceNumber)
        .fontSize(20)
        .margin(10)
      ChangeVariablesChild({ sourceNumber: this.sourceNumber })
      Button('Change sourceNumber in Parent Component')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.sourceNumber++;
        })
    }
    .width('100%')
    .height('100%')
  }
}

@Component
struct ChangeVariablesChild {
  @State memberMessage: string = 'Hello World';
  @Link @Watch('onSourceChange') sourceNumber: number;

  onSourceChange() {
    // Local modification of memberMessage in the child component does not affect the variable change in the parent component.
    this.memberMessage = this.sourceNumber.toString();
  }

  build() {
    Column() {
      Text(this.memberMessage)
        .fontSize(20)
        .margin(10)
      Text(`sourceNumber of the child component:` + this.sourceNumber.toString())
        .fontSize(20)
        .margin(10)
      Button('Change memberMessage in Child Component')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.memberMessage = 'Hello memberMessage';
        })
    }
    .width('100%')
  }
}

[h2]Using Union Types

@Link supports union types, undefined, and null. In the following example, the type of name is string | undefined. Tapping the button in the parent component UnionTypes changes the property or type of name, and the UnionChild component is refreshed accordingly.

@Component
struct UnionChild {
  // @Link supports union types.
  @Link name: string | undefined;

  build() {
    Column() {
      Button('Child change name to Bob')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.name = 'Bob';
        })

      Button('Child change name to undefined')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.name = undefined;
        })

    }
    .width('100%')
  }
}

@Entry
@Component
struct UnionTypes {
  @State name: string | undefined = 'mary';

  build() {
    Column() {
      Text(`The name is  ${this.name}`)
        .fontSize(20)
        .margin(10)

      UnionChild({ name: this.name })

      Button('Parents change name to Peter')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.name = 'Peter';
        })

      Button('Parents change name to undefined')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.name = undefined;
        })
    }
    .width('100%')
  }
}