HarmonyOS一杯冰美式的时间 -- 列表

2 阅读4分钟

一、前言

最近一直在写HramonyOS,很久没有输出情绪了。今天憋不住了,特定来输出一下。今天来说下List。List在APP里面是必不可少的,在Android中刷新还非常简单的,无论是使用DiffUtil,还是直接notifiDataChange()。

在HarmonyOS中,使用IDataSource进行懒加载,@ObjectLink、@Observed、@State组合进行刷新是必不可少的了。让我们来捋一捋。

如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏

二、喝前准备

1、User

 export class User {
     name: string = "帕鲁"
     age: number = 18
     occ: string = "无产阶级"
 }

2、UserPage

 @Entry
 @Preview
 @Component
 struct UserPage {
     build() {}
 }

3、UserItemComponent

 @Component
 struct UserItemComponent {
     build() {}
 }

4、 CommonDataSource

太多啦~,放在文末啦。

三、第一口ForEach

如果你的List可以直挺挺的显示在页面,不需要考虑性能、大量Item的情况下,使用ForEach即可

1、代码如下

 struct UserPage {
     @State users: User[] = []
 ​
     aboutToAppear(): void {
         //假装有一个请求!
         setTimeout(() => {
             //数据来了!
             this.users = [new User("认真帕鲁"), new User("偷懒帕鲁"), new User("磨洋工帕鲁"), new User("偷懒成瘾帕鲁"), new User("超级黑奴帕鲁")]
         }, 1000)
     }
 ​
     build() {
         if (this.users.length > 0) {
             Column() {
                 List({ space: 10 }) {
                     ForEach(this.users, (item: User, index: number) => {
                         ListItem() {
                             Text(`${index + 1}${item.age}岁的${item.name}`)
                                 .fontColor(Color.Black)
                                 .fontSize(15)
                                 .width('100%')
                                 .textAlign(TextAlign.Center)
                                 .height(50)
                         }.onClick(()=>{
                             this.users.push(new User("聪明帕鲁"))
                         })
                     }, (item: User, index: number) => item.name)
                 }
                 .width('100%')
                 .height('100%')
             }
             .width('100%')
             .height('100%')
         }
     }
 }

使用@State来监听users的长度变化,底层会自动刷新列表。

onClick中,添加了一个“聪明帕鲁”。当你多次触发点击事件,你会发现列表并不会刷新。因为使用了item.name作为key。底层判定为相同的Item。考虑使用 item.name+index

四、第二口LazyForEach

列表不考虑懒加载,一次性加载完的情况毕竟是少数,所以推荐使用LazyForEach。

1、直接将ForEach替换为LazyForEach

image-20240305172321691

可以看到,他希望传入的对象是一个实现了IDataSource的对象。

2、CommonDataSource

在官方示例中随意找个实现了IDataSource的类即可

代码太多,贴个图 (在文章末尾找代码吧~)

image-20240305172515675

3、修改后

 struct UserPage {
     @State users: CommonDataSource<User> = new CommonDataSource()
 ​
     aboutToAppear(): void {
         //假装有一个请求!
         setTimeout(() => {
             //数据来了!
             this.users.setData([new User("认真帕鲁"), new User("偷懒帕鲁"), new User("磨洋工帕鲁"), new User("偷懒成瘾帕鲁"), new User("超级黑奴帕鲁")])
         }, 1000)
     }
 ​
     build() {
         if (this.users.isNotEmpty()) {
             Column() {
                 List({ space: 10 }) {
                     LazyForEach(this.users, (item: User, index: number) => {
                         ListItem() {
                             Text(`${index + 1}只${item.age}岁的${item.name}`)
                                 .fontColor(Color.Black)
                                 .fontSize(15)
                                 .width('100%')
                                 .textAlign(TextAlign.Center)
                                 .height(50)
                         }
                     }, (item: User, index: number) => item.name)
                 }
                 .cachedCount(10)
                 .width('100%')
                 .height('100%')
             }
             .width('100%')
             .height('100%')
         }
 ​
     }
 }

基本没有什么区别。

4、刷新

在复杂的列表中,最好生成一个非常独特的Key,比如JSON.stringify(item),此时修改、添加、删除、位移等操作,需要调用对应的notifyDataChange方法。比如下面修改了点击的年龄:

 LazyForEach(this.users, (item: User, index: number) => {
     ListItem() {
         Text(`${index + 1}只${item.age}岁的${item.name}`)
             .fontColor(Color.Black)
             .fontSize(15)
             .width('100%')
             .textAlign(TextAlign.Center)
             .height(50)
     }.onClick(()=>{
         item.age = index + 1
         this.users.notifyDataChange(index)
     })
 }, (item: User, index: number) => JSON.stringify(item))

五、第三口@ObjectLink

修改数据之后,使用notifyDataChange来刷新,让我们回到了Android 的 notifyDataChange 开发,稍感不适。有没有像DiffUtil一样,系统自己去找不同呢?

@ObjectLink是个好主意,使用起来也很简单;

1、User使用@Observed注解

 @Observed
 export class User {
     name: string = "帕鲁"
     age: number = 18
 ​
     constructor(name: string) {
         this.name = name
         this.age = Math.floor(Math.random() * 101)
     }
 }

2、将ListItem内容使用单独的Commpont声明

注意,将每个Item对应的数据,使用@ObjectLink声明

 @Component
 export struct UserItemComponent {
     @State index: number = 0
     @ObjectLink data: User
     build() {
         Text(`${this.index + 1}只${this.data.age}岁的${this.data.name}`)
             .fontColor(Color.Black)
             .fontSize(15)
             .width('100%')
             .textAlign(TextAlign.Center)
             .height(50)
     }
 }

3、修改后

只粘贴了build代码,因为没有任何变化,主要还是在于User对象的修改和UserItemComponent的抽出。

可以看到onClick,直接修改对象即可。

 build() {
     if (this.users.isNotEmpty()) {
         Column() {
             List({ space: 10 }) {
                 LazyForEach(this.users, (item: User, index: number) => {
                     ListItem() {
                         UserItemComponent({ data: item, index: index })
                     }.onClick(() => {
                         item.age = index + 1
                     })
                 }, (item: User, index: number) => JSON.stringify(item))
             }
             .cachedCount(10)
             .width('100%')
             .height('100%')
         }
         .width('100%')
         .height('100%')
     }
 }

至此,一个简单的列表就好了。

六、喝完:为什么我的ObjectLink不生效

相信有同学遇到过,使用@ObjectLink处理网络请求回来的JSON数据时,修改列表数据后列表不刷新的情况。

其实问题很简单:通过JSON.parse得到的对象并不是通过User构造出的实例,其数据变化无法被观测到,所以不能实现ui刷新

解决方法,说简单,也很复杂....

1、直接遍历所有对象。使用Object相关 + new 重新创建整个对象。

想必你也觉得这种办法很蠢

2、使用三方库 reflect-metadata 和 class-transformer

你需要在接受List对象的地方使用@Type来声明.....

别忘了“import 'reflect-metadata';”,否则编译会报错。

 import { Type } from 'class-transformer'
 import 'reflect-metadata';
 export class UserList {
     @Type (() => User)
     data ?: User[]
 }

在使用的地方:

let reslut : UserList = plainToClass(UserList, jsonString);

蛋疼的是API 11 并不能把plainToClass封装到请求层去,你只能在实际调用的地方使用....蠢炸了

当然你也可以使用一个fill_class的库。如果你有更好的办法,务必一定要告诉我。

目前我知道的就两种办法了。

注意:鸿蒙的注解都只能观察自身构造出来的实例,许多不生效都可能是因为你使用的对象来历不明!

七、总结

实际上我没有TS的学习经验,所以很多思维一直在用Kotlin和Java的思维,踩了很多坑。阿弥陀佛。希望对各位有帮助。

最近在准备考试和适配API11,所以头很大。不过如果是讨论HarmonyOS 之类的问题请留言吧。或者你需要什么功能,尽管告诉我。我应该把一个完全不懂TS的Android到熟练写HarmonyOS路上的坑,踩了个遍😭😭😭

最后、如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏