HarmonyOS learning road Ark development framework - learning ArkTS language (state management 2)

@Prop decorator: parent-child one-way synchronization

Variables decorated with @Prop can establish a one-way synchronization relationship with the parent component. Variables decorated with @Prop are mutable, but changes are not synced back to its parent component.

overview

The variable decorated by @Prop establishes a one-way synchronization relationship with the parent component:

  • @Prop variables allow local modification, but the modified changes will not be synchronized back to the parent component.
  • When the data source in the parent component changes, the related @Prop decorated variables are automatically updated. If the child component has locally modified the @Prop decorated related variable value, and the corresponding @State decorated variable in the parent component is modified, the @Prop decorated related variable value locally modified by the child component will be overwritten.

Description of decorator usage rules

@Prop variable decorator

illustrate

decorator parameter

none

Synchronization type

One-way synchronization: The modification of the state variable value of the parent component will be synchronized to the variable decorated by @Prop of the child component, and the modification of the @Prop variable of the child component will not be synchronized to the state variable of the parent component

Variable types that allow decoration

string, number, boolean, enum type.

any is not supported, and undefined and null are not allowed.

Type must be specified.

In the parent component, the value passed to the @Prop decoration cannot be undefined or null, as shown below.

CompA ({ aProp: undefined })

CompA ({ aProp: null })

@Prop and the data source type need to be the same, there are the following three situations (take @State as an example for the data source):

  • The variables decorated by @Prop are of the same type as the state variables of the parent component, namely @Prop : S and @State : S
  • When the state variable of the parent component is an array, the variables decorated by @Prop are of the same type as the array items of the state variable of the parent component, namely @Prop : S and @State : Array<S>
  • When the state variable of the parent component is Object or class, the variable decorated by @Prop is the same as the property type of the state variable of the parent component, that is, @Prop : S and @State : { propA: S }

The initial value of the decorated variable

Allow local initialization.

Explanation of variable transfer/access rules

pass/access

illustrate

Initialize from parent component

Optional if there is initialization locally. If not, it is required. It supports regular variables in the parent component, @State, @Link, @Prop, @Provide, @Consume, @ObjectLink, @StorageLink, @StorageProp, @LocalStorageLink and @LocalStorageProp to initialize the child components @Prop variable.

Used to initialize child components

@Prop supports deinitialization of regular variables, @State, @Link, @Prop, @Provide in subcomponents.

Whether to support access outside the component

Variables decorated with @Prop are private and can only be accessed within the component.

Figure 1  Illustration of initialization rules

 

Observe changes and behavior

observe changes

The following changes can be observed for data decorated with @Prop.

  • When the type of decoration is an allowed type, that is, the assignment changes that can be observed for string, number, boolean, and enum types;
// 简单类型
@Prop count: number;
// 赋值的变化可以被观察到
this.count = 1;

For the synchronous scenario of @State and @Prop:

  • Initializes the @Prop variable in the child component with the value of the @State variable in the parent component. When the @State variable changes, the variable value will also be updated to the @Prop variable synchronously.
  • Modifications to @Prop-decorated variables will not affect the value of its data source @State-decorated variables.
  • In addition to @State, the data source can also be decorated with @Link or @Prop, and the synchronization mechanism for @Prop is the same.
  • The datasource and @Prop variables need to be of the same type.

frame behavior

To understand the @Prop variable value initialization and update mechanism, it is necessary to understand the initial rendering and update process of the parent component and the child component with the @Prop variable.

  1. Initial render:
    1. Executing the build() function of the parent component will create a new instance of the child component and pass the data source to the child component;
    2. Initialize the variables decorated by @Prop of the child component.
  2. renew:
    1. When the child component @Prop is updated, the update only stays in the current child component and will not be synchronized back to the parent component;
    2. When the data source of the parent component is updated, the @Prop decorated variable of the child component will be reset by the data source from the parent component, and all local modifications of the @Prop decoration will be overwritten by the update of the parent component.

scenes to be used

Parent component @State to child component @Prop simple data type synchronization

The following example is a simple data synchronization from @State to child component @Prop. The state variable countDownStartValue of the parent component ParentComponent initializes the count decorated by @Prop in the child component CountDownComponent. Click "Try again". The modification of count is only kept in CountDownComponent and will not be synchronized to the parent. Component CountDownComponent.

The change of the state variable countDownStartValue of ParentComponent will reset the count of CountDownComponent.

@Component
struct CountDownComponent {
  @Prop count: number;
  costOfOneAttempt: number = 1;

  build() {
    Column() {
      if (this.count > 0) {
        Text(`You have ${this.count} Nuggets left`)
      } else {
        Text('Game over!')
      }
      // @Prop装饰的变量不会同步给父组件
      Button(`Try again`).onClick(() => {
        this.count -= this.costOfOneAttempt;
      })
    }
  }
}

@Entry
@Component
struct ParentComponent {
  @State countDownStartValue: number = 10;

  build() {
    Column() {
      Text(`Grant ${this.countDownStartValue} nuggets to play.`)
      // 父组件的数据源的修改会同步给子组件
      Button(`+1 - Nuggets in New Game`).onClick(() => {
        this.countDownStartValue += 1;
      })
      // 父组件的修改会同步给子组件
      Button(`-1  - Nuggets in New Game`).onClick(() => {
        this.countDownStartValue -= 1;
      })

      CountDownComponent({ count: this.countDownStartValue, costOfOneAttempt: 2 })
    }
  }
}

In the example above:

  1. When the CountDownComponent subcomponent is first created, the count variable decorated by @Prop will be initialized from the countDownStartValue variable decorated by the parent component @State;
  2. When the "+1" or "-1" button is pressed, the countDownStartValue value of the @State decoration of the parent component will change, which will trigger the re-rendering of the parent component. During the re-rendering process of the parent component, the UI component using the countDownStartValue state variable will be refreshed and One-way synchronously update the count value in the CountDownComponent subcomponent;
  3. Updating the value of the count state variable will also trigger the re-rendering of the CountDownComponent. During the re-rendering process, the if statement condition (this.count > 0) using the count state variable is evaluated, and the UI component related to the count state variable in the true branch is executed. Description to update the UI display of the Text component;
  4. When the "Try again" button of the subcomponent CountDownComponent is pressed, its @Prop variable count will be changed, but the change of the count value will not affect the countDownStartValue value of the parent component;
  5. When the countDownStartValue value of the parent component changes, the modification of the parent component will overwrite the local modification of count in the child component CountDownComponent.

Parent component @State array item to child component @Prop simple data type synchronization

If @State is decorated in the parent component, its array items can also initialize @Prop. In the following example, the array arr decorated with @State in the parent component Index will initialize its array items to the value decorated with @Prop in the child component Child.

@Component
struct Child {
  @Prop value: number;

  build() {
    Text(`${this.value}`)
      .fontSize(50)
      .onClick(()=>{this.value++})
  }
}

@Entry
@Component
struct Index {
  @State arr: number[] = [1,2,3];

  build() {
    Row() {
      Column() {
        Child({value: this.arr[0]})
        Child({value: this.arr[1]})
        Child({value: this.arr[2]})

        Divider().height(5)

        ForEach(this.arr, 
          item => {
            Child({value: item})
          }, 
          item => item.toString()
        )
        Text('replace entire arr')
        .fontSize(50)
        .onClick(()=>{
          // 两个数组都包含项“3”。
          this.arr = this.arr[0] == 1 ? [3,4,5] : [1,2,3];
        })
      }
    }
  }
}

The initial rendering creates 6 subcomponent instances, and each @Prop decorated variable initialization copies an array item locally. The child component onclick event handler changes the local variable value.

Assuming we clicked multiple times, the local value of all variables is "7".

7
7
7
----
7
7
7

After clicking replace entire arr, the screen will display the following information, why?

3
4
5
----
7
4
5
  • All the modifications made in the child component Child will not be synchronized back to the parent component Index component, so even if all 6 components are displayed as 7, but in the parent component Index, the value saved by this.arr is still [1,2, 3].
  • Click replace entire arr, this.arr[0] == 1 is established, assign this.arr to [3, 4, 5];
  • Because this.arr[0] has changed, the Child({value: this.arr[0]}) component synchronizes the update of this.arr[0] to the instance @Prop decorated variable. The same goes for Child({value: this.arr[1]}) and Child({value: this.arr[2]}).
  • The change of this.arr triggers the ForEach update, and before and after the update of this.arr, there are array items with a value of 3: [3, 4, 5] and [1, 2, 3]. According to the diff mechanism, the array item "3" will be kept, the array items "1" and "2" will be deleted, and the array items "4" and "5" will be added. This means that the component of array item "3" is not regenerated, but moved to the first position. Therefore, the component corresponding to "3" will not be updated. At this time, the value of the component corresponding to "3" is "7", and the final rendering result of ForEach is "7", "4", and "5".

Synchronization from @State class object properties in parent components to @Prop simple types

If a library has a book and two users, each user can mark the book as read without affecting other reader users. Code-wise, local changes to the @Prop book object are not synced to the @State book object in the library component.

class Book {
  public title: string;
  public pages: number;
  public readIt: boolean = false;

  constructor(title: string, pages: number) {
    this.title = title;
    this.pages = pages;
  }
}

@Component
struct ReaderComp {
  @Prop title: string;
  @Prop readIt: boolean;

  build() {
    Row() {
      Text(this.title)
      Text(`... ${this.readIt ? 'I have read' : 'I have not read it'}`)
        .onClick(() => this.readIt = true)
    }
  }
}

@Entry
@Component
struct Library {
  @State book: Book = new Book('100 secrets of C++', 765);

  build() {
    Column() {
      ReaderComp({ title: this.book.title, readIt: this.book.readIt })
      ReaderComp({ title: this.book.title, readIt: this.book.readIt })
    }
  }
}

@Prop local initialization is not synchronized with the parent component

In order to support component reuse scenarios decorated by @Component, @Prop supports local initialization, which makes it optional whether @Prop establishes a synchronization relationship with the parent component. Passing @Prop's datasource from parent to child is optional if and only if @Prop has local initialization.

In the example below, the child component contains two @Prop variables:

  • @Prop customCounter has no local initialization, so the parent component needs to provide a data source to initialize @Prop, and when the data source of the parent component changes, @Prop will also be updated;
  • @Prop customCounter2 has local initialization. In this case, @Prop still allows but does not force the parent component to synchronize the data source to @Prop.
@Component
struct MyComponent {
  @Prop customCounter: number;
  @Prop customCounter2: number = 5;

  build() {
    Column() {
      Row() {
        Text(`From Main: ${this.customCounter}`).width(90).height(40).fontColor('#FF0010')
      }

      Row() {
        Button('Click to change locally !').width(480).height(60).margin({ top: 10 })
          .onClick(() => {
            this.customCounter2++
          })
      }.height(100).width(480)

      Row() {
        Text(`Custom Local: ${this.customCounter2}`).width(90).height(40).fontColor('#FF0010')
      }
    }
  }
}

@Entry
@Component
struct MainProgram {
  @State mainCounter: number = 10;

  build() {
    Column() {
      Row() {
        Column() {
          Button('Click to change number').width(480).height(60).margin({ top: 10, bottom: 10 })
            .onClick(() => {
              this.mainCounter++
            })
        }
      }

      Row() {
        Column()       
          // customCounter必须从父组件初始化,因为MyComponent的customCounter成员变量缺少本地初始化;此处,customCounter2可以不做初始化。
          MyComponent({ customCounter: this.mainCounter })
          // customCounter2也可以从父组件初始化,父组件初始化的值会覆盖子组件customCounter2的本地初始化的值
          MyComponent({ customCounter: this.mainCounter, customCounter2: this.mainCounter })
        }.width('40%')
      }
    }
  }

Guess you like

Origin blog.csdn.net/weixin_47094733/article/details/131951937