微信小程序之长表单页面优化
1、前言
长表单录入信息在业务系统中是非常常见的一个场景,然而长表单加业务系统,那流畅度,简直不忍直视。所以趁着现在空闲,想了一些关于长表单页面的优化方案。
2、优化方案一: 减少绑定方法
我们平时写小程序页面的时候,可能会不太注意,会有这样的写法。
<!-- html页面 -->
<view class='content2'>
<view class='item'>
<text class='item_title'>姓名</text>
<input type='text' value ='{{name}}' bindinput ="getName"></input>
</view>
<view class='item'>
<text class='item_title'>手机号</text>
<input type='text' value ='{{phone}}' bindinput ="getPhone"></input>
</view>
</view>
// js页面如下
getName(e){
this.setData({
name : e.detail.value
})
},
getPhone(e){
this.setData({
name: e.detail.value
})
}
// 其他 ....
表单少的时候还好,如果你想一下业务系统中,有些页面有2/3十个字段呢?难道每个input都给一个相应方法吗?
不仅代码重复多,而且js页面中定义的方法过多占用内存、占用体积、而且还导致代码多阅读困难等,而且哪一天如果要对输入的内容做检查处理,就算可以把检查的代码抽象出来,难道还要调用二三十次?
当然,上一种方法也不是没有优点,优点就是:职责单一,哪天不小心把某个方法改bug也不影响其他的使用。
但从性能方面考虑,我们就可以对某一类相似的输入方法就行优化,绑定同一个方法,像下面这样:
<!-- html页面 -->
<view class='content2'>
<view class='item'>
<text class='item_title'>姓名</text>
<input type='text' value ='{{name}}' data-type="name" bindinput ="getValue"></input>
</view>
<view class='item'>
<text class='item_title'>手机号</text>
<input type='text' value ='{{phone}}' data-type="phone" bindinput ="getValue"></input>
</view>
</view>
// js页面如下
getValue(res){
let type = res.currentTarget.dataset.type;
switch(type)
{
case 'name':
this.setData({
name : res.detail.value
})
break;
case 'phone':
this.setData({
phone : res.detail.value
})
break;
// 其他...
default:
break;
}
},
// 其他 ....
3、优化方案二: 不使用this.setData
在方案一中,我们主要针对长表单页面进行了简单的优化,而事实上你优化后运行,也感觉不出性能有什么提升,但从逻辑上来说,性能确实提升了一些,只是效果不明显而已。
而方案二,才是优化的重点。
想想,我们在vue中一般都是使用双向数据绑定,vue帮我们处理了许多逻辑。而在小程序中,页面并不是双向数据绑定的,而且由于小程序中获取不到dom节点,我们无法在点提交的时候才去获取各个input里的值,所以我们一般都会习惯性地把各个input的值存入data中,每次存数据到data中都要调用this.setData这个方法,并且由于小程序双线程的构架,this.setData调用后,js逻辑线程就会通知渲染线程去渲染数据更新页面,而2个线程之间的通信及渲染层渲染才是造成性能下降的罪魁祸首。
比如,输入名字中,我输入一个: 任 字。
由于使用拼音的缘故,我要输入 ren,然后选择 任 字。你在getValue()方法中打印一下,看getValue()被调用了多少次?
4次!!!
不仅是getValue()被调用了4次,this.setData()也被调用了4次!!
如果我要输入的内容超多,打字速度超快(20多年的手速...),然后你就会看到,页面卡顿...
然后分析我们的wxml代码,发现有一行 value = '{{name}}' 。
嗯??? 我们为什么要设置这一行代码?为了使页面所渲染的数据和data中保持同步!但本身input数据是刚从渲染层(Input是原生组件,实际是native层,不过这不重要)传递到逻辑层,数据本身就是同步的了!!!
所以,对于input这种输入性标签,我们只需要保存数据就好了!而不用多此一举再次通知渲染层渲染!!!
如果我们不需要通知渲染层渲染,那就不需要用data来存储数据,那么代码将会变成下面这样:
<!-- html页面 -->
<view class='content2'>
<view class='item'>
<text class='item_title'>姓名</text>
<input type='text' data-type='name' bindinput ="getValue"></input>
</view>
<view class='item'>
<text class='item_title'>手机号</text>
<input type='text' data-type='phone' bindinput ="getValue"></input>
</view>
<view class='item'>
<text class='item_title'>身份证</text>
<input type='text' bindinput ="getBankName"></input>
</view>
</view>
//js页面如下
data:{
},
// 这里才是核心!!!
// 本质上Page()是一个方法,传进来的是一个对象,
// 那么既然能存在data,肯定也可以使用自定义mydata来存数据啦。
mydata:{
phone:'',
name:'',
cardId:'',
barnchName:'',
bankCardNo:'',
linkName:'',
linkShip:'',
linkPhone:''
},
getValue(res){
let type = res.currentTarget.dataset.type;
// 如果你不需要对输入的内容各自处理的话,甚至还可以这样做,
// 这样就可以去掉裹脚布一样的switch了
// this.mydata[type] = res.detail.value;
switch(type)
{
case 'name':
this.mydata.name = res.detail.value
break;
case 'phone':
this.mydata.phone = res.detail.value
break;
default:
break;
}
},
在这份代码中,主要使用了mydata来存储数据,页面显示的就是用户输入的数据,不过我们把数据备份了一份,同时切断了他们之间的“同步”。就这样,没有了“同步”,没有了this.setData(),也没有了卡顿。
然后在下一步的方法中,打印一下数据,发现数据也能完整取到,如下图:
4、完整代码
<!-- html -->
<!--pages/task/information/information.wxml-->
<!-- <view class='showTips' animation="{{animation}}">{{message}}</view> -->
<view class='container2'>
<view class='pageItem'>
<!-- <text>1/3</text> -->
<view class='content2'>
<view class='item'>
<text class='item_title'>姓名</text>
<input type='text' data-type='name' bindinput ="getValue"></input>
</view>
<view class='item'>
<text class='item_title'>手机号</text>
<input type='text' data-type='phone' bindinput ="getValue"></input>
</view>
<view class='item'>
<text class='item_title'>身份证</text>
<input type='text' data-type='cardId' bindinput ="getValue"></input>
</view>
<!-- <view class='item'>
<text class='item_title'>开户行</text>
<select prop-array='{{myBankArr}}' bind:myget='getBank'></select>
</view> -->
<view class='item'>
<text class='item_title'>支行名称</text>
<input type='text' data-type='branchName' bindinput ="getValue" ></input>
</view>
<view class='item'>
<text class='item_title'>银行卡号</text>
<input type='text' data-type='cardNo' bindinput ="getValue"></input>
</view>
<view class='item'>
<text class='item_title'>紧急联系人</text>
<input type='text' data-type='linkName' bindinput ="getValue"></input>
<!-- <text class='tips_1'>*</text> -->
</view>
<view class='item'>
<text class='item_title'>联系人关系</text>
<input type='text' data-type='linkShip' bindinput ="getValue"></input>
<!-- <text class='tips_1'>*</text> -->
</view>
<view class='item'>
<text class='item_title'>联系人电话</text>
<input type='text' data-type='linkPhone' bindinput ="getValue" ></input>
<!-- <text class='tips_1'>*</text> -->
</view>
</view>
<button bindtap='next'>下一步</button>
</view>
<view class='pageItem'>
</view>
<view class='pageItem'>
</view>
<button bindtap='nextStep'>下一步</button>
</view>
// js
// pages/t
Page({
mydata:{
phone:'',
name:'',
cardId:'',
barnchName:'',
bankCardNo:'',
linkName:'',
linkShip:'',
linkPhone:''
},
data: {
isChecked: false,
animation: {},
message: '银行卡必须是本人的,用作发佣金',
myBankArr:[
{
"id": 0,
"text": '中国招商银行'
},
{
"id": 1,
"text": '中国工商银行'
},
{
"id": 2,
"text": '中国农业银行'
},
{
"id": 3,
"text": '中国银行'
},
{
"id": 4,
"text": '中国建设银行'
},
{
"id": 5,
"text": '中国邮政储蓄银行'
}
]
},
onLoad: function (options) {
},
onShow: function () {
},
getValue(res){
let type = res.currentTarget.dataset.type;
switch(type)
{
case 'name':
this.mydata.name = res.detail.value
break;
case 'phone':
this.mydata.phone = res.detail.value
break;
case 'cardId':
break;
case 'barnchName':
break;
case 'bankCardNo':
break;
case 'linkName':
break;
case 'linkShip':
break;
case 'linkPhone':
break;
default:
break;
}
},
next(){
console.log(this.mydata)
}
})
/* css 部分 */
/* pages/task/information/information.wxss */
page,view{
margin: 0;
padding: 0;
}
.showTips{
position: absolute;
left: 0;
top: -70rpx;
width: 750rpx;
height: 70rpx;
line-height: 70rpx;
text-align: center;
background-color: #c6d0e3;
color: #00276f;
}
.container2{
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
padding: 0;
overflow: hidden;
}
.pageItem{
width: 750rpx;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: center;
flex-shrink: 0;
}
.content2{
width: 100%;
/* border-top: solid 2rpx #333;
border-bottom: solid 2rpx #333; */
padding-left: 20rpx;
}
.item{
margin-top: 10rpx;
padding: 10rpx 10rpx;
width: 100%;
/* margin: 25rpx 0; */
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
border-bottom: solid 2rpx #EDEDED;
}
.item_title{
width: 150rpx;
flex-shrink: 0;
text-align: right;
font-size: 28rpx;
}
.item > input{
text-align: left;
font-size: 34rpx;
height: 60rpx;
line-height: 60rpx;
padding: 0 20rpx;
width: 490rpx;
/* border-radius: 10rpx; */
}
.item > select{
text-align: left;
font-size: 34rpx;
height: 60rpx;
line-height: 60rpx;
padding: 0 20rpx;
width: 490rpx;
/* border-radius: 10rpx; */
/* border-bottom: solid 2rpx #EDEDED; */
}
.inputPlace{
font-size: 30rpx;
}
.tips_1{
color: red;
margin-left: 10rpx;
width: 10rpx;
}
.tips{
height: 33rpx;
line-height: 33rpx;
margin-top: 180rpx;
}
.tips .img image{
width: 27rpx;
height: 33rpx;
margin: 0 10rpx 0 150rpx;
}
.tips text{
color: #1D4692;
font-size: 25rpx;
}
button{
margin-top: 50rpx;
}
.checkbox{
text-align: center;
color: #1D4692;
margin: 50rpx 0;
}
.checkbox view,.checkbox navigator{
font-size: 26rpx;
}
.checkbox navigator{
display: inline;
text-decoration: underline;
}
checkbox .wx-checkbox-input {
width: 20rpx;
height: 20rpx;
margin-right: 30rpx;
}