微信小程序目前正处于一个高速发展的阶段,官方的开发文档有着很多不完善的地方。最近美学上线的微信小程序“美学福利社”就是在不断踩坑的过程中开发完成的。本篇文章总结了我在开发时遇到的一些问题和解决方案,供大家参考。
在接到需求的时候微信小程序还没有开放自定义组件的能力(现在已有官方实现),代码是以页面为基本单位来组织的。并且官方提供的一些组件可定制性不强,限制太多。虽然也有一些第三方的解决方案如Min、wx-component等,但考虑到本身业务复杂度不高,需要定制的组件也都比较简单,引入这些解决方案带来的额外的开发成本比较高,于是选择了自己实现自定义组件。
一个自定义组件由js文件、wxml模板文件和wxss样式文件组成,这三个文件实际是互相没有关联的。使用组件的页面要在对应的文件中分别引入这三个文件,这样它们通过页面联系在一起,成为一个完整的组件。js文件定义了组件类,它负责管理组件的属性和方法,由于组件类没有办法和模板进行直接的数据绑定,所以UI的状态管理只能交给引用组件的页面来做。
以一个toast组件为例:
组件类
export default class Toast {
constructor(host, showTime = 1500) {
this.host = host; // 引用组件的宿主对象
this.showTime = showTime; // 默认展示时间1500ms
}
/**
* 展示toast
* msg: 要提示的信息
*/
showToast({ msg, showTime}) {
this.host.setData({
msg: msg,
toastVisible: true
});
// showTime后隐藏
setTimeout(() => {
this.host.setData({
toastVisible: false
})
}, showTime || this.showTime);
}
}
组件的host属性保留了对实例化组件的页面的引用,便于通过宿主页面对组件的状态进行管理。这样的实现方法其实存在着组件和页面、以及多个组件间状态污染的问题,只能通过合理的命名来规避,管理起来比较困难。
模板
<template name="toast">
<view wx:if="{{ toastVisible }}" class="toast">
<text class="toast__msg">{{ msg }}</text>
</view>
</template>
样式
.toast {
position: fixed;
z-index: 1000;
display: inline-block;
width: 438rpx;
left: 50%;
margin-left: -219rpx;
padding: 13rpx 23rpx 25rpx 25rpx;
top: 250rpx;
box-sizing: border-box;
background-color: rgba(0, 0, 0, .8);
border-radius: 10rpx;
text-align: center;
}
.toast__msg {
font-size: 28rpx;
line-height: 40rpx;
color: #fff;
}
由于wxss不支持css嵌套,我们采用了BEM命名法来避免可能的类名污染。
使用组件时引入必要的文件,在页面中实例化对象
const toast = new Toast(host, showTime);
然后在需要的地方调用组件的方法即可。
这里的自定义组件实现方式是比较简陋的,不过对于简单的定制组件来说已经够用了。值得一提的是现在官方已经开放了自定义组件,最重要的是使用官方的自定义组件只需要在页面的json配置文件中进行引用声明就好了,对于一个懒癌来说不用手动引入诸多文件简直不要太幸福!虽然支持该特性的微信版本比较高,并且有着诸多的坑等着我们去踩,但也不妨一试。
小程序中有一个绕不过去的场景就是获取用户信息,获取用户信息就要取得用户授权。而当用户拒绝授权时我们需要重新发起授权。那么怎么重新发起授权呢?
微信的授权方法是wx.authorize,有三个关键参数:
scope指定要获取授权的scope,success和fail分别是授权成功和失败的回调。坑爹的是这个方法在开发者工具2017.11.16更新之前的版本里,不管用户允许还是拒绝授权都会进入成功回,只能用真机调试。
言归正传,怎么重新发起授权?代码是这样的:
// 申请授权
wx.authorize({
scope: 'scope.userInfo',
success(res) {
that.logIn();
},
fail(res) {
// 未授权提示
wx.showModal({
title: '提示',
content: '需要授权,才可以申请试用',
showCancel: false
})
}
});
这是一个点击操作的响应函数中的代码片段。本以为重新发起授权是一个默认的行为,结果发现拒绝授权之后,除非用户本地的授权数据被清除,不然就无法重新发起授权,调用授权接口只会直接走拒绝授权逻辑。查阅文档后发现小程序提供了一个设置页面(wx.openSetting)可以让用户选择重新授权,但是操作步骤多,成本较高,并且会打断当前的交互流程。怎么办?继续看文档,终于发现了一个解决方案——— button组件的open-type属性
button组件设置open-type属性值为getUserInfo后,如果用户未授权则每次点击都会再次弹出授权弹窗提示用户授权。
值得注意的是,用户拒绝授权的情况要妥善处理。必须要授权才能进行的操作可以提示用户授权,不处理用户拒绝授权的情况可能会无法通过小程序审核。
小程序的的接口大部分都提供了异步和同步方法。同步方法使用过多的话阻塞时间较长,所以平时开发异步方法用的比较多。而异步方法的问题就是方法间互相依赖时免不了陷入回调地狱。新版本的基础库中又移除了对promise的支持,所以我引入了第三方库es6-promise来处理异步流程。
将小程序api改造成promise,以获取用户信息为例:
function getEncryptedData(options) {
return new Promise((resolve = Promise.resolve, reject = Promise.reject) => {
wx.getUserInfo({
...options,
withCredentials: true,// 这个参数表示需要获取敏感信息,需要在登录态下才能成功
success(res) {
resolve(res)
},
fail(errMsg) {
reject();
console.error(errMsg);
}
});
});
}
将小程序api的成功与失败回调通过resolve、reject处理,其他参数可在初始化promise时传入。
多个异步方法有数据依赖时有可能会出现这样的情况:
// promiseFactory 为返回promise的函数
promiseFactory1()
.then(promiseFactory2)
.then(promiseFactory3)
...
.then(promiseFactoryN)
如果这个调用链长了的话未免看起来不太清爽。小程序不支持es7,没有办法用async/await来处理。于是我想到了co这个框架,co是一个基于es6 generator和yield特性实现的框架,可以用同步的语法来处理异步的流程,彻底摆脱回调的烦恼,并且它的新版本也支持promise了。于是我兴冲冲地引入了co,结果小程序不支持yield关键字,说好的支持es6呢!
好吧,接受这个残酷的事实,看来只能自己处理了。
/**
* 顺序执行promise
* promiseFactories 返回promise的函数或函数数组
*/
function executeSequentially(promiseFactories) {
var result = Promise.resolve();
promiseFactories.forEach(function (promiseFactory) {
if (promiseFactory instanceof Array) {
result = result.then(...promiseFactory);
} else {
result = result.then(promiseFactory);
}
});
return result;
}
具有依赖关系需要依次执行的promise可以交给executeSequentially处理,像这样:
executeSequentially([promiseFactory1, promiseFactory2, ..., promiseFactoryN])
虽然和.then调用链的写法没有本质上的区别,不过能少写几行代码多少也算是个进步吧。
模板消息是微信服务号向用户推送的一种服务消息,而发送模板消息则需要获取formId,一个formId对应一条模板消息。在小程序中有两个获取formId的渠道:
页面的 form 组件,属性report-submit为true时,可以声明为需发模板消息,此时点击按钮提交表单可以获取formId,用于发送模板消息。或者当用户完成支付行为,可以获取prepay_id用于发送模板消息。
我们的需求里,需要在用户操作后的一段时间,比如15天后向用户发送模板消息。而一个formId的有效期一般不超过7天,怎么才能解决这个问题呢?我们的解决方案是将所有存在点击操作的地方都包装成form表单,通过表单提交来处理点击操作,以此来尽可能地收集formId。只要用户在发送模板消息的前七天内进入过小程序,基本就能收集到所需的formId。
<form report-submit bindsubmit="sendFormId">
<button class="share" open-type="share" form-type='submit'>
<text>分享</text>
</button>
</form>
要注意的是需要重置button的默认样式。
<view class="form-item">
<label class="form-label">验证码</label>
<input class="form-input input--captcha" type="number" placeholder='输入验证码' placeholder-class="input-placeholder" bindinput="handleCaptcha"></input>
<button class="captcha" plain bindtap="getCaptcha" disabled="{{!mobile || sending}}">{{ sending ? countDownText : "发送验证码" }}</button>
</view>
登录流程中有一个发送验证码的操作,发送验证码的按钮是绝对定位在input上面的。 小程序的input组件未激活时可以通过在按钮设置z-index使按钮覆盖在input组件上,而input组件获得焦点激活时,它的层级是最高的,覆盖在按钮上,无法触发按钮的点击事件。
解决方法是把input的长度缩短,这样即使激活也不会盖住按钮了。
微信小程序做为微信仍在快速迭代中的一个功能,还有许多不完善的地方,如果本文能让大家减少一些摸索的成本,那就再好不过了。
最后欢迎大家关注美学福利社(°:з」∠)
参考:
网易云新用户大礼包:https://www.163yun.com/gift
本文来自网易实践者社区,经作者丰凡程授权发布。