目录

@Param Decorator: Inputting External Parameters to Components

To enhance the capability of child components to accept external parameter input, developers can use the @Param decorator.

@Param can receive not only the external input of the component, but also the synchronous change of @Local. Before reading this topic, you are advised to read @Local.

The @Param decorator is supported in custom components decorated with @ComponentV2 since API version 12.

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

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

Overview

@Param indicates the state passed in from outside, ensuring that data can be synchronized between the parent and child components.

Limitations of State Management V1 to Accept Decorators Passed in Externally

State management V1 has multiple decorators that can accept external input, including @State, @Prop, @Link, and @ObjectLink. These decorators have restrictions and are difficult to distinguish. Improper use of them may cause performance problems.

@Observed
class Region {
  public x: number;
  public y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

@Observed
class Info {
  public region: Region;

  constructor(x: number, y: number) {
    this.region = new Region(x, y);
  }
}

@Entry
@Component
struct Index {
  @State info: Info = new Info(0, 0);

  build() {
    Column() {
      Button('change Info')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.info = new Info(100, 100);
        })
      Child({
        region: this.info.region,
        regionProp: this.info.region,
        infoProp: this.info,
        infoLink: this.info,
        infoState: this.info
      })
    }
    .width('100%')
  }
}

@Component
struct Child {
  // Summary of decorators that accept external input in V1.
  @ObjectLink region: Region;
  @Prop regionProp: Region;
  @Prop infoProp: Info;
  @Link infoLink: Info;
  @State infoState: Info = new Info(1, 1);

  build() {
    Column() {
      Text(`ObjectLink region: ${this.region.x}-${this.region.y}`)
        .fontSize(20)
        .margin(10)
      Text(`Prop regionProp: ${this.regionProp.x}-${this.regionProp.y}`)
        .fontSize(20)
        .margin(10)
    }
    .width('100%')
  }
}

In the preceding example, @State can only receive the reference of info during initialization. After info is changed, synchronization cannot be performed. @Prop supports one-way synchronization, but the deep copy performance is still poor for complex types. @Link can synchronize the input reference in a two-way manner, but it requires that the data source be also a state variable. Therefore, it cannot accept the member property region in info. @ObjectLink can accept the class member property which must be decorated by @Observed. Different restrictions of decorators make the value transfer rules between parent and child components complex and difficult to use. This is where @Param, a decorator that indicates the component state passed in from outside, comes into the picture.

Decorator Description

@Param Variable Decorator Description
Parameters None
Allowed local modification No. If you need to change the value, you can use @Param with @Once to change the local value of the child component. Alternatively, you can use the @Event decorator to change the value of the @Param data source.
Synchronization type One-way synchronization from the parent to the child component.
Allowed decorated variable types Basic types, such as object, class, string, number, boolean, and enum, and built-in types such as Array, Date, Map, and Set. It supports null, undefined, and union types.
Initial value for the decorated variable Local initialization is allowed. If local initialization is not performed, this parameter must be used together with the @Require decorator and initialization must be passed from outside.

Variable Passing

Behavior Rule
Initialization from the parent component @Param decorated variables can be initialized locally. If local initialization is not performed, the variables must be initialized from outside. When both the local initial value and external input value exist, the latter is preferentially used for initialization.
Child component initialization @Param decorated variables can initialize @Param-decorated variable in a child component.
Synchronization @Param can be synchronized with the state variable data source passed in by the parent component (that is, the variable decorated by @Local or @Param). When the data source changes, the changes will be synchronized to @Param of the child component.

Observed Changes

@Param decorated variables enjoy observation capability. When a decorated variable changes, the UI component bound to the variable will be re-rendered.

Constraints

The @Param decorator has the following constraints:

Use Cases

[h2]Passing and Synchronizing Variables from the Parent Component to the Child Component

@Param receives and synchronizes the data passed in by the @Local or @Param parent component in real time.

@ObservedV2
class Region {
  @Trace public x: number;
  @Trace public y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
}

@ObservedV2
class Info {
  @Trace public name: string;
  @Trace public age: number;
  @Trace public region: Region;

  constructor(name: string, age: number, x: number, y: number) {
    this.name = name;
    this.age = age;
    this.region = new Region(x, y);
  }
}

@Entry
@ComponentV2
struct Index {
  // Decorate the infoList array with @Local and pass it as the data source to the child component's @Param.
  @Local infoList: Info[] = [new Info('Alice', 8, 0, 0), new Info('Barry', 10, 1, 20), new Info('Cindy', 18, 24, 40)];

  build() {
    Column() {
      ForEach(this.infoList, (info: Info) => {
        MiddleComponent({ info: info })
      })
      // Modify array elements and object properties to trigger updates in MiddleComponent and SubComponent.
      Button('change')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.infoList[0] = new Info('Atom', 40, 27, 90);
          this.infoList[1].name = 'Bob';
          this.infoList[2].region = new Region(7, 9);
        })
    }
    .width('100%')
  }
}

@ComponentV2
struct MiddleComponent {
  // Use @Param to receive the Info object passed from the parent component. The child component updates when the data source changes.
  @Require @Param info: Info;

  build() {
    Column() {
      Text(`name: ${this.info.name}`)
        .fontSize(20)
        .margin(10)
      Text(`age: ${this.info.age}`)
        .fontSize(20)
        .margin(10)
      // Pass the Region object further to the child component's @Param.
      SubComponent({ region: this.info.region })
    }
    .width('100%')
  }
}

@ComponentV2
struct SubComponent {
  // @Param receives the Region object passed from the parent component. The child component updates when the data source changes.
  @Require @Param region: Region;

  build() {
    Column() {
      Text(`region: ${this.region.x}-${this.region.y}`)
        .fontSize(20)
        .margin(10)
    }
    .width('100%')
  }
}

[h2]Decorating Variables of the Array Type

By using @Param to decorate the variables of the Array type, you can observe the value assignment to the array and the changes brought by the push, pop, shift, unshift, splice, copyWithin, fill, reverse, and sort APIs of the array.

@ComponentV2
struct Child {
  // Use @Param to receive an Array type variable passed from the parent component.
  @Require @Param count: number[];

  build() {
    Column() {
      ForEach(this.count, (item: number) => {
        Text(`${item}`)
          .fontSize(30)
          .margin(10)
        Divider()
      })
    }
    .width('100%')
  }
}

@Entry
@ComponentV2
struct Index {
  // Use @Local to decorate an Array type variable as the data source to pass to the child component's @Param.
  @Local count: number[] = [1, 2, 3];

  build() {
    Row() {
      Column() {
        Child({ count: this.count })
        // Reassign the entire array to trigger child component updates.
        Button('init array')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.count = [9, 8, 7];
          })
        // Add array elements to trigger child component updates.
        Button('push')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.count.push(0);
          })
        // Reverse array elements to trigger child component updates.
        Button('reverse')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.count.reverse();
          })
        // Fill the array with the same element to trigger child component updates.
        Button('fill')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.count.fill(6);
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

[h2]Decorating Variables of the Date Type

By using @Param to decorate the variables of the Date type, you can observe the value changes to the entire Date and the changes brought by calling the Date APIs: setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, and setUTCMilliseconds.

@ComponentV2
struct DateComponent {
  // Use @Param to receive a Date variable passed from the parent component.
  @Param selectedDate: Date = new Date('2024-01-01');

  build() {
    Column() {
      DatePicker({
        start: new Date('1970-1-1'),
        end: new Date('2100-1-1'),
        selected: this.selectedDate
      })
    }
    .width('100%')
  }
}

@Entry
@ComponentV2
struct Index {
  // Use @Local to decorate a Date variable as the data source to pass to the child component's @Param.
  @Local parentSelectedDate: Date = new Date('2021-08-08');

  build() {
    Column() {
      // Reassign the entire Date variable to trigger a child component update.
      Button('parent update the new date')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.parentSelectedDate = new Date('2023-07-07');
        })
      // Call the setFullYear method of Date to modify the year, triggering a child component update.
      Button('increase the year by 1')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.parentSelectedDate.setFullYear(this.parentSelectedDate.getFullYear() + 1);
        })
      // Call the setMonth method of Date to modify the month, triggering a child component update.
      Button('increase the month by 1')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.parentSelectedDate.setMonth(this.parentSelectedDate.getMonth() + 1);
        })
      // Call the setDate method of Date to modify the day, triggering a child component update.
      Button('parent increase the day by 1')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.parentSelectedDate.setDate(this.parentSelectedDate.getDate() + 1);
        })
      DateComponent({ selectedDate: this.parentSelectedDate })
    }
    .width('100%')
  }
}

[h2]Decorating Variables of the Map Type

By using @Param to decorate the variables of the Map type, you can observe the overall value changes to the entire Map and the changes brought by calling the Map APIs: set, clear, and delete.

@ComponentV2
struct Child {
  // Use @Param to receive a Map type variable passed from the parent component.
  @Param value: Map<number, string> = new Map();

  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()
      })
    }
    .width('100%')
  }
}

@Entry
@ComponentV2
struct Index {
  // Use @Local to decorate a Map type variable as the data source to pass to the child component's @Param.
  @Local message: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);

  build() {
    Row() {
      Column() {
        Child({ value: this.message })
        // Reassign the entire Map to trigger a child component update.
        Button('init map')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
          })
        // Add a key-value pair to trigger a child component update.
        Button('set new one')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message.set(4, 'd');
          })
        // Clear the Map to trigger a child component update.
        Button('clear')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message.clear();
          })
        // Update a key-value pair to trigger a child component update.
        Button('replace the first one')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message.set(0, 'aa');
          })
        // Delete a key-value pair to trigger a child component update.
        Button('delete the first one')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message.delete(0);
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

[h2]Decorating Variables of the Set Type

By using @Param to decorate the variables of the Set type, you can observe the overall value changes to the entire Set and the changes brought by calling the Set APIs: add, clear, and delete.

@ComponentV2
struct Child {
  // Use @Param to receive a Set type variable passed from the parent component.
  @Param message: Set<number> = new Set();

  build() {
    Column() {
      ForEach(Array.from(this.message.entries()), (item: [number, number]) => {
        Text(`${item[0]}`)
          .fontSize(30)
          .margin(10)
        Divider()
      })
    }
    .width('100%')
  }
}

@Entry
@ComponentV2
struct Index {
  // Use @Local to decorate a Set type variable as the data source to pass to the child component's @Param.
  @Local message: Set<number> = new Set([0, 1, 2, 3, 4]);

  build() {
    Row() {
      Column() {
        Child({ message: this.message })
        // Reassign the entire Set to trigger child component updates.
        Button('init set')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message = new Set([0, 1, 2, 3, 4]);
          })
        // Add an element to trigger child component updates.
        Button('set new one')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message.add(5);
          })
        // Clear the Set to trigger child component updates.
        Button('clear')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message.clear();
          })
        // Delete an element to trigger child component updates.
        Button('delete the first one')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message.delete(0);
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

[h2]Union Type

@Param supports null, undefined, and union types. In the following example, count is of the number | undefined type. Clicking the buttons to change the type of count will trigger UI re-rendering.

@Entry
@ComponentV2
struct Index {
  // Use @Local to decorate a union type variable and pass it as the data source to the child component's @Param.
  @Local count: number | undefined = 0;

  build() {
    Column() {
      MyComponent({ count: this.count })
      // Modify the union type value to trigger the child component update.
      Button('change')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.count = undefined;
        })
    }
  }
}

@ComponentV2
struct MyComponent {
  // Use @Param to receive the union type variable passed from the parent component.
  @Param count: number | undefined = 0;

  build() {
    Column() {
      Text(`count(${this.count})`)
        .fontSize(30)
        .margin(10)
    }
  }
}