鸿蒙--那些年我们踩过的坑(上)
写了这么长时间的ArkTs,一个应用上架了,另外一个应用也快要提交审核了。记录一下自己踩过的坑以及对应的解决方案,大家可以借鉴一下,少走一些弯路。但也不一定,万一我的方法是另外一条弯路嘞~ 不过话又说回来,再过几个版本说不定官方就把这些坑填上了。。
自绘制编辑框
输入验证码的输入框,由于有些样式需要定制,使用TextInput
满足不了需求(有没有一种可能是因为我菜?),于是就按照在自绘编辑框中使用输入法中的介绍,通过InputMethodController.on('insertText')
监听键盘输入, 使用多个Text
来展示输入的验证码,具体文章可以看这里。
写起来也挺简单,带着业务跑了一下也挺好,直到后来这个组件被用到了验证码登录的情景:用户输入手机号,点击获取验证码按钮,请求服务器发送验证码,服务器返回成功,跳转到输入验证码页面,弹起键盘,用户输入验证码。
中规中矩的流程,四四方方的需求。但是,键盘弹起来之后输入的内容不会展示。呵呵哒,在其他业务上表现正常,为啥在这里就不正常?
首先排除控件有bug 首先怀疑控件问题,整个playground工程,精简业务流程后就留下一个输入框,一个按钮,点击按钮后跳转输入验证码页面,这个页面也只留下封装的控件。
简单走一下流程,果然键盘弹出来了,但输入内容后没有展示出来。
一开始以为是监听失败了,debug,加日志后发现是InputMethodController.attach
失败了,报了{"code":12800009}
错误码,查问但发现该错误码是input method client is detached.
当时就麻了:我要进行attach操作,你告诉我已经detached。 emmm
后来发现是其他业务上是点击按钮或者其他操作之后直接发送验证码,然后跳转到输入验证码页面。而登录需要先输入手机号码,再跳转到输入验证码页面。当网络不好的时候,上个页面跳转之前键盘完全收起来,在输入验证码页面再调起键盘就正常。
这里有两个解决方案: 方案一:在控件中延迟一定时间后再进行attach操作 方案二:展示的时候使用TextInput,可以看这里
Foreach循环渲染刷新
列表渲染大部分甚至绝大部分都是使用的ForEach循环渲染,本着磨刀不误砍柴工的原则,先看文档,了解注意事项 ForEach:循环渲染。 文档中提到:在ForEach组件进行非首次渲染时,它会检查新生成的键值是否在上次渲染中已经存在。如果键值不存在,则会创建一个新的组件;如果键值存在,则不会创建新的组件,而是直接渲染该键值所对应的组件。也就是说,我们如果想要刷新某一项,需要改变对应的生成的key,当然也提供了默认的生成规则:
默认的键值生成函数,即(item: Object, index: number) => { return index + '__' + JSON.stringify(item); }。
一般情况下, 默认的生成函数就够用了。
但我还是遇到了坑,这里先放结论:key一旦生成了,再去修改item的属性(假如item是个字面量对象),这个key也不会更新。
这就会遇到一些比较恶心的问题:当我们想要单独刷新某一项时,需要自已定义键值函数,单独改变这一项参与生成键值函数变量的值,比如自定义的键值生成函数(item: Object, index: number) => { return index + '__' + item.id +'__' +item.updateTime ; }
,并且这里的item必须是被@Observed
修饰的class,并且每一个子组件都必须是自定义组件,并且组件内部的数据(也就是这个 item 类型的变量)需要使用@ObjectLink
修饰。 这是因为@State只能观察到简单的数据类型数组数据源变化,对于自定义类,嵌套类,是观察不到的,需要使用@Observed
和@ObjectLink
来实现这种深度观察。
关于这两个装饰器可以看这里 @Observed装饰器和@ObjectLink装饰器:嵌套类对象属性变化
举个例子 先定义一个普通的数据类
class ForeachModel{
name:string=''
defaultSelect: boolean = false
}
页面也很简单,顶部三个按钮操作数据,下面就是一个ForEach循环渲染的列表;自定义的键值生成函数就是index和属性name、属性defaultSelect字符串连接。 列表内容也很简单,一个展示name
属性的Text
组件,一个展示defaultSelect
属性的Checkbox
组件
@Entry
@Component
struct ForeachPage {
@State foreachModelList: ForeachModel[] = []
aboutToAppear(): void {
this.initData()
}
initData() {
this.foreachModelList = []
for (let i = 0; i < 5; i++) {
let model: ForeachModel = new ForeachModel()
model.name = `第 ${i} 项`
if (i == 1) {
model.defaultSelect = true
} else {
model.defaultSelect = false
}
this.foreachModelList.push(model)
}
}
build() {
Column() {
//顶部的三个操作函数
Row() {
Button('重新赋值').onClick((_) => {
this.initData()
})
Button('改变最后一项的值').onClick((_) => {
this.foreachModelList[this.foreachModelList.length-1].name = '改变后的值'
})
Button('添加一项').onClick((_) => {
let model: ForeachModel = new ForeachModel()
model.name = `第 ${this.foreachModelList.length} 项`
this.foreachModelList.push(model)
})
}
ForEach(this.foreachModelList, (model: ForeachModel) => {
Row() {
Text(model.name)
Checkbox().select(model.defaultSelect).onChange((checked: boolean) => {
model.defaultSelect = checked
hilog.error(0x01, 'ForeachPage', `checked ${JSON.stringify(model.name)}`)
})
}
}, (item: ForeachModel, index: number) => {
let key = index + '__' + item.name +'__'+item.defaultSelect;
return key
})
}
.height('100%')
.width('100%')
}
}
gif图中有几个现象:
- 当点击
添加一项
的时候,页面可以刷新,因为@State
能观察到数组长度的变化从而刷新页面。 - 当
添加一项
后,点击重新赋值
,页面可以刷新,因为数组长度发生了变化,而@State
能观察到数组长度的变化从而刷新页面。 - 当点击
改变最后一项的值
时,页面并没有刷新,因为@State
观察不到对象内部属性变化 - 先点击
改变最后一项的值
,再点击添加一项
时,会发现最后一项内容发生了变化,并且新增了一项。因为数组长度发生变化,页面刷新。刷新时发现ForEach子组件的key发生了变化,重新渲染。 - 当点击某项的选择框,改变选中状态后,不管是点击修
改变最后一项的值
,还是添加一项
、重新赋值
都不会刷新其选中状态。
对于第三项,我们只需要使用@Observed
和@ObjectLink
就可以解决: 修改后:
@Component
struct SimpleView{
@ObjectLink model:ForeachModel
build() {
Row() {
Text(this.model.name)
Checkbox().select(this.model.defaultSelect).onChange((checked: boolean) => {
this.model.defaultSelect = checked
hilog.error(0x01, 'ForeachPage', `checked ${JSON.stringify(this.model.name)}`)
})
}
}
}
@Observed
class ForeachModel {
name: string = ''
defaultSelect: boolean = false
}
//循环渲染
ForEach(this.foreachModelList, (model: ForeachModel) => {
SimpleView({model:model})
}, (item: ForeachModel, index: number) => {
let key = index + '__' + item.name +'__'+item.defaultSelect;
return key
})
修改成这种形式之后,点击改变最后一项的值
后,发现内容页面刷新了,但需要注意的是,这是因为@ObjectLink
观察到了类属性的变化,从而刷新了页面,但ForEach子组件的key并没有发生变化,我们可以通过在键值对生成函数中添加日志来验证。 在上面修改后的代码中,点击改变最后一项的值
后,再次点击重新赋值
,页面并没有刷新为初始状态。原因上面也解释了,因为点击改变最后一项的值
后,key并没有发生变化,再次点击重新赋值
后,通过函数生成的key和之前一致,所以页面也不会刷新。
在上面修改后的代码中,上述第五条依然成立:因为点击选择框之后,虽然UI发生了变化,但这种变化是因为点击行为导致的,并不是因为key的改变,也不是因为属性的改变。因此,不管是点击修改变最后一项的值
,还是添加一项
、重新赋值
都不会刷新其选中状态。
奇怪现象
这里还有一个奇怪的现象,也可能是 SDK 的 bug 也不一定: 我们在页面aboutToAppear
这个生命周期函数中调用了一次this.initData()
,页面展示出来之后,点击改变最后一项的值
表现是正常的。 但是如果页面在页面展示出来之后,再点击一次重新赋值
,这时候 子组件是重新渲染了一遍,因为看到键值生成函数中有日志打印。但这时候点击改变最后一项的值
,页面是没有刷新的。怀疑是因为key 没有改变(重新赋值是加载了同样的数据),但实际的对象地址是变化了的,但不知道为啥@ObjectLink
没有观察到。 当我们点击其他按钮,比如增加一项其他操作之后,再点击改变最后一项的值
是有刷新的,这时候再点击重新赋值
,再点击改变最后一项的值
页面是有刷新的。
也就是说,当我们两次或者多次对数组赋值,并且多次赋值使得键值对生成函数生成的 key 是一致的话,@ObjectLink
和@Observed
这两个装饰器会失效。 真让人摸不着头脑
PS:如何知道@ObjectLink
有没有生效。可以加个@Watch
哇,比如这样:
@Watch('onForeachModelChange') @ObjectLink model: ForeachModel
onForeachModelChange(){
hilog.error(0x01, 'ForeachPage', `onForeachModelChange`)
}
- 0回答
- 0粉丝
- 0关注
- 鸿蒙-那些年我们踩过的坑-下
- Ark-TS 语言:鸿蒙生态的高效开发利器,让我们用大白话说一说
- 鸿蒙Flutter实战:02-Windows环境搭建踩坑指南
- 鸿蒙原生开发手记:05-开发之外的那些
- 我眼中的宋江
- 猫哥的2024年终总结
- 鸿蒙开发:一个轻盈的上拉下拉刷新组件
- 鸿蒙-验证码输入框的几种实现方式(上)
- 掌握未来:OpenKyLin 避坑指南
- HarmonyOS Next应用开发实战:广告的使用介绍及避坑指南
- 鸿蒙开发:如何上架一个元服务应用
- HarmonyOS NEXT 地图服务中‘我的位置’功能全解析
- 在OpenHarmony开发者论坛上分享的技术经验的推广渠道
- 我这个鸿蒙项目中的bind文件为什么占用内存那么大,足足131MB
- (二五)ArkCompiler 在不同设备上的优化:编译策略与实践