在公司小程序也开发了一段时间了,中间遇到过很多问题,特此记录几个比较典型的问题和解决方案。
一、textarea 的高层级问题
此问题提供源码 demo,可导入微信开发者工具查看。
症状(表现)
textarea 是小程序的原生组件,它的一个表现就是优先级很高,这导致了一些困扰,比如我们有一个表单页面,最下面就是一个 textarea 和一个保存按钮,这会导致 textarea 的文字会浮现在按钮上。如下图:
它最大的问题时会导致保存按钮可能点击无效或者会弹出键盘,并且开发者工具模拟器和真机表现不一样,这真是个坑!
诊断(实验)
模拟器中,针对 position:fixed
定位的按钮,我们加一个 z-index:10
即可, z-index
等于多少合适不清楚,试了等于 1 是不行的,10 就可以,其余的值没试过。
.submit-cls {
position: fixed;
left: 30px;
right: 30px;
bottom: 300px;
text-align: center;
background-color: green;
color: #fff;
z-index: 10;
}
模拟器中的表现:
然儿,真机上(Android)依然无效!如下图:
于是我想到了 cover-view 标签,cover-view 是微信提供的一个原生组件,它是覆盖在原生组件之上的文本视图,可覆盖的原生组件包括 map、video、canvas、camera、live-player、live-pusher 之上,只支持嵌套 cover-view、cover-image,可在 cover-view 中使用 button。
用 cover-view 标签包裹 button 如何呢?郁闷的事情发生了,真机上按钮不见了!。。。这方法貌似不行。。
<cover-view>
<button class="submit-cls" id="button" bindtap="onClick">
Button z-index: 10
</button>
</cover-view>
那我直接用 cover-view 标签作为按钮呢?
<cover-view
class="cover-view-clas"
id="cover-view"
bindtap="onClick"
>
cover-view z-index: 10
</cover-view>
.cover-view-clas {
position: fixed;
height: 40px;
line-height: 40px;
left: 30px;
right: 30px;
bottom: 250px;
text-align: center;
background-color: orangered;
color: #fff;
}
结果在模拟器里不行
但是真机上表现很好。于是我也加了一个 z-index: 10
,这样模拟器和真机表现就一致。
药方(总结)
综上所述,要解决这个问题似乎只有一个办法,那就是用 cover-view
+ z-index:10
,然儿这样会导致一个的副作用,没法使用微信的开放能力比如 open-type
。
二、setData 优化
我们知道,与传统的浏览器 Web 页面最大区别在于,小程序的是基于 双线程 模型的,在这种架构中,小程序的渲染层使用 WebView 作为渲染载体,而逻辑层则由独立的 JsCore 线程运行 JS 脚本,双方并不具备数据直接共享的通道,因此渲染层和逻辑层的通信要由 Native 的 JSBrigde 做中转。
每当小程序视图数据需要更新时,逻辑层会调用小程序宿主环境提供的 setData
方法将数据从逻辑层传递到视图层,经过一系列渲染步骤之后完成 UI 视图更新。然而当 setData
传递大量的新数据、频繁的执行 setData
操作、过多的页面节点数时会影响渲染性能。
区分数据类别
意思是, setData 只用来通知页面更新,只有需要通知页面更新的时候,页面引用了某个 data 字段时才使用,其它字段数据我们有时候可能只是为了让 js 方便使用。比如如下数据
data: {
form: {
name: 'xxxx',
... ...
},
index: 0
}
假如 页面上根本没用到 index 来展示,只是我们的逻辑变量,那么我们在赋值的时候就直接 this.data.index = xxx
即可,不要用 setData 去赋值了。
合理利用局部更新
setData 是支持使用 数据路径 的方式对对象的局部字段进行更新,我们可能会遇到这样的场景: list 列表是从后台获取的数据,并展示在页面上,当 list 列表的第一项数据的 src 字段需要更新时,一般情况下我们会从后台获取新的 list 列表,执行 setData 更新整个 list 列表。
// 后台获取列表数据
const list = requestSync();
// 更新整个列表
this.setData({ list });
实际上,只有个别字段需要更新时,我们可以这么写来避免整个 list 列表更新:
// 后台获取列表数据
const list = requestSync();
// 局部更新列表
this.setData({
"list[0].src": list[0].src,
});
善用自定义组件
小程序自定义组件的实现是由小程序官方设计的 Exparser 框架所支持,框架实现的自定义组件的组件模型与 Web Components 标准的 Shadow DOM 相似:
在页面引用自定义组件后,当初始化页面时,Exparser 会在创建页面实例的同时,也会根据自定义组件的注册信息进行组件实例化,然后根据组件自带的 data 数据和组件 WXML,构造出独立的 Shadow Tree ,并追加到页面 Composed Tree 。创建出来的 Shadow Tree 拥有着自己独立的逻辑空间、数据、样式环境及 setData 调用,这是组件化带来的好处。
基于自定义组件的 Shadow DOM 模型设计,我们可以将页面中一些需要高频执行 setData 更新的功能模块(如倒计时、进度条等)封装成自定义组件嵌入到页面中。当这些自定义组件视图需要更新时,执行的是组件自己的 setData ,新旧节点树的对比计算和渲染树的更新都只限于组件内有限的节点数量,有效降低渲染时间开销。
三、大表单交互的一点实践经验
在项目中,有一个预约模块,字段忒多,保险业务嘛,需要用户填写各种数据的,为了用户体验拆成了多个步骤,如图
一开始,业务上要求切换 tab 的时候数据要缓存,跟 Vue 的 keep-alive 一样,但是小程序没有这样的机制,所以利用小程序的 hidden
属性,也就是 Vue 中的 v-show
,组件始终会被渲染,只是简单的控制显示与隐藏。关于 wx:if 和 hidden。
这样的导致页面节点太多,在低性能手机上会出现卡死的现象,直接无法渲染或者渲染太慢。
后来改为 wx:if
来切换
<view wx:if="{{current === 0}}">......</view>
<view wx:if="{{current === 1}}">......</view>
<view wx:if="{{current === 2}}">......</view>
... ...
这样以来一次渲染节点太多的问题解决了,但是怎么实现 tab 切换的时候输入的内容杯缓存呢?其实我们的笨办法就是切换的时候把前一个表单内容保存到 localStorage 或 gloabData 中,切换回去的时候再取出来填充,这中间会有一个明显的渲染过程,肉眼可见,没办法,目前只能牺牲一点点体验了。
对于这种大型表单,数据处理和逻辑交互的时候要非常注意,很容易出现性能问题。
这次就说这么多吧,文章如有什么错误,或有什么想法,请留言,不吝赐教!
全文完。
赞,这两点写的非常好