目录

@Param装饰器:组件外部输入

为了增强子组件接受外部参数输入的能力,开发者可以使用@Param装饰器。

@Param不仅可以接受组件外部输入,还可以接受@Local的同步变化。在阅读本文档前,建议提前阅读:@Local。

从API version 12开始,在@ComponentV2装饰的自定义组件中支持使用@Param装饰器。

从API version 12开始,该装饰器支持在元服务中使用。

从API version 23开始,该装饰器支持在ArkTS卡片中使用。

概述

@Param表示组件从外部传入的状态,使得父子组件之间的数据能够进行同步:

状态管理V1版本接受外部传入的装饰器的局限性

状态管理V1存在多种可接受外部传入的装饰器,常用的有@State、@Prop、@Link、@ObjectLink。这些装饰器使用有限制且不易区分,不当使用会导致性能问题。

@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 {
  // 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%')
  }
}

在上面的示例中,@State仅能在初始化时接收info的引用,改变info之后无法同步。@Prop虽然能够进行单向同步,但是对于较复杂的类型来说,深拷贝性能较差。@Link能够接受传入的引用进行双向同步,但它必须要求数据源也是状态变量,因此无法接受info中的成员属性region。@ObjectLink能够接受类成员属性,但是要求该属性类型必须为@Observed装饰的类。装饰器的不同限制使得父子组件之间的传值规则复杂、不易使用。因此推出@Param装饰器,表示组件从外部传入的状态。

装饰器说明

@Param变量装饰器 说明
装饰器参数 无。
能否本地修改 否。若需要修改值,可使用@Param搭配@Once修改子组件的本地值。或通过@Event装饰器,修改@Param数据源的值。
同步类型 由父到子单向同步。
允许装饰的变量类型 Object、class、string、number、boolean、enum等基本类型以及Array、Date、Map、Set等内嵌类型。支持null、undefined以及联合类型。
被装饰变量的初始值 允许本地初始化,若不在本地初始化,则需要和@Require装饰器一起使用,要求必须从外部传入初始化。

变量传递

传递规则 说明
从父组件初始化 @Param装饰的变量允许本地初始化,若无本地初始化则必须从外部传入初始化。当同时存在本地初始值与外部传入值时,优先使用外部传入值进行初始化。
初始化子组件 @Param装饰的变量可以初始化子组件中@Param装饰的变量。
同步 @Param可以和父组件传入的状态变量数据源(即@Local或@Param装饰的变量)进行同步,当数据源发生变化时,会将修改同步给子组件的@Param。

观察变化

使用@Param装饰的变量具有被观测变化的能力。当装饰的变量发生变化时,会触发该变量绑定的UI组件刷新。

限制条件

@Param装饰器存在以下使用限制:

使用场景

[h2]从父组件到子组件变量传递与同步

@Param能够接受父组件@Local或@Param传递的数据并与之变化同步。

@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 {
  // 使用@Local装饰infoList数组,作为数据源传递给子组件的@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 })
      })
      // 修改数组元素及对象属性,触发MiddleComponent和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 {
  // 使用@Param接收父组件传入的Info对象,数据源变化时触发子组件更新。
  @Require @Param info: Info;

  build() {
    Column() {
      Text(`name: ${this.info.name}`)
        .fontSize(20)
        .margin(10)
      Text(`age: ${this.info.age}`)
        .fontSize(20)
        .margin(10)
      // 将Region对象继续传递给子组件的@Param。
      SubComponent({ region: this.info.region })
    }
    .width('100%')
  }
}

@ComponentV2
struct SubComponent {
  // @Param接收父组件传入的Region对象,数据源变化时触发子组件更新。
  @Require @Param region: Region;

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

[h2]装饰Array类型变量

@Param装饰Array类型变量,可以观察到数据源对Array整体的赋值,以及调用Array的接口push, pop, shift, unshift, splice, copyWithin, fill, reverse, sort带来的变化。

@ComponentV2
struct Child {
  // 使用@Param接收父组件传入的Array类型变量。
  @Require @Param count: number[];

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

@Entry
@ComponentV2
struct Index {
  // 使用@Local装饰Array类型变量,作为数据源传递给子组件的@Param。
  @Local count: number[] = [1, 2, 3];

  build() {
    Row() {
      Column() {
        Child({ count: this.count })
        // 对数组整体重新赋值,触发子组件更新。
        Button('init array')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.count = [9, 8, 7];
          })
        // 新增数组元素,触发子组件更新。
        Button('push')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.count.push(0);
          })
        // 翻转数组元素,触发子组件更新。
        Button('reverse')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.count.reverse();
          })
        // 使用同一元素填充数组,触发子组件更新。
        Button('fill')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.count.fill(6);
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

[h2]装饰Date类型变量

@Param装饰Date类型变量,可以观察到数据源对Date整体的赋值,以及调用Date的接口setFullYear, setMonth, setDate, setHours, setMinutes, setSeconds, setMilliseconds, setTime, setUTCFullYear, setUTCMonth, setUTCDate, setUTCHours, setUTCMinutes, setUTCSeconds, setUTCMilliseconds带来的变化。

@ComponentV2
struct DateComponent {
  // 使用@Param接收父组件传入的Date类型变量。
  @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 {
  // 使用@Local装饰Date类型变量,作为数据源传递给子组件的@Param。
  @Local parentSelectedDate: Date = new Date('2021-08-08');

  build() {
    Column() {
      // 对Date类型变量整体重新赋值,触发子组件更新。
      Button('parent update the new date')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.parentSelectedDate = new Date('2023-07-07');
        })
      // 调用Date的setFullYear方法修改年份,触发子组件更新。
      Button('increase the year by 1')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.parentSelectedDate.setFullYear(this.parentSelectedDate.getFullYear() + 1);
        })
      // 调用Date的setMonth方法修改月份,触发子组件更新。
      Button('increase the month by 1')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.parentSelectedDate.setMonth(this.parentSelectedDate.getMonth() + 1);
        })
      // 调用Date的setDate方法修改日期,触发子组件更新。
      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]装饰Map类型变量

@Param装饰Map类型变量,可以观察到数据源对Map整体的赋值,以及调用Map的接口set, clear, delete带来的变化。

@ComponentV2
struct Child {
  // 使用@Param接收父组件传入的Map类型变量。
  @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 {
  // 使用@Local装饰Map类型变量,作为数据源传递给子组件的@Param。
  @Local message: Map<number, string> = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);

  build() {
    Row() {
      Column() {
        Child({ value: this.message })
        // 对Map整体重新赋值,触发子组件更新。
        Button('init map')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message = new Map([[0, 'a'], [1, 'b'], [3, 'c']]);
          })
        // 新增键值对,触发子组件更新。
        Button('set new one')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message.set(4, 'd');
          })
        // 清空Map,触发子组件更新。
        Button('clear')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message.clear();
          })
        // 更新键值对,触发子组件更新。
        Button('replace the first one')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message.set(0, 'aa');
          })
        // 删除键值对,触发子组件更新。
        Button('delete the first one')
          .width(300)
          .margin(10)
          .onClick(() => {
            this.message.delete(0);
          })
      }
      .width('100%')
    }
    .height('100%')
  }
}

[h2]装饰Set类型变量

@Param装饰Set类型变量,可以观察到数据源对Set整体的赋值,以及调用Set的接口add, clear, delete带来的变化。

@ComponentV2
struct Child {
  // 使用@Param接收父组件传入的Set类型变量。
  @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 {
  // 使用@Local装饰Set类型变量,作为数据源传递给子组件的@Param。
  @Local message: Set<number> = new Set([0, 1, 2, 3, 4]);

  build() {
    Row() {
      Column() {
        Child({ message: this.message })
        // 对Set整体重新赋值,触发子组件更新。
        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);
          })
        // 清空Set,触发子组件更新。
        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%')
    }
    .height('100%')
  }
}

[h2]联合类型

@Param支持null、undefined以及联合类型。以下示例中,count类型为number | undefined,点击改变count的类型时,UI会自动刷新。

@Entry
@ComponentV2
struct Index {
  // 使用@Local装饰联合类型变量,作为数据源传递给子组件的@Param。
  @Local count: number | undefined = 0;

  build() {
    Column() {
      MyComponent({ count: this.count })
      // 修改联合类型值,触发子组件更新。
      Button('change')
        .width(300)
        .margin(10)
        .onClick(() => {
          this.count = undefined;
        })
    }
  }
}

@ComponentV2
struct MyComponent {
  // 使用@Param接收父组件传入的联合类型变量。
  @Param count: number | undefined = 0;

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