小程序开发如何避免踩坑 (微信小程序开发需要避的坑)

微信小程序踩坑指南

来源:https://juejin.im/post/5d06f10f6fb9a07eea327202

开发就是一个西行取经的过程,期间不是一帆风顺,不定会遇到什么鬼。

最近开发一个简单的每日答题签到小程序,期间遇到一些始料未及的坑记录一下,避免日后重复踩坑。本文针对初入小程序的小白。:)

可能会有更好的解决方案,欢迎指正。

多张gif图预警,大概20M

第一难: scroll-view标签对dispay:flex无效

想要实现一个横向滚动的日历,很自然的想到scroll-view组件,于是洋洋洒洒的写下几行代码:

demo.wxml

<scroll-view
 scroll-x="true"
 class="scroll-view-demo"
>
 <view class="item" wx:for="{{[1,2,3,4,5,6,7,8,9,10,11,12,13,14]}}" wx:key="{{index}}">
 {{item}}
 </view>
</scroll-view>
复制代码

demo.scss(为了写着方便,请自行转换成wxss语法,下同)

page {
 width: 100%;
 height: 100%;
 background: #17448E;
}
.scroll-view-demo {
 display: flex;
 flex-direction: row;
 .item {
 width: 80rpx;
 height: 80rpx;
 border: solid 1px #fff;
 display: flex;
 color: #fff;
 font-size: 36rpx;
 justify-content: center;
 align-items: center;
 border-radius: 40rpx;
 margin-left: 50rpx;
 }
}
复制代码

搓搓手,看看效果

微信小程序的坑,微信小程序入坑指南

尴尬,说好的横向滚动呢,明明flex-direction: row;写的清清楚楚明明白白。。

既然scroll-view对flex不友好,那我在子元素上再包裹一层view组件应该万事大吉了吧,于是:

demo.wxml

<scroll-view
 scroll-x="true"
 class="scroll-view-demo"
>
 <view class="item-container">
 <view class="item" wx:for="{{[1,2,3,4,5,6,7,8,9,10,11,12,13,14]}}" wx:key="{{index}}">
 {{item}}
 </view>
 </view>
</scroll-view>
复制代码

demo.scss

.scroll-view-demo {
 .item-container {
 display: flex;
 flex-direction: row;
 .item {
 width: 80rpx;
 height: 80rpx;
 border: solid 1px #fff;
 display: flex;
 color: #fff;
 font-size: 36rpx;
 justify-content: center;
 align-items: center;
 border-radius: 40rpx;
 margin-left: 50rpx;
 }
 }
}
复制代码

看看效果:

微信小程序的坑,微信小程序入坑指南

然鹅。。好像不是那么回事,子元素怎么就被挤成胶囊了。。 仔细研究一番,需要给.item-container加上固定的width才行,甚是麻烦。

简单点,开发的方式简单点,既然scroll-view对flex如此高冷,那就换一种方式:

demo.wxml

<scroll-view
 scroll-x="true"
 class="scroll-view-demo"
>
 <view class="item" wx:for="{{[1,2,3,4,5,6,7,8,9,10,11,12,13,14]}}" wx:key="{{index}}">
 {{item}}
 </view>
</scroll-view>
复制代码

demo.scss

.scroll-view-demo {
 width: 100%;
 white-space: nowrap;
 .item {
 width: 80rpx;
 height: 80rpx;
 line-height: 80rpx;
 text-align: center;
 border: solid 1px #fff;
 display: inline-block;
 color: #fff;
 font-size: 36rpx;
 border-radius: 40rpx;
 margin-left: 50rpx;
 }
复制代码

效果:

微信小程序的坑,微信小程序入坑指南

两个字,接近完美

不过,在手机上浏览scroll-view会有一个滚动条,很丑有木有

于是挑灯夜读翻阅资料找到解决方法:

app.css

/*隐藏scroll-view滚动条*/
::-webkit-scrollbar{
 width: 0;
 height: 0;
 color: transparent;
}
复制代码

这下妥妥的了,

年轻人切记,scroll-view 尽量不用flex

第二难: scroll-view 设置scroll-left无效问题

以为scroll-view踩完一个坑就结束了么,图森破图样。

想给scroll-view在页面loading结束后滚动到某一个位置, 很简单,闭着眼写代码:

demo.wxml

<scroll-view
 scroll-x="true"
 class="daily-check-calendar-scroll"
 scroll-left="{{scrollLeft}}"
>
 <view class="item" wx:for="{{list}}" wx:key="{{index}}">
 {{item}}
 </view>
</scroll-view>
复制代码

demo.js

Page({
	data: {
		scrollLeft: 100,
		list: []
	},
	onLoad(){
		setTimeout(()=>{
			//假装异步获取数据
			this.setData({
				list: [1,2,3,4,5,6,7,8]
			});
		},1000);
	}
})
复制代码

看效果,发现scroll-view带在原地纹丝不动。惆怅~

let me see see 代码稍作改动:

demo.js

Page({
	data: {
		scrollLeft: 0,
		list: []
	},
	onLoad(){
		setTimeout(()=>{
			//假装异步获取数据
			this.setData({
				list: [1,2,3,4,5,6,7,8],
				scrollLeft: 100
			});
		},1000);
	}
})
复制代码

perfect 果然达到了预期,细想原因:

  • 一般在页面加载时我们会请求数据,并渲染列表, 但是我们在标签或者data中设置的scroll-left值会在数据渲染前赋值.
  • 此时的scroll-view中还是空的,所以scroll-left不会生效. 我们应该在数据渲染到scroll-view中以后,再同步scroll-left的值

第三难: button自定义样式,无法去掉默认样式

想写一个自定义样式的按钮,so easy,给我一秒写出来:

demo.wxml

<view class="button">超新脱俗的按钮</view>
<button>超新脱俗的按钮,too</button>
复制代码

demo.scss

button,
.button {
 width: 500rpx;
 height: 88rpx;
 display: flex;
 align-items: center;
 justify-content: center;
 font-size: 32rpx;
 background: #FE7437;
 color: #fff;
 border-radius: 44rpx;
 margin: 20rpx auto;
}
复制代码

效果:

微信小程序的坑,微信小程序入坑指南

猛一看,感觉不能再完美了,定睛一看,怎么觉得button组件写出来的按钮不是那么纯洁,有阴影。

很自然的再加行代码:

demo.scss

border: none;
复制代码

事情并没有那么简单,阴影依然噩梦般存在。 有困难,找百度,果不其然大神们的奇技淫巧顺利解决,贴上完整代码:

demo.scss

button,
.button {
 width: 500rpx;
 height: 88rpx;
 display: flex;
 align-items: center;
 justify-content: center;
 font-size: 32rpx;
 background: #FE7437;
 color: #fff;
 border-radius: 44rpx;
 margin: 20rpx auto;
 &::after{
 border-radius: 0;
 border: none;
 }
}
复制代码

效果:

微信小程序的坑,微信小程序入坑指南

简直两个按钮一模一样。

第四难 让人伤神的input组件

小程序开发免不了要有登录页面,于是就离不开input组件,代码就不贴了,常规操作,直接上图。

微信小程序的坑,微信小程序入坑指南

看图很容易发现几个问题:

  1. 切换input时 文字会闪动
  2. 切换下一个input时 需要多次才能获取到焦点,弹出键盘。
  3. 如果在不同的手机上看,会发现密码框的黑点大小也不一致

针对第一个问题,百度谷歌挖地三尺也木有找到好的解决办法(可能是挖的不够深 :() 第二个问题,既然不能自主获取焦点,那我们结合官方文档助它一臂之力:

demo.wxml

<view class="account-main">
 <text class="account-title">登录</text>
 <form class="login-form" bindsubmit="formSubmit">
 <view class="form-item {{selectName ? ’selected’ : ’’}}">
 <input type="text" value="{{username}}" focus="{{selectName}}" bindtap="handleName" bindinput="getUsername" placeholder-class="login-ipt-place" class="login-ipt" placeholder="手机号/邮箱" />
 </view>
 <view class="form-item {{selectPwd ? ’selected’ : ’’}}">
 <input type="text" value="{{password}}" focus="{{selectPwd}}" bindtap="handlePwd" bindinput=’getPassword’ placeholder-class="login-ipt-place" maxlength="48" class="login-ipt pwd-ipt" placeholder="登录密码" password="true" />
 </view>
 <button class="form-submit" form-type="submit">
 登录
 </button>
 </form>
</view>
复制代码

demo.scss

...
复制代码

demo.js

Page({
 data: {
 username: ’’,
 password: ’’,
 selectName: true,
 selectPwd: false,
 },
 getUsername(e){
 //获取用户名
 this.setData({
 username: e.detail.value,
 });
 },
 getPassword(e){
 //获取密码
 this.setData({
 password: e.detail.value,
 })
 },
 handleName(){
 this.setData({
 selectName: true,
 selectPwd: false,
 })
 },
 handlePwd(){
 this.setData({
 selectName: false,
 selectPwd: true,
 })
 }
});
复制代码

看看效果:

微信小程序的坑,微信小程序入坑指南

看着像是好多了 思考: 若文本框不止两个,怎么写才更优雅,另,没有看在不同手机上的表现。

第三个关于密码黑点大小的问题 我也木有良策 +_+

以下是网上大佬们关于input组件bug的总结:

  • placeholder 文字与 input 的值重叠 暂无解决方法
  • 获取焦点 和 失去焦点 时,光标和文字跳动 暂无解决方法
  • 当 input 设置为居中对齐时,光标会出现在奇怪的位置 暂无解决方法
  • bindconfirm 事件在失去焦点时也会触发,类似于 blur 暂无解决方法
  • 对 input 做动画时,如果是获取焦点状态,会失效 暂无解决方案,因为 input 在获取焦点时是native 组件,失去焦点后改回 web 组件
  • type 为 idcard, digit 时并不是调用数字键盘 暂无解决方案,目前起作用的只有 number
  • 在input聚焦期间,不能做css动画,否则input中的placeholder会错位,如果动画和聚焦都想要的话,那么可以在动画完成之后,再设置聚焦

看看微信官方社区的吐槽,如果你也被坑了,去给助个攻吧

微信小程序的坑,微信小程序入坑指南

传送门

第五难 getPhoneNumber获取用户手机号解密报错问题

小程序登录时我们可以利用微信获取手机号快捷登录,根据文档我们很容易写一套登录流程:

demo.wmxl

<button class="login-by-wx" open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">
 微信登录
</button>
复制代码

demo.js

Page({
	...
	async getPhoneNumber(e) {
		if(!e.detail.iv){
 //被拒绝授权
 return;
 }
 
 let code = ’’;
 
 /*
 	 * 别慌,wxPromise.login()其实就是wx的api被promise化,参考 **转转**代码
 	 * https://github.com/zhuanzhuanfe/fancy-mini/blob/master/src/wxPromise.js
 	 */
 	 const res = await wxPromise.login();
 	 code = res.code;
 	 
 	 if(!code){
 	 	console.error(`调用 wx.login 失败`, err);
 return util.toast(’微信登录失败’);
 	 }
 
 try{
 	//server端微信登录接口
 const result = await service.passport.wxLogin({
 data: {
 code: code,
 iv: e.detail.iv,
 encryptedData: e.detail.encryptedData
 }
 });
 const res = result.data;
 if (res.status === 0) {
 // 登录成功
 	...
 }else {
 ...
 }
 }catch(err){
 ...
 }
 
	}
	...
});
复制代码

server.js

async wxLoginAction(){
	//微信登录
 const {ctx} = this;
 const body = ctx.request.body;
 //请求微信接口 https://api.weixin.qq.com/sns/jscode2session
 const result = await this.callService(’wxApplet.code2Session’, {
 appid: ***,
 secret: ***,
 js_code: body.code,
 grant_type: ’authorization_code’
 });
 
 if(result.status == 0){
 let sessionKey = result.data.session_key;
 let encryptedData = body.encryptedData;
 let iv = body.iv;
 //解密获取手机号 (解密库小程序文档有对应的源码)
 let pc = new WXBizDataCrypt(appid, sessionKey);
 let mobile = pc.decryptData(encryptedData, iv).phoneNumber;
 
 //解密成功 生成登录态等操作
 ...
 
 return this.json({
 	status: 0,
 	message: ’ok’,
 	data: mobile
 });
 
 }else {
 	....
 }
}
复制代码

搓搓手,看效果:

微信小程序的坑,微信小程序入坑指南

竟然首次登录的时候有报错,以后再次登录却一切正常。

看server端报错日志,如下:

Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
复制代码

这条error信息是从WXBizDataCrypt解密失败时抛出来的。

查看文档,看button的官方文档Tips中有这一条:

在bindgetphonenumber 等返回加密信息的回调中调用 wx.login 登录,可能会刷新登录态。此时服务器使用 code 换取的 sessionKey 不是加密时使用的 sessionKey,导致解密失败。建议开发者提前进行 login;或者在回调中先使用 checkSession 进行登录态检查,避免 login 刷新登录态。

另外,查看网上大神的解决方案,其中有一条是这样说的:

微信小程序的坑,微信小程序入坑指南

纳尼,点击按钮之前就要调用login,于是将信将疑的再次修改代码:

demo.js

Page({
	data: {
		code: ’’
	},
	async onLoad(options){
		try {
 const res = await wxPromise.login();
 this.setData({
 code: res.code
 })
 }catch (err){
 this.setData({
 code: ’’
 })
 }
	},
	async getPhoneNumber(e) {
		if(!e.detail.iv){
 //被拒绝授权
 return;
 }
 let code = this.data.code;
 if(!code){
 	 	console.error(`调用 wx.login 失败`, err);
 return util.toast(’微信登录失败’);
 	 }
 
 try{
 	//server端微信登录接口
 const result = await service.passport.wxLogin({
 data: {
 code: code,
 iv: e.detail.iv,
 encryptedData: e.detail.encryptedData
 }
 });
 const res = result.data;
 if (res.status === 0) {
 // 登录成功
 	...
 }else {
 ...
 }
 }catch(err){
 ...
 }
	}
});
复制代码

修改完毕,经过测试,首次登录果然很顺利就成功了,但是又出现了一个幺蛾子,再次登录就又又又报错:

error: code is used
复制代码

这个报错就很好理解了,稍稍修改一下代码:

demo.js

Page({
	data: {
		code: ’’
	},
	async onLoad(options){
		try {
 const res = await wxPromise.login();
 this.setData({
 code: res.code
 })
 }catch (err){
 this.setData({
 code: ’’
 })
 }
	},
	async getPhoneNumber(e) {
		if(!e.detail.iv){
 //被拒绝授权
 return;
 }
 let code = this.data.code;
 
 if (!code) {
 //防止code失效
 try{
 const res = await wxPromise.login();
 code = res.code;
 }catch(err){
 console.error(`调用 wx.login 失败`, err);
 return util.toast(’微信登录失败’);
 }
 }
 
 try{
 	//server端微信登录接口
 const result = await service.passport.wxLogin({
 data: {
 code: code,
 iv: e.detail.iv,
 encryptedData: e.detail.encryptedData
 }
 });
 const res = result.data;
 
 this.setData({
 //用过的code需要清空,重新获取
 code: ’’
 });
 
 if (res.status === 0) {
 // 登录成功
 	...
 }else {
 ...
 }
 }catch(err){
 ...
 }
	}
});
复制代码

泪流满面,自我测试,确实经得起组织考验,完美解决。(也可能测试的不够透彻,如果还有报错,或者更好的解决方案,欢迎打脸T T)

第六难 刷新页面图片加载时变形问题

image组件开发时特别常见,要写一个宽度固定,高度自适应代码也很简单:

demo.wxml

<image
 mode = "widthFix"
 src="你的图片地址"
>
</image>
复制代码

看着很简单,感觉稳稳地,但是刷新页面时会有中见鬼的赶脚,话不多说上图:

微信小程序的坑,微信小程序入坑指南

仔细看似乎有鬼影掠过。。。 加载时图片被拉扯变形,

其实解决方法也很简单:

demo.scss

image {
	height: auto;
}
复制代码

对,就是这么简单,就完美解决。