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
【背景知识】
- 状态管理(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
,但需要注意ForEach
的keyGenerator
问题。 ForEach
在渲染列表时,默认使用索引作为key。当重新赋值数组后,如果新数组与旧数组的项在相同索引位置具有相同内容,ForEach
可能复用组件实例,而不是创建新实例,导致@ObjectLink
未更新。
解决方案
根据鸿蒙文档,解决此问题需确保ForEach
能正确识别数组项的变化,从而触发子组件更新。建议在ForEach
中提供唯一的keyGenerator
,以便当数组变化时,组件能正确重新渲染。
- 修改
ForEach
调用,添加keyGenerator
: 在您的代码中,ForEach
应使用一个唯一键来标识每个项,避免组件复用。例如,使用item.name
和item.age
组合作为key(确保唯一性,实际项目中建议使用唯一ID):
ForEach(this.list, (item: DemoModel) => {
ListItem() {
MyItem({ item: item }).width('100%')
}
}, (item: DemoModel) => item.name + item.age.toString()) // 添加keyGenerator
这样,当数组重新赋值时,ForEach
会根据key的变化决定是否更新组件。
- 确保数据源被正确观测:
DemoModel
已使用@Observed
装饰,正确。- 在
getList()
中创建新数组和 newDemoModel
实例,赋值给@State list
,这会触发父组件刷新,正确。
- 其他注意事项:
- 文档《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
的更新。
如果您的实际数据中有重复的name
和age
,建议为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 刷新。