HarmonyOS鸿蒙Next中循环渲染对象列表,页面无法观察到对象内部的属性变化
HarmonyOS鸿蒙Next中循环渲染对象列表,页面无法观察到对象内部的属性变化 SDK: 6.0.0(20)
操作步骤:进入页面,点击点赞图标,图标变化为’已点赞’或’未点赞’。
相关代码如下:
PostInfo类,使用**@Observed**修饰,isLike变化后,更新点赞图标
[@Observed](/user/Observed)
export class PostInfo {
id: number;
postText: string;
rightCount: number;
answeredCount: number;
timeUsed: number;
createTime: string;
likeCount: number;
nickname: string;
avatarUrl: string;
// 有初始值,才能建立观察通道
@Track isLike: boolean = false;
constructor(id: number, postText: string, rightCount: number, answeredCount: number,
timeUsed: number, createTime: string, likeCount: number,
nickname: string, avatarUrl: string, isLike: boolean) {
this.id = id;
this.postText = postText;
this.rightCount = rightCount;
this.answeredCount = answeredCount;
this.timeUsed = timeUsed;
this.createTime = createTime;
this.likeCount = likeCount;
this.nickname = nickname;
this.avatarUrl = avatarUrl;
this.isLike = isLike;
}
}
API
// 分页获取打卡数据
export function getAllPost(param: PaginationParams): Promise<ApiResponse<PageData<PostInfo>>> {
console.log('start getAllPost');
return instance.get('/word/post/getAll', {params: param})
}
// 点赞
export function like(postId: number): Promise<ApiResponse<null>> {
return instance.get('/word/like/create', {params: {postId: postId}})
}
// 取消点赞
export function cancelLike(postId: number): Promise<ApiResponse<null>> {
return instance.get('/word/like/cancel', {params: {postId: postId}})
}
// 定义统一的API响应结构
export interface ApiResponse<T> {
code: number | string;
message?: string;
data: T;
}
// 定义统一的分页返回类型
export interface PageData<T> {
page: string;
size: string;
records: T[];
total: number;
}
页面组件,获取分页数据,循环渲染。 postInfoList变化时,触发列表更新。 列表项中的PostInfo变量使用**@ObjectLink**修饰,但其中isLike变化时,未观察到点赞图标更新。
@State postInfoList: PostInfo[] = []
@StorageProp('token') @Watch('onTokenChange') token: string = ''
@State isLoadingMore: boolean = true;
scroller: Scroller = new Scroller()
page:number = 1
total:number = 0
async getPostInfoPage() {
const response = await getAllPost({page: this.page, size: 10})
console.log(JSON.stringify(response));
const data: PageData<PostInfo> = response.data
this.total = data.total
// push(...data.records)导致子元素的属性改变后无法被观察到
// this.postInfoList.push(...data.records)
this.postInfoList = [...this.postInfoList, ...data.records]
this.page++
this.isLoadingMore = false
}
List({scroller: this.scroller}) {
ForEach(this.postInfoList, (postInfo: PostInfo) => {
ListItem() {
PostItem({ post: postInfo })
}
}, (postInfo: PostInfo) => postInfo.id.toString())
}
@Component
struct PostItem {
[@ObjectLink](/user/ObjectLink) post: PostInfo;
build() {
Column({ space: 10 })
Row() {
Column() {
Text(this.post.likeCount.toString())
.fontSize(12)
.fontWeight(FontWeight.Medium)
.fontColor(this.post.isLike ? '#3ECBA1' : '#000000')
Image(this.post.isLike ?
$r('app.media.ic_post_like_selected') : $r('app.media.ic_post_like'))
.width(26)
.height(26)
.onClick(async () => {
if (this.post.isLike) {
// 取消点赞
console.log('取消点赞')
let response = await cancelLike(this.post.id)
if (response.code === 200) {
this.post.isLike = false
this.post.likeCount--
this.getUIContext().getPromptAction().showToast({message: '取消点赞成功'})
}
} else {
// 点赞
console.log('点赞')
let response = await like(this.post.id)
if (response.code === 200) {
this.post.isLike = true
this.post.likeCount++
this.getUIContext().getPromptAction().showToast({message: '点赞成功'})
}
}
})
}.width(50)
}.width('100%')
.alignItems(VerticalAlign.Bottom)
}
.padding(10)
.width('90%')
.margin({ top: 10 })
.borderRadius(10)
.shadow({ radius: 20 })
}
}
更多关于HarmonyOS鸿蒙Next中循环渲染对象列表,页面无法观察到对象内部的属性变化的实战教程也可以访问 https://www.itying.com/category-93-b0.html
楼主您好,您这边UI未更新原因是**@ObjectLink修饰的变量,没有通过new的方式实例化,可以看一下下文中的【解决方案】**,已经根据问题描述中的代码完成修改:
【背景知识】
[@Observed和@ObjectLink装饰器](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-observed-and-objectlink):该装饰器可以监测多层嵌套的情况,比如二维数组,或者数组项class中class属性变化,或者class的属性是class中内部class的属性变化。
ForEach循环渲染:在ForEach组件进行非首次渲染时,它会检查新生成的键值是否在上次渲染中已经存在。如果键值不存在,则会创建一个新的组件;如果键值存在,则不会创建新的组件,而是直接渲染该键值所对应的组件。详情参考:组件创建规则。
【问题定位】
List组件内UI不刷新的问题可能有以下几种情况:
- @State无法观察到对象数组中对象属性的变化的这样也会导致UI无法刷新,需要使用@Observed和@ObjectLink进行修饰。
- @ObjectLink修饰的变量,没有通过new的方式实例化。
- 根据知识背景中的组件创建规则,数据源修改后,如果监测方法正确,ForEach中键值对的值也会跟着修改,但是如果ForEach内keyGenerator(代表键值对中的键)没有更新也会导致无法刷新UI,ForEach参数介绍详见:ForEach参数介绍。
- @ObjectLink修饰的变量含有嵌套类对象,组件改变嵌套对象的属性,无法刷新UI。因为@ObjectLink仅能观察其代理的属性,无法观察到代理属性的嵌套对象属性。参考:复杂嵌套对象属性更改失效。
【解决方案】
楼主目前UI未触发更新的场景属于第二种现象:@ObjectLink修饰的变量,没有通过new的方式实例化。 建议通过new实例化后再处理,我这边根据您的代码进行修改,能够正常更新点赞,您可以看下:
[@Observed](/user/Observed)
export class PostInfo {
id: number;
postText: string;
@Track likeCount: number;
// 有初始值,才能建立观察通道
@Track isLike: boolean = false;
constructor(id: number, postText: string, isLike: boolean, likeCount: number) {
this.id = id;
this.postText = postText;
this.isLike = isLike;
this.likeCount = likeCount;
}
}
// 在设置页面中使用
@Entry
@Component
struct shareTestComponent {
[@State](/user/State) postInfoList: PostInfo[] = []
[@State](/user/State) isLoadingMore: boolean = true;
scroller: Scroller = new Scroller()
page: number = 1
total: number = 0
async getPostInfoPage() {
this.postInfoList = [new PostInfo(1122, '测试点赞', false, 0), new PostInfo(1123, '测试点赞2', false, 0)]
}
aboutToAppear(): void {
this.getPostInfoPage()
}
build() {
Column() {
List({ scroller: this.scroller }) {
ForEach(this.postInfoList, (postInfo: PostInfo) => {
ListItem() {
PostItem({ post: postInfo })
}
}, (postInfo: PostInfo) => postInfo.id.toString())
}
}
}
}
@Component
struct PostItem {
[@ObjectLink](/user/ObjectLink) post: PostInfo;
build() {
Column({ space: 10 }) {
Row() {
Text(this.post.likeCount.toString())
.fontSize(12)
.fontWeight(FontWeight.Medium)
.fontColor(this.post.isLike ? '#3ECBA1' : '#000000')
Column() {
Image(this.post.isLike ?
$r('app.media.video_create_gif_preview3') : $r('app.media.video_create_gif_preview5'))
.width(26)
.height(26)
.onClick(async () => {
if (this.post.isLike) {
this.post.isLike = false;
this.post.likeCount--
// 取消点赞
this.getUIContext().getPromptAction().showToast({ message: '取消点赞成功' })
} else {
// 点赞
console.log('点赞')
this.post.isLike = true
this.post.likeCount++
this.getUIContext().getPromptAction().showToast({ message: '点赞成功' })
}
})
}.width(50)
}.width('100%')
.alignItems(VerticalAlign.Bottom)
}
.padding(10)
.width('90%')
.margin({ top: 10 })
.borderRadius(10)
.shadow({ radius: 20 })
}
}
其它UI不刷新的问题具体修改方式如下,也可以一并参考:
【分析结论】
- 建议将ForEach的最后一个参数缺省,系统会默认设置键值中的键。
- 使用@Observed和@ObjectLink进行修饰。
- @ObjectLink修饰的变量通过new的方式实例化。
- @ObjectLink修饰的变量含有嵌套类对象,需要给子组件单独传递该嵌套类对象,子组件中new一个用@ObjectLink修饰的嵌套类对象来接收。
【修改建议】 场景一:@Observed结合@ObjectLink观察嵌套类对象属性变化。 可以参考如下实现方式:
[@Observed](/user/Observed)
class TestList {
text: string;
isFlag: boolean;
constructor(text: string, isFlag: boolean) {
this.text = text;
this.isFlag = isFlag;
}
}
@Entry
@Component
struct DisplayTest {
[@State](/user/State) ListArray: Array<TestList> = [new TestList('第一条数据:', false), new TestList('第二条数据:', true)]
build() {
Column({ space: 20 }) {
Text('测试:')
.width('100%')
Button('点击修改第一条数据的值!')
.type(ButtonType.Capsule)
.onClick(() => {
this.ListArray[0].isFlag = !this.ListArray[0].isFlag
})
.width('50%')
Button('点击新增加数据!')
.type(ButtonType.Capsule)
.onClick(() => {
this.ListArray.push(new TestList('新增:', false))
})
.width('50%')
Scroll() {
Column() {
ForEach(this.ListArray, (item: TestList) => {
Test001({ test: item })
.height(72)
})
}
}
.align(Alignment.TopStart)
.layoutWeight(1)
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
@Component
struct Test001 {
[@ObjectLink](/user/ObjectLink) test: TestList
build() {
Row() {
Text(this.test.text + this.test.isFlag)
}
.justifyContent(FlexAlign.Center)
.height(100)
.width('100%')
}
}
场景二:@ObjectLink修饰的变量含有嵌套类对象,将该嵌套类对象单独传递给子组件。 可以参考如下实现方式:
@Entry
@Component
struct Index {
[@State](/user/State) listArray: Person[] = []
aboutToAppear(): void {
this.initData()
}
initData() {
const p1 = new Person()
p1.name = "p1"
p1.id = "id1"
const c1 = new Child()
c1.name = "c1"
c1.id = "cid1"
const goods = new GoodsModel()
goods.goodName = "大米"
c1.goods = goods
const c2 = new Child()
c2.name = "c2"
c2.id = "cid2"
p1.children = [c1, c2]
this.listArray = [p1]
}
build() {
Column() {
Button("更改数据")
.fontSize(20)
.margin({
top: 50, bottom: 50
})
.onClick(() => {
this.listArray.forEach((person: Person) => {
person.children?.forEach((child: Child) => {
if (child.id == "cid1") {
if (!!child.goods) {
child.goods.goodName = "大米_002" // 修改嵌套类对象属性
}
}
})
})
})
this.contentList()
}
.justifyContent(FlexAlign.Start)
.width("100%")
.height("100%")
}
@Builder
contentList() {
List() {
ForEach(this.listArray, (person: Person) => {
ListItem() {
List() {
ForEach(person.children, (child: Child) => {
ListItem() {
ChildrenItemCard({
child: child,
goods: child.goods
})
}
})
}
}
}, (person: Person) => person.id)
}
}
}
[@Observed](/user/Observed)
export class Person {
name?: string
age: number = 0
id: string = ""
children?: Child[]
}
[@Observed](/user/Observed)
export class Child {
name?: string
age: number = 0
id: string = ""
goods?: GoodsModel
}
[@Observed](/user/Observed)
export class GoodsModel {
goodName?: string
}
@Component
export struct ChildrenItemCard {
[@ObjectLink](/user/ObjectLink) @Watch("updateModel") child: Child
[@ObjectLink](/user/ObjectLink) goods: GoodsModel // 将嵌套类对象单独传递给子组件,不然无法观测到其属性变化
updateModel() {
console.debug("xx")
}
build() {
Column() {
Row() {
Text("child.name:")
Text(this.child.name ?? "")
}
.justifyContent(FlexAlign.Center)
.width("90%")
.height(30)
Row() {
Text("child.good.goodName:")
Text(this.goods?.goodName ?? "")
}
.justifyContent(FlexAlign.Center)
.width("90%")
.height(30)
}
}
}
更多关于HarmonyOS鸿蒙Next中循环渲染对象列表,页面无法观察到对象内部的属性变化的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
在HarmonyOS Next中,使用@State或@Observed装饰器管理对象列表时,若对象内部属性变化未触发UI更新,是因为装饰器仅监测对象引用变化。
需使用@Observed装饰对象类,并用@ObjectLink装饰组件内引用该对象的变量,或使用@Track装饰对象内需监听的属性。
例如:
[@Observed](/user/Observed) class Item {
[@Track](/user/Track) name: string = '';
}
组件中使用[@ObjectLink](/user/ObjectLink) item: Item。
这样对象内部属性变更时,UI可同步更新。
这个问题是由于在 PostItem 组件中直接修改 @ObjectLink 装饰的 post 对象的属性时,状态管理机制未能正确触发UI更新。在HarmonyOS Next中,@ObjectLink 装饰的变量需要遵循特定的更新规则。
核心问题:@ObjectLink 装饰的变量虽然可以观察到对象内部属性的变化,但直接修改其属性(如 this.post.isLike = false)可能不会触发UI重新渲染。
解决方案:需要确保对 @Observed 类实例的属性修改是通过 重新赋值整个对象 或 使用可观察的更新方式 来实现。
以下是修改建议:
- 在
PostInfo类中添加一个更新方法:
@Observed
export class PostInfo {
// ... 其他属性
updateLikeStatus(isLike: boolean): void {
this.isLike = isLike;
this.likeCount = isLike ? this.likeCount + 1 : this.likeCount - 1;
}
}
- 修改
PostItem组件中的点击事件处理:
.onClick(async () => {
const newLikeStatus = !this.post.isLike;
if (newLikeStatus) {
// 点赞
let response = await like(this.post.id);
if (response.code === 200) {
this.post.updateLikeStatus(true);
}
} else {
// 取消点赞
let response = await cancelLike(this.post.id);
if (response.code === 200) {
this.post.updateLikeStatus(false);
}
}
})
- 或者,在父组件中维护状态更新: 如果上述方法仍不生效,可以考虑在父组件中维护状态更新:
// 在父组件中添加更新方法
updatePostLikeStatus(postId: number, isLike: boolean): void {
const index = this.postInfoList.findIndex(item => item.id === postId);
if (index !== -1) {
// 创建新对象替换原对象
const updatedPost = { ...this.postInfoList[index] };
updatedPost.isLike = isLike;
updatedPost.likeCount = isLike ? updatedPost.likeCount + 1 : updatedPost.likeCount - 1;
// 替换数组中的对象
this.postInfoList = [
...this.postInfoList.slice(0, index),
updatedPost,
...this.postInfoList.slice(index + 1)
];
}
}
然后在 PostItem 中通过回调调用父组件的方法。
关键点:
@ObjectLink装饰的变量需要确保对对象属性的修改能够被状态管理系统检测到- 对于复杂对象,推荐使用方法封装属性更新
- 如果直接属性赋值不生效,考虑创建新对象替换原对象
这种模式确保了状态变化的可观察性,能够正确触发UI更新。

