HarmonyOS鸿蒙Next中列表数据源是interface类型,无法添加装饰器如何刷新UI

HarmonyOS鸿蒙Next中列表数据源是interface类型,无法添加装饰器如何刷新UI 请问这种interface类型作为list数据源,如何刷新ui呢

@Entry
@Component
struct InterfaceListPage {
  @State messageList: IMessageModel[] = []

  aboutToAppear(): void {
    this.messageList.push(new SystemMessageModel('您已开通vip会员', '15:31'))
    for (let index = 1; index < 20; index++) {
      this.messageList.push(new NormalMessageModel(`这是一条消息${index}`, `昵称${index}`, `20:${index}`))
    }
  }

  build() {
    Column() {
      List({ space: 12 }) {
        ForEach(this.messageList, (item: IMessageModel) => {
          ListItem() {
            Row({ space: 10 }) {
              Image(item.getAvatar())
                .width(50)
                .height(50)
                .borderRadius(100)

              Column({ space: 8 }) {
                Text(item.getNickname())
                  .fontSize(14)

                Text(item.getContent())
                  .fontSize(12)
                  .fontColor('#666666')
              }
              .layoutWeight(1)
              .alignItems(HorizontalAlign.Start)

              Column({ space: 8 }) {
                Text(item.getTime())
                  .fontSize(12)
                  .fontColor('#999999')

                Text(item.getUnreadCount() + '')
                  .fontSize(8)
                  .width(15)
                  .borderRadius(100)
                  .height(15)
                  .backgroundColor(Color.Red)
                  .textAlign(TextAlign.Center)
                  .fontColor(Color.White)
                  .visibility(item.getUnreadCount() ? Visibility.Visible : Visibility.None)
              }
            }
            .width('100%')
            .padding(12)
            .constraintSize({ maxWidth: '100%' })
            .margin({ left: 15, right: 15 })
            .backgroundColor(Color.White)
            .borderRadius(10)
            .shadow(ShadowStyle.OUTER_DEFAULT_XS)
            .onClick(() => {
              if (item instanceof SystemMessageModel) {
                item.unreadCount ++
              } else if (item instanceof NormalMessageModel) {
                item.unreadCount ++
              }
              console.log('item = ' , JSON.stringify(item))
            })
          }
        })
      }
      .layoutWeight(1)
    }
    .height('100%')
    .width('100%')
    .backgroundColor('#efefef')
  }
}

interface IMessageModel {
  getContent(): string

  getNickname(): string

  getAvatar(): string

  getTime(): string

  getUnreadCount(): number
}

class NormalMessageModel implements IMessageModel {
  content?: string
  nickname?: string
  avatar: string = 'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3517856700,1330290746&fm=253&gp=0.jpg'
  time?: string
  unreadCount: number = 0

  constructor(content: string, nickname: string, time: string) {
    this.content = content
    this.nickname = nickname
    this.time = time
  }

  getContent(): string {
    return this.content || '暂不支持的消息类型'
  }

  getNickname(): string {
    return this.nickname || '未知对象'
  }

  getAvatar(): string {
    return this.avatar
  }

  getTime(): string {
    return this.time || ''
  }

  getUnreadCount(): number {
    return this.unreadCount
  }
}

class SystemMessageModel implements IMessageModel {
  content?: string
  time?: string
  nickname: string = '系统消息'
  avatar: string = 'https://img.ixintu.com/download/jpg/20201129/eb3d1c2ebd8062f013c740ee3b0da91c_512_512.jpg'
  unreadCount: number = 0

  constructor(content: string, time: string) {
    this.content = content
    this.time = time
  }

  getUnreadCount(): number {
    return this.unreadCount
  }

  getContent(): string {
    return this.content || '暂不支持的消息类型'
  }

  getNickname(): string {
    return this.nickname
  }

  getAvatar(): string {
    return this.avatar
  }

  getTime(): string {
    return this.time || ''
  }
}

更多关于HarmonyOS鸿蒙Next中列表数据源是interface类型,无法添加装饰器如何刷新UI的实战教程也可以访问 https://www.itying.com/category-93-b0.html

8 回复

背景知识:

楼主,修改数组对象的字段属性不能使用 @State 更新到对象里面的属性。如图用法
cke_5847.png

既然不能直接使用@State来更新那就只能找到可以更新对象数据的装饰器了,可以使用@Observed@ObjectLink

@Observed 作用在类属性上,

@ObjectLink 使用在变量需要修改的地方上

注意:列表item也需要抽离出来,因为在@Entry 装饰上的布局不能使用@ObjectLink

问题解决:

参考代码:

[@Entry](/user/Entry)
@Component
struct InterfaceListPage {
    [@State](/user/State) messageList: IMessageModel[] = []

    aboutToAppear(): void {
        this.messageList.push(new SystemMessageModel('您已开通vip会员', '15:31'))
        for (let index = 1; index < 20; index++) {
            this.messageList.push(new NormalMessageModel(`这是一条消息${index}`, `昵称${index}`, `20:${index}`))
        }
    }

    build() {
        Column() {
            List({ space: 12 }) {
                ForEach(this.messageList, (item: IMessageModel) => {
                    ListItem() {
                      //抽离一个item,使用一个新的组件(无[@Entry](/user/Entry))
                        MessageItem({item:item})
                    }
                })
            }
            .layoutWeight(1)
        }
        .height('100%')
        .width('100%')
        .backgroundColor('#efefef')
    }


}

//抽出来的item布局
@Component
struct MessageItem{
    [@ObjectLink](/user/ObjectLink) item:IMessageModel
    build() {
        Row({ space: 10 }) {
            Image(this.item.getAvatar())
                .width(50)
                .height(50)
                .borderRadius(100)

            Column({ space: 8 }) {
                Text(this.item.getNickname())
                    .fontSize(14)

                Text(this.item.getContent())
                    .fontSize(12)
                    .fontColor('#666666')
            }
            .layoutWeight(1)
            .alignItems(HorizontalAlign.Start)

            Column({ space: 8 }) {
                Text(this.item.getTime())
                    .fontSize(12)
                    .fontColor('#999999')

                Text(this.item.getUnreadCount() + '')
                    .fontSize(8)
                    .width(15)
                    .borderRadius(100)
                    .height(15)
                    .backgroundColor(Color.Red)
                    .textAlign(TextAlign.Center)
                    .fontColor(Color.White)
                    .visibility(this.item.getUnreadCount() ? Visibility.Visible : Visibility.None)
            }
        }
        .width('100%')
        .padding(12)
        .constraintSize({ maxWidth: '100%' })
        .margin({ left: 15, right: 15 })
        .backgroundColor(Color.White)
        .borderRadius(10)
        .shadow(ShadowStyle.OUTER_DEFAULT_XS)
        .onClick(() => {
            if (this.item instanceof SystemMessageModel) {
                this.item.unreadCount ++
            } else if (this.item instanceof NormalMessageModel) {
                this.item.unreadCount ++
            }
            console.log('item = ' , JSON.stringify(this.item))
        })
    }
}

interface IMessageModel {
    getContent(): string

    getNickname(): string

    getAvatar(): string

    getTime(): string

    getUnreadCount(): number
}

//在对象上使用 Observed 装饰器
[@Observed](/user/Observed)
class NormalMessageModel implements IMessageModel {
    content?: string
    nickname?: string
    avatar: string = 'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3517856700,1330290746&fm=253&gp=0.jpg'
    time?: string
    unreadCount: number = 0

    constructor(content: string, nickname: string, time: string) {
        this.content = content
        this.nickname = nickname
        this.time = time
    }

    getContent(): string {
        return this.content || '暂不支持的消息类型'
    }

    getNickname(): string {
        return this.nickname || '未知对象'
    }

    getAvatar(): string {
        return this.avatar
    }

    getTime(): string {
        return this.time || ''
    }

    getUnreadCount(): number {
        return this.unreadCount
    }
}

//在对象上使用 Observed 装饰器
[@Observed](/user/Observed)
class SystemMessageModel implements IMessageModel {
    content?: string
    time?: string
    nickname: string = '系统消息'
    avatar: string = 'https://img.ixintu.com/download/jpg/20201129/eb3d1c2ebd8062f013c740ee3b0da91c_512_512.jpg'
    unreadCount: number = 0

    constructor(content: string, time: string) {
        this.content = content
        this.time = time
    }

    getUnreadCount(): number {
        return this.unreadCount
    }

    getContent(): string {
        return this.content || '暂不支持的消息类型'
    }

    getNickname(): string {
        return this.nickname
    }

    getAvatar(): string {
        return this.avatar
    }

    getTime(): string {
        return this.time || ''
    }
}

真机演示:

cke_162412.gif

更多关于HarmonyOS鸿蒙Next中列表数据源是interface类型,无法添加装饰器如何刷新UI的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


问题原因分析

在您的代码中,数据源 messageListIMessageModel[] 类型,使用 @State 装饰器。@State 只能检测到数组引用的变化(例如整个数组重新赋值),但无法感知数组元素内部属性的变化(例如直接修改 unreadCount 属性)。因为 IMessageModel 是接口类型,您无法直接为接口添加装饰器(如 [@Observed](/user/Observed)),但实现该接口的类可以被装饰。

解决方案

具体步骤如下:

  1. 使用 [@Observed](/user/Observed) 装饰实现类:将 NormalMessageModelSystemMessageModel 类用 [@Observed](/user/Observed) 装饰,使它们的属性变化可被监听。
  2. 创建子组件并使用 [@ObjectLink](/user/ObjectLink):将列表项提取为独立组件,并使用 [@ObjectLink](/user/ObjectLink) 装饰 item,这样当 item 的属性变化时,子组件会重新渲染。
  3. 修改点击事件:在点击事件中直接修改对象属性,由于使用了 [@Observed](/user/Observed)[@ObjectLink](/user/ObjectLink),UI 会自动刷新。

代码修改示例

以下是修改后的代码,基于您的原始代码调整:

// 1. 使用 [@Observed](/user/Observed) 装饰实现类
[@Observed](/user/Observed)
class NormalMessageModel implements IMessageModel {
  // ... 其余代码保持不变 ...
}

[@Observed](/user/Observed)
class SystemMessageModel implements IMessageModel {
  // ... 其余代码保持不变 ...
}

// 2. 创建子组件,使用 [@ObjectLink](/user/ObjectLink) 装饰 item
@Component
struct MessageItem {
  [@ObjectLink](/user/ObjectLink) item: IMessageModel // 使用 [@ObjectLink](/user/ObjectLink) 引用对象

  build() {
    Row({ space: 10 }) {
      Image(this.item.getAvatar())
        .width(50)
        .height(50)
        .borderRadius(100)

      Column({ space: 8 }) {
        Text(this.item.getNickname())
          .fontSize(14)

        Text(this.item.getContent())
          .fontSize(12)
          .fontColor('#666666')
      }
      .layoutWeight(1)
      .alignItems(HorizontalAlign.Start)

      Column({ space: 8 }) {
        Text(this.item.getTime())
          .fontSize(12)
          .fontColor('#999999')

        Text(this.item.getUnreadCount() + '')
          .fontSize(8)
          .width(15)
          .borderRadius(100)
          .height(15)
          .backgroundColor(Color.Red)
          .textAlign(TextAlign.Center)
          .fontColor(Color.White)
          .visibility(this.item.getUnreadCount() ? Visibility.Visible : Visibility.None)
      }
    }
    .width('100%')
    .padding(12)
    .constraintSize({ maxWidth: '100%' })
    .margin({ left: 15, right: 15 })
    .backgroundColor(Color.White)
    .borderRadius(10)
    .shadow(ShadowStyle.OUTER_DEFAULT_XS)
    .onClick(() => {
      // 3. 直接修改属性,UI 会自动刷新
      if (this.item instanceof SystemMessageModel) {
        this.item.unreadCount ++
      } else if (this.item instanceof NormalMessageModel) {
        this.item.unreadCount ++
      }
      console.log('item = ', JSON.stringify(this.item))
    })
  }
}

// 主组件保持不变,但 ForEach 中使用子组件
@Entry
@Component
struct InterfaceListPage {
  @State messageList: IMessageModel[] = []

  aboutToAppear(): void {
    this.messageList.push(new SystemMessageModel('您已开通vip会员', '15:31'))
    for (let index = 1; index < 20; index++) {
      this.messageList.push(new NormalMessageModel(`这是一条消息${index}`, `昵称${index}`, `20:${index}`))
    }
  }

  build() {
    Column() {
      List({ space: 12 }) {
        ForEach(this.messageList, (item: IMessageModel) => {
          ListItem() {
            MessageItem({ item: item }) // 使用子组件传递 item
          }
        })
      }
      .layoutWeight(1)
    }
    .height('100%')
    .width('100%')
    .backgroundColor('#efefef')
  }
}

// Interface 定义保持不变
interface IMessageModel {
  getContent(): string
  getNickname(): string
  getAvatar(): string
  getTime(): string
  getUnreadCount(): number
}

关键说明

  • @Observed:装饰类,使类内部属性变化可被监听。必须应用于具体类(如 NormalMessageModel),而不是接口。
  • @ObjectLink:在子组件中装饰变量,用于接收被 [@Observed](/user/Observed) 装饰类的实例。当实例属性变化时,子组件会更新。
  • 为什么这样做:通过将列表项拆分为子组件,并使用 [@ObjectLink](/user/ObjectLink),实现了对每个 item 对象内部属性的精细监听,从而解决 UI 刷新问题。

有点开发不规范导致,修改列表数据源的类型,换成class后使用和@Observed @ObjectLink,使用时需注意foreach或lazyforeach的第三个键值生成函数,如果用默认的会导致修改某一字段造成这个对象全刷数据,如果想只刷某一对象的某一属性,键值生成函数返回数据的id+","+index较为合适

有要学HarmonyOS AI的同学吗,联系我:https://www.itying.com/goods-1206.html

由于interface本身是抽象类型,无法直接使用@Observed@State等状态装饰器,导致属性变更无法触发UI自动刷新

改用可观测的类实现

将interface改为类并用@Observed装饰,通过@ObjectLink触发子组件刷新:

// 修改实现类为可观测
[@Observed](/user/Observed)
class NormalMessageModel implements IMessageModel {
  // 原有属性保持不动...
}

// 子组件中使用[@ObjectLink](/user/ObjectLink)
@Component
struct MessageItem {
  [@ObjectLink](/user/ObjectLink) item: NormalMessageModel

  build() {
    // UI结构与原ListItem内部相同
    Text(this.item.getUnreadCount().toString())
  }
}

// 父组件中ForEach使用新组件
ForEach(this.messageList, (item: IMessageModel) => {
  ListItem() {
    MessageItem({ item: item as NormalMessageModel })
  }
})
  1. 装饰器限制:ArkTS中@Observed/@ObservedV2无法装饰interface类型,导致无法直接观察数据变化。
  2. 状态管理机制@State仅能感知数组整体引用变化,无法检测内部对象属性变更。

解决方案 **方案一:改用class替代interface**将IMessageModel改为可被装饰的抽象类

在HarmonyOS鸿蒙Next中,当列表数据源为interface类型时,由于接口无法直接添加装饰器,可采用以下方式实现UI刷新:

  1. 使用@Observed装饰器包装interface的实现类,并在UI组件中引用该实现类实例
  2. 通过@ObjectLink@Prop装饰器在组件层建立响应式关联
  3. 数据变更时调用this.controller.refresh()触发列表刷新
  4. 对于ArkUI的ForEach组件,确保数据源id生成器正确实现

若使用ViewModel管理状态,可通过@Provide/@Consume装饰器在组件树间传递响应式数据。

在HarmonyOS Next中,当使用interface类型作为列表数据源时,由于interface无法直接添加装饰器,需要采用以下方式实现UI刷新:

  1. 使用@State装饰器包装数组:你已经正确使用了[@State](/user/State) messageList: IMessageModel[],这确保了数组引用变化时UI会刷新。

  2. 修改数据时重新赋值数组:问题出现在onClick方法中直接修改了对象的unreadCount属性。由于对象引用未变,UI不会刷新。正确的做法是:

.onClick(() => {
  // 创建新数组实现数据更新
  const newList = [...this.messageList];
  const index = newList.indexOf(item);
  if (index !== -1) {
    // 创建新对象而不是直接修改属性
    if (item instanceof SystemMessageModel) {
      newList[index] = new SystemMessageModel(item.content!, item.time!);
      newList[index].unreadCount = item.unreadCount + 1;
    } else if (item instanceof NormalMessageModel) {
      newList[index] = new NormalMessageModel(item.content!, item.nickname!, item.time!);
      newList[index].unreadCount = item.unreadCount + 1;
    }
    this.messageList = newList; // 触发UI刷新
  }
})
  1. 为具体类添加@Observed装饰器(如果需要深度观察):
[@Observed](/user/Observed)
class NormalMessageModel implements IMessageModel {
  // ... 现有代码
}

[@Observed](/user/Observed)  
class SystemMessageModel implements IMessageModel {
  // ... 现有代码
}

关键点是:每次数据变更都要通过重新赋值this.messageList来触发UI刷新,而不是直接修改数组内的对象属性。

回到顶部