Problem Description
Define the Item class and decorate it with @Observed to make it a deeply observable class. In the parent component, define items as an array of Item objects and render them using a ForEach loop. In the child component, use the @ObjectLink decorator. However, after the properties of the array objects change, the UI rendering does not refresh. The problematic code is as follows:
@Observed
class Item {
name: string = '';
num: number = 0;
}
@Component
@Entry
struct Index {
@State items: Item[] = [
{ name: 'Zhang San', num: 18 },
{ name: 'Li Si', num: 20 },
]
build() {
Column() {
ForEach(this.items, (item: Item) => {
MyItem({ item: item })
})
}
}
}
@Component
struct MyItem {
@ObjectLink item: Item
build() {
Row() {
Text(this.item.name)
Row() {
Button('+').onClick(() => {
this.item.num++
})
Row() {
Text(`${this.item.num}`)
}
.width(50)
.justifyContent(FlexAlign.Center)
Button('-').onClick(() => {
this.item.num--
})
}
}
.margin({
top: 20
})
.justifyContent(FlexAlign.SpaceAround)
.width('100%')
}
}
Background Knowledge
- @Observed/@ObjectLink: Used for observing nested scenarios, such as two-dimensional arrays, object arrays, and nested class scenarios, where deep property changes need to be observed. @Observed and @ObjectLink must be used together.
- makeV1Observed: Wraps an unobservable object into a state-managed V1 observable object, with capabilities equivalent to @Observed, and allows initialization of @ObjectLink.
Troubleshooting Process
For the issue where state variables do not trigger UI updates, you can generally consider the following aspects (other special scenarios require special considerations):
1.The rules governing the decorator's behavior. For @Observed/@ObjectLink, according to the official documentation, a class needs to meet the following conditions to be observable:
- The declared state variables must be created using the new character.
- If this class is a deeply nested class, you need to ensure that the deep-level class containing the observed property is also decorated with @Observed.
- In child components, use
@Propto receive state variables. However, if you modify these variables within the child component, it will not trigger a refresh in the parent component. Instead, you should use@ObjectLinkto receive state variables.
2.Summarize the rules for parameter passing. When passing state variables, if subcomponents use @Builder or @LocalBuilder, it is also important to consider the issues of value passing versus reference passing.
Based on the considerations above, the following checks are made:
1.Verify whether the source data has changed: Print logs after clicking the button to check if the data itself has changed. By comparing the logs, it was found that the data had indeed changed, but it did not trigger a UI rendering refresh.
09-26 10:23:51.944 1748-1748 A03d00/JSAPP com.example.test19 I Zhang San:19
09-26 10:23:52.837 1748-1748 A03d00/JSAPP com.example.test19 I Zhang San:20
09-26 10:23:54.827 1748-1748 A03d00/JSAPP com.example.test19 I Li Si:21
09-26 10:23:55.430 1748-1748 A03d00/JSAPP com.example.test19 I Li Si:22
2.According to the rules of decorator usage, check whether it is used correctly:
- The state variable
itemsis an array object, and the objects within the array are declared using literal notation rather than being created with thenewoperator, which does not conform to the intended usage rules. - Object arrays also fall under a type of deep nesting. The class of the property being modified is decorated with @Observed, which complies with the rules.
- In the problem code, there is no modification of child components involved, and the parent component's display behavior is observed. The child component uses @ObjectLink to receive parameters passed from the parent component, which complies with the rules.
3.The custom builder function @Builder or @LocalBuilder does not exist in the problem code, so this check is ignored.
Analysis Conclusion
In summary, the issue of the UI not refreshing in the code is due to the fact that the new operator was not used when declaring the state variable, causing the UI to fail to update when the data changes.
Solution
- Option 1: Modify the method of creating objects originally done through literals to use the
newkeyword instead. This task can be implemented in theaboutToAppearcallback. The parent component's code is modified as follows:
aboutToAppear(): void {
this.items = this.items.map((item) => {
let cur = new Item()
cur.name = item.name
cur.num = item.num
return cur
})
}
Map each item in the this.items array to a new Item instance, copying the name and num properties of the original item to the new instance. Finally, this.items is updated to this new array.
- Option 2: In state management, to address the issue where certain classes in third-party packages cannot be decorated by the state management decorator, leading to UI not being refreshed, the
makeV1Observedinterface was introduced in version 1 of the state management system to enable deep listening for regular variables. Import UIUtils and modify the code as follows:
@State items: Item[] = [
UIUtils.makeV1Observed({ name: 'Zhang San', num: 18 }),
UIUtils.makeV1Observed({ name: 'Li Si', num: 20 })
]
Limitations or Considerations
- This example supports API Version 19 Release and later versions.
- This example supports HarmonyOS 5.1.1 Release SDK and later versions.
- This example requires DevEco Studio 5.1.1 Release or later for compilation and running.
Top comments (0)