一、前言
最近一直在写HramonyOS,很久没有输出情绪了。今天憋不住了,特定来输出一下。今天来说下List。List在APP里面是必不可少的,在Android中刷新还非常简单的,无论是使用DiffUtil,还是直接notifiDataChange()。
在HarmonyOS中,使用IDataSource进行懒加载,@ObjectLink、@Observed、@State组合进行刷新是必不可少的了。让我们来捋一捋。
如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏
二、喝前准备
1、User
ini
export class User {
name: string = "帕鲁"
age: number = 18
occ: string = "无产阶级"
}
2、UserPage
less
@Entry
@Preview
@Component
struct UserPage {
build() {}
}
3、UserItemComponent
scss
@Component
struct UserItemComponent {
build() {}
}
4、 CommonDataSource
太多啦~,放在文末啦。
三、第一口ForEach
如果你的List
可以直挺挺的显示在页面,不需要考虑性能、大量Item
的情况下,使用ForEach
即可
1、代码如下
typescript
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
可以看到,他希望传入的对象是一个实现了IDataSource的对象。
2、CommonDataSource
在官方示例中随意找个实现了IDataSource的类即可
代码太多,贴个图 (在文章末尾找代码吧~)
3、修改后
scss
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方法。比如下面修改了点击的年龄:
scss
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注解
typescript
@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声明
less
@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,直接修改对象即可。
scss
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';",否则编译会报错。
typescript
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路上的坑,踩了个遍😭😭😭
最后、如果您有任何疑问、对文章写的不满意、发现错误或者有更好的方法,欢迎在评论、私信或邮件中提出,非常感谢您的支持。🙏