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
背景知识:
楼主,修改数组对象的字段属性不能使用 @State 更新到对象里面的属性。如图用法
既然不能直接使用@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 || ''
}
}
真机演示:
更多关于HarmonyOS鸿蒙Next中列表数据源是interface类型,无法添加装饰器如何刷新UI的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html
问题原因分析
在您的代码中,数据源 messageList
是 IMessageModel[]
类型,使用 @State
装饰器。@State
只能检测到数组引用的变化(例如整个数组重新赋值),但无法感知数组元素内部属性的变化(例如直接修改 unreadCount
属性)。因为 IMessageModel
是接口类型,您无法直接为接口添加装饰器(如 [@Observed](/user/Observed)
),但实现该接口的类可以被装饰。
解决方案
具体步骤如下:
- 使用
[@Observed](/user/Observed)
装饰实现类:将NormalMessageModel
和SystemMessageModel
类用[@Observed](/user/Observed)
装饰,使它们的属性变化可被监听。 - 创建子组件并使用
[@ObjectLink](/user/ObjectLink)
:将列表项提取为独立组件,并使用[@ObjectLink](/user/ObjectLink)
装饰 item,这样当 item 的属性变化时,子组件会重新渲染。 - 修改点击事件:在点击事件中直接修改对象属性,由于使用了
[@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
- 装饰器限制:ArkTS中
@Observed
/@ObservedV2
无法装饰interface类型,导致无法直接观察数据变化。 - 状态管理机制:
@State
仅能感知数组整体引用变化,无法检测内部对象属性变更。
解决方案
**方案一:改用class
替代interface
**将IMessageModel
改为可被装饰的抽象类
在HarmonyOS鸿蒙Next中,当列表数据源为interface类型时,由于接口无法直接添加装饰器,可采用以下方式实现UI刷新:
- 使用@Observed装饰器包装interface的实现类,并在UI组件中引用该实现类实例
- 通过@ObjectLink或@Prop装饰器在组件层建立响应式关联
- 数据变更时调用this.controller.refresh()触发列表刷新
- 对于ArkUI的ForEach组件,确保数据源id生成器正确实现
在HarmonyOS Next中,当使用interface类型作为列表数据源时,由于interface无法直接添加装饰器,需要采用以下方式实现UI刷新:
-
使用@State装饰器包装数组:你已经正确使用了
[@State](/user/State) messageList: IMessageModel[]
,这确保了数组引用变化时UI会刷新。 -
修改数据时重新赋值数组:问题出现在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刷新
}
})
- 为具体类添加@Observed装饰器(如果需要深度观察):
[@Observed](/user/Observed)
class NormalMessageModel implements IMessageModel {
// ... 现有代码
}
[@Observed](/user/Observed)
class SystemMessageModel implements IMessageModel {
// ... 现有代码
}
关键点是:每次数据变更都要通过重新赋值this.messageList
来触发UI刷新,而不是直接修改数组内的对象属性。