HarmonyOS鸿蒙Next中@Observed与@ObjectLink使用接口返回的数据时,ui不刷新。

HarmonyOS鸿蒙Next中@Observed@ObjectLink使用接口返回的数据时,ui不刷新。

[@Observed](/user/Observed)
export class DemoModel {
  name: string;
  age: number;
  constructor(name:string,age:number) {
    this.name = name;
    this.age = age
  }

}
interface Info {
  name:string;
  age: number
}

@Component
struct MyItem {
  [@ObjectLink](/user/ObjectLink) item:DemoModel;

  build() {

    Row(){
      Text(this.item.name)
      Text(String(this.item.age))
    }
    .width('100%')
    .padding(20)
    .backgroundColor(Color.Green)
    .justifyContent(FlexAlign.Center)
  }
}

@Entry
@Component
struct Index {
  @State list:DemoModel[] = []


  getList(){
    // 假设data是接口返回的数据,那我想刷列表肯定是这么拿数据然后赋值给this.list
    let data:Info[] =  [{name:'张三',age:22},{name:'李四',age:32}]
    this.list = data.map(item=>{
      return new DemoModel(item.name,item.age)
    })
  }
  aboutToAppear(): void {

   this.getList()
  }
  build() {
    Column() {
      List({space:10}){
        ForEach(this.list,(item:DemoModel)=>{
          ListItem(){
            MyItem({item: item}).width('100%')
          }
        })
      }
      .width('100%')
      .height('80%')

      Column(){

        Button('修改').onClick(()=>{
          this.list[0].name = '张四'
        })
        Button('重新获取').onClick(()=>{
          this.getList()
          console.log('list变化',this.list)
        })
      }


    }
    .height('100%')
    .width('100%')
  }
}

代码如上:操作场景,当我们做了修改操作后,一般会重新向后端请求接口刷新列表。在我的demo里点击修改可以看到list里的第一条数据变了同时ui也刷新了,此时我用点击重新获取按钮模拟再次请求接口拿数据的场景,可以看到list数据已经恢复了,但是ui却没有跟着刷新恢复。这种场景我该怎么让他刷新??


更多关于HarmonyOS鸿蒙Next中@Observed与@ObjectLink使用接口返回的数据时,ui不刷新。的实战教程也可以访问 https://www.itying.com/category-93-b0.html

8 回复

【背景知识】

  • 状态管理(V1): 自定义组件中的变量必须被装饰器装饰才可以成为状态变量,状态变量的改变会引起UI的渲染刷新。其中@State@Prop@Link@Provide和@Consume装饰器都只能观察第一层的变化;@Observed/@ObjectLink配套使用是用于嵌套场景的观察,主要是为了弥补上述装饰器仅能观察一层的能力限制,比如二维数组,或者数组项class,或者class的属性是class。
  • 状态管理(V2): 提供了一套全新的装饰器,@ObservedV2装饰器装饰class,使得被装饰的类具有深度监听的能力,@ObservedV2@Trace配合使用可以使类中的属性具有深度观测的能力,尤其对于二维数组使用V2版本更容易实现数据的监听。
  • ForEach组件进行非首次渲染时,ForEach会根据传入的键值进行匹配,如果没有匹配到相同的键值则会创建一个新的组件,而当键值已存在则不会创建新的组件,转而直接渲染该键值所对应的组件。

【解决方案】

使用状态管理(V2)解决:@ObservedV2@Trace配合使用监听数据变化。

[@ObservedV2](/user/ObservedV2)
export class DemoModel {
  [@Trace](/user/Trace) name: string;
  [@Trace](/user/Trace) age: number;
  constructor(name:string,age:number) {
    this.name = name;
    this.age = age
  }

}
interface Info {
  name:string;
  age: number
}

@ComponentV2
struct MyItem {
  @Param @Require item:DemoModel;

  build() {

    Row(){
      Text(this.item.name)
      Text(String(this.item.age))
    }
    .width('100%')
    .padding(20)
    .backgroundColor(Color.Green)
    .justifyContent(FlexAlign.Center)
  }
}

@Entry
@ComponentV2
struct Index {
  @Local list:DemoModel[] = []


  getList(){
    // 假设data是接口返回的数据,那我想刷列表肯定是这么拿数据然后赋值给this.list
    let data:Info[] =  [{name:'张三',age:22},{name:'李四',age:32}]
    this.list = data.map(item=>{
      return new DemoModel(item.name,item.age)
    })
  }
  aboutToAppear(): void {

    this.getList()
  }
  build() {
    Column() {
      List({space:10}){
        ForEach(this.list,(item:DemoModel)=>{
          ListItem(){
            MyItem({item: item}).width('100%')
          }
        }, (item: DemoModel) => item.name + item.age.toString())
      }
      .width('100%')
      .height('80%')

      Column(){

        Button('修改').onClick(()=>{
          this.list[0].name = '张四'
        })
        Button('重新获取').onClick(()=>{
          this.getList()
          console.log('list变化',this.list[0].name)
        })
      }


    }
    .height('100%')
    .width('100%')
  }
}

【总结】

为了增强状态管理框架对类对象中属性的观测能力,开发者可以使用@ObservedV2装饰器和@Trace装饰器装饰类以及类中的属性。@ObservedV2@Trace提供了对嵌套类对象属性变化直接观测的能力,是状态管理V2中相对核心的能力之一。

更多关于HarmonyOS鸿蒙Next中@Observed与@ObjectLink使用接口返回的数据时,ui不刷新。的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


1.下面是修改后的代码能够实现更新,ArkUI 的 ForEach 可以接收第三个参数作为唯一标识,这样在数组重建时能正确 diff,而不是认为还是同一个对象

参看代码:

@Observed
export class DemoModel {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age
  }
}

interface Info {
  name: string;
  age: number
}

@Component
struct MyItem {
  @ObjectLink item: DemoModel;

  build() {

    Row() {
      Text(this.item.name)
      Text(String(this.item.age))
    }
    .width('100%')
    .padding(20)
    .backgroundColor(Color.Green)
    .justifyContent(FlexAlign.Center)
  }
}

@Entry
@Component
struct FocusTestPage {
  @State list: DemoModel[] = []

  getList() {
    // 假设data是接口返回的数据,那我想刷列表肯定是这么拿数据然后赋值给this.list
    let data: Info[] = [{ name: '张三', age: 22 }, { name: '李四', age: 32 }]
    this.list = data.map(item => {
      return new DemoModel(item.name, item.age)
    })
  }

  aboutToAppear(): void {

    this.getList()
  }

  build() {
    Column() {
      List({ space: 10 }) {
        ForEach(this.list, (item: DemoModel) => {
          ListItem() {
            MyItem({ item: item }).width('100%')
          }
        }, (item: DemoModel) => item.name)
      }
      .width('100%')
      .height('80%')

      Column() {

        Button('修改').onClick(() => {
          this.list[0].name = '张四'
        })
        Button('重新获取').onClick(() => {
          this.getList()
          console.log('list变化', this.list)
        })
      }

    }
    .height('100%')
    .width('100%')
  }
}

在鸿蒙应用中使用@Observed@ObjectLink时遇到重新获取数据后UI不刷新的问题,主要原因是ForEach组件未正确识别数据变化

通过增加唯一性标识(如id字段),帮助框架精准识别数据变化:

[@Observed](/user/Observed)
export class DemoModel {
  id: string = generateUUID(); // 生成唯一ID
  name: string;
  age: number;
  constructor(name:string, age:number) {
    this.name = name;
    this.age = age;
  }
}

// ForEach中指定唯一键值
ForEach(this.list, 
  (item: DemoModel) => item.id, // 以唯一ID作为键值
  (item: DemoModel) => {
    ListItem(){
      MyItem({item: item}).width('100%')
    }
  }
)

原因

当通过 this.list = newData 重新赋值时,若新生成的数组引用与旧数组完全相同,ArkUI 框架无法识别到数据变化。

ForEach 默认使用数组索引作为 Key,当数据内容变化但索引未变时,框架会认为数据未更新,导致 UI 不刷新。

解决方案

第一种方法:强制刷新数组引用。在重新获取数据时,通过扩展运算符或显式创建新数组触发框架更新:

getList() {
  let data: Info[] = [{name:'张三',age:22}, {name:'李四',age:32}];
  // 使用解构创建新数组
  this.list = [...data.map(item => new DemoModel(item.name, item.age))];
}

第二种方法:为数据模型添加唯一标识符。修改数据模型类,添加唯一标识符 id,并在 ForEach 中指定 Key:

@Observed
export class DemoModel {
  id: string = generateUUID(); // 添加唯一标识
  name: string;
  age: number;
  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

// 组件中使用唯一 Key
ForEach(this.list, (item: DemoModel) => {
  ListItem() {
    MyItem({item: item})
  }
}, (item: DemoModel) => item.id) // 指定唯一标识作为 Key

我试了好像不行吧。第一种方法创建新数组,我本身用的map函数返回的已经是新数组了,所以这方法没用;第二种方法,在这我虽然没用指定唯一标识,但默认是index作为key,在这个场景key想当于唯一的,再者,我本地试过增加id属性,还是无效果,

问题分析

您的代码中,DemoModel类已使用@Observed装饰,父组件中的list使用@State装饰,子组件MyItem使用@ObjectLink item: DemoModel。当点击"修改"按钮时,直接修改list[0].name,UI能正常刷新,这是因为@ObjectLink能观测到嵌套对象的属性变化。但当点击"重新获取"按钮时,您通过getList()重新赋值整个list数组(创建新DemoModel实例),数据虽然变化,但UI未刷新。

鸿蒙文档《arkts-observed-and-objectlink.md》中提到:

  • @ObjectLink的数据源更新依赖其父组件。当父组件数据源改变引起父组件刷新时,才会重新设置子组件的@ObjectLink数据源。这个过程不是在数据源变化后立刻发生的,而是在父组件实际刷新时才会进行。
  • 在您的场景中,重新赋值list后,父组件Index会刷新(因为@State装饰的list变化),但子组件MyItem中的@ObjectLink可能没有及时更新,原因可能与ForEach的渲染机制有关。

鸿蒙文档《arkts-state.md》和《properly-use-state-management-to-develope.md》指出:

  • 如果重新赋值时,新创建的对象未被正确观测(例如,未使用@Observed装饰或赋值方式问题),可能导致UI不刷新。您的代码中DemoModel已装饰@Observed,但需要注意ForEachkeyGenerator问题。
  • ForEach在渲染列表时,默认使用索引作为key。当重新赋值数组后,如果新数组与旧数组的项在相同索引位置具有相同内容,ForEach可能复用组件实例,而不是创建新实例,导致@ObjectLink未更新。

解决方案

根据鸿蒙文档,解决此问题需确保ForEach能正确识别数组项的变化,从而触发子组件更新。建议在ForEach中提供唯一的keyGenerator,以便当数组变化时,组件能正确重新渲染。

  1. 修改ForEach调用,添加keyGenerator: 在您的代码中,ForEach应使用一个唯一键来标识每个项,避免组件复用。例如,使用item.nameitem.age组合作为key(确保唯一性,实际项目中建议使用唯一ID):
ForEach(this.list, (item: DemoModel) => {
  ListItem() {
    MyItem({ item: item }).width('100%')
  }
}, (item: DemoModel) => item.name + item.age.toString()) // 添加keyGenerator

这样,当数组重新赋值时,ForEach会根据key的变化决定是否更新组件。

  1. 确保数据源被正确观测
  • DemoModel已使用@Observed装饰,正确。
  • getList()中创建新数组和 new DemoModel实例,赋值给@State list,这会触发父组件刷新,正确。
  1. 其他注意事项
  • 文档《arkts-observed-and-objectlink.md》强调,避免在构造函数中修改被@Observed装饰类的属性(不会触发UI刷新),但您的getList()是在方法中赋值,不属于此问题。
  • 如果问题仍然存在,可以考虑使用@Watch监听list变化并强制刷新,但根据文档,上述方法应能解决。

完整代码修改示例

基于您的代码,修改ForEach部分:

@Entry
@Component
struct Index {
  @State list: DemoModel[] = []

  // getList等方法不变...

  build() {
    Column() {
      List({ space: 10 }) {
        ForEach(this.list, (item: DemoModel) => {
          ListItem() {
            MyItem({ item: item }).width('100%')
          }
        }, (item: DemoModel) => item.name + item.age.toString()) // 添加keyGenerator
      }
      .width('100%')
      .height('80%')
      // 其余代码不变...
    }
    .height('100%')
    .width('100%')
  }
}

为什么这样做?

鸿蒙文档《properly-use-state-management-to-develope.md》中提到,合理使用ForEach的key可以精准控制组件更新范围,避免不必要的刷新。通过提供唯一key,确保当数组项变化时,ForEach能正确识别并更新组件,从而触发@ObjectLink的更新。

如果您的实际数据中有重复的nameage,建议为DemoModel添加唯一标识符(如id),并使用它作为key。

如果问题仍未解决,请检查数据源是否被正确观测或提供更多细节。

@Observed@ObjectLink在接口返回数据时UI不刷新的常见原因是数据未正确触发响应式更新。确保接口返回的数据使用@Observed装饰类封装,且属性被@ObjectLink装饰的变量引用。检查数据赋值方式,需通过修改被@Observed装饰类的属性来触发更新,直接替换整个对象可能无效。确认UI组件正确绑定了@ObjectLink变量。

在您的代码中,@ObjectLink 用于建立对 @Observed 对象的双向绑定,但直接重新赋值 this.list 会导致 @ObjectLink 引用失效,UI 无法刷新。

问题在于 getList() 方法中,您通过 this.list = data.map(...) 创建了新的 DemoModel 实例,但 @ObjectLink 绑定的仍然是旧的对象引用,因此 UI 不会自动更新。

解决方案:使用 this.list.splice(0, this.list.length, ...newItems) 替代直接赋值,保持数组引用不变,仅更新内部元素。这样 @ObjectLink 能够正确检测到变化并刷新 UI。

修改 getList() 方法如下:

getList() {
  let data: Info[] = [{name: '张三', age: 22}, {name: '李四', age: 32}];
  let newItems = data.map(item => new DemoModel(item.name, item.age));
  this.list.splice(0, this.list.length, ...newItems);
}

这样操作可以确保数组引用不变,同时更新内容,触发 UI 刷新。

回到顶部