🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
常用设计开发流程 首先,我们对整个小程序整体的产品体验有一个清晰的规划和定义,一般会通过交互图或者手稿描绘小程序的界面交互和界面之间的跳转关系。 其次,我们优先完成WXML+WXSS还原设计稿,把界面涉及到的元素和视觉细节先调试完成。 最后,我们把按照页面交互梳理出每个页面的data部分,填充WXML的模板语法,还有完成JS逻辑部分。 有些时候我们可能在产品交互体验还不明确的情况下,先完成JS逻辑层一些模块的工作并做好测试。高效的开发流程有很多种方式,一般是根据整个团队的工作节奏来选择和开展。 [TOC] ## 3.1 Flex布局 在小程序开发中,我们需要考虑各种尺寸终端设备上的适配。采用flex布局取代了传统网页开发中的盒模型。 采用flex布局的元素,简称为“容器”,在代码示例中以`container`表示容器的类名。容器内的元素简称为“项目”,在代码示例中以`item`表示项目的类名。 ### 3.1.1 container ### 3.1.2 item ## 3.2 界面常见的交互反馈 用户和小程序上进行交互的时候,某些操作可能比较耗时,我们应该予以及时的反馈以舒缓用户等待的不良情绪。 ### 3.2.1 触摸反馈 * hover 小程序的`view`容器组件和`button`组件提供了hover-class属性,触摸时会往该组件加上对应的class改变组件的样式。 * loading 有时候在点击`button`按钮处理更耗时的操作时,我们也会使用button组件的loading属性,在按钮的文字前边出现一个Loading,让用户明确的感觉到,这个操作会比较耗时,需要等待一小段时间。 * Toast 在完成某个操作成功之后,我们希望告诉用户这次操作成功并且不打断用户接下来的操作。弹出式提示Toast就是用在这样的场景上,Toast提示默认1.5秒后自动消失。 * 模态(Modal)对话框 一般需要用户明确知晓操作结果状态的话,会使用模态对话框来提示,不适合用一闪而过的Toast弹出式提示。 ### 3.2.3 界面滚动 为了让用户可以快速刷新当前界面的信息,一般在小程序里会通过下拉整个界面这个操作来触发。 * **下拉刷新** 宿主环境提供了统一的下拉刷新交互,开发者只需要通过配置开启当前页面的下拉刷新,用户往下拉动界面触发下拉刷新操作时,Page构造器的onPullDownRefresh回调会被触发,此时开发者重新拉取新数据进行渲染。 * **上拉触底** 多数的购物小程序会在首页展示一个商品列表,用户滚动到底部的时候,会加载下一页的商品列表渲染到列表的下方,我们把这个交互操作叫为上拉触底。宿主环境提供了上拉的配置和操作触发的回调。 * **可滚动视图组件** 是页面中某一小块区域需要可滚动,此时就要用到宿主环境所提供的`scroll-view`可滚动视图组件。 ## 3.3 发起HTTPS网络通信`wx.request` 小程序使用`wx.request`这个API,往服务器传递数据或者从服务器拉取信息。 >[info] hy,封装了JavaScript的XhttpRequest对象?通过Ajax来实现。 ### 3.3.1 wx.request接口 :-: 表4-1 wx.request详细参数 | 参数名 | 类型 | 必填 | 默认值 | 描述| | --- | ---| --- | --- | --- | | url | String | 是 | | 开发者服务器接口地址| | data | Object/String | 否 | | 请求的参数 | | header | Object | 否 | | 设置请求的 header,header 中不能设置 Referer,默认header['content-type'] = 'application/json' | method | String | 否 | GET | (需大写)有效值:OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT | | dataType | String | 否 | json | 回包的内容格式,如果设为json,会尝试对返回的数据做一次 JSON解析| | success | Function | 否 | | 收到开发者服务成功返回的回调函数,其参数是一个Object,见表4-2。 | fail | Function | 否 | | 接口调用失败的回调函数| | complete | Function | 否 | | 接口调用结束的回调函数(调用成功、失败都会执行)| ### 3.3.2 服务器要求 url参数是当前发起请求的服务器接口地址,小程序宿主环境要求request发起的网络请求必须是https协议请求,因此开发者服务器必须提供HTTPS服务的接口,同时为了保证小程序不乱用任意域名的服务,wx.request请求的域名需要在小程序管理平台进行配置[[配置参考]](https://developers.weixin.qq.com/miniprogram/dev/qcloud/qcloud.html#%E5%AF%BC%E5%85%A5-PHP-DEMO-%E5%92%8C%E9%85%8D%E7%BD%AE)( 登录`mp.weixin.qq.com`,在设置, 开发设置里配置request的服务器域名,同时TLS版本需要支持1.2及以上。),如果小程序正式版使用wx.request请求未配置的域名,在控制台会有相应的报错。 一般我们在开发阶段时,处于开发阶段的服务器接口还没部署到现网的域名下,经常会通过另一个域名来进行开发调试,考虑到这一点,为了方便开发者进行开发调试,开发者工具、小程序的开发版和小程序的体验版在某些情况下允许`wx.request`(开发者工具需要勾选不校验可信域名;小程序开发版和体验版需要打开调试模式。)请求任意域名。 ### 3.3.3 请求参数 通过wx.request这个API,有两种方法把数据传递到服务器: * 通过url上的参数 url是有长度限制的,其最大长度是1024字节,同时url上的参数需要拼接到字符串里,参数的值还需要做一次urlEncode。 * 通过data参数 两种实现方式在HTTP GET请求的情况下表现几乎是一样的。但向服务端发送的数据超过1024字节时,就要采用HTTPPOST的形式,此时传递的数据就必须要使用data参数,基于这个情况,一般建议需要传递数据时,使用data参数来传递。 需要传一些比较复杂的数据结构到后台的时候,用JSON格式会更加合适。此时可以在wx.request的`header`参数设置content-type头部为application/json,小程序发起的请求的包体内容就是data参数对应的JSON字符串。 ### 3.3.4 收到回包 通过wx.request发送请求后,服务器处理请求并返回HTTP包,小程序端收到回包后会触发success回调,同时回调会带上一个Object信息,详细参数表4-2所示。 :-: 表4-2 wx.request的success返回参数 | 参数名 | 类型 | 描述 | | --- | --- | --- | | data | Object/String | 开发者服务器返回的数据 | | statusCode | Number | 开发者服务器返回的 HTTP 状态码 | | header | Object | 开发者服务器返回的 HTTP Response Header | >[danger] 只要成功收到服务器返回,无论HTTP状态码是多少都会进入success回调。因此开发者需要自己通过对回包的返回码进行判断后再执行后续的业务逻辑。 success回调的参数data字段类型是根据header\['content-type']决定的,默认`header['content-type']是'application/json'`,在触发success回调前,小程序宿主环境会对data字段的值做JSON解析,如果解析成功,那么data字段的值会被设置成解析后的Object对象,其他情况data字段都是String类型,其值为HTTP回包包体。 ### 3.3.5 一般使用技巧 1. **设置超时时间** 小程序request默认超时时间是60秒,一般来说,可能在等待3秒后还没收到回包就需要给用户一个明确的服务不可用的提示。在小程序项目根目录里边的app.json可以指定request的超时时间。 2. **请求前后的状态处理** 大部分场景可能是这样的,用户点击一个按钮,界面出现“加载中...”的Loading界面,然后发送一个请求到后台,后台返回成功直接进入下一个业务逻辑处理,后台返回失败或者网络异常等情况则显示一个“系统错误”的Toast,同时一开始的Loading界面会消失。 为了防止用户极快速度触发两次tap回调,我们还加了一个hasClick的“锁”,在开始请求前检查是否已经发起过请求,如果没有才发起这次请求,等到请求返回之后再把锁的状态恢复回去。 ### 3.3.6 排查异常的方法 在使用wx.request接口我们会经常遇到无法发起请求或者服务器无法收到请求的情况,我们罗列排查这个问题的一般方法: 1. 检查手机网络状态以及wifi连接点是否工作正常。 2. 检查小程序是否为开发版或者体验版,因为开发版和体验版的小程序不会校验域名。 3. 检查对应请求的HTTPS证书是否有效,同时TLS的版本必须支持1.2及以上版本,可以在开发者工具的console面板输入showRequestInfo()查看相关信息。 4. 域名不要使用IP地址或者localhost,并且不能带端口号,同时域名需要经过ICP备案。 5. 检查app.json配置的超时时间配置是否太短,超时时间太短会导致还没收到回报就触发fail回调。 6. 检查发出去的请求是否302到其他域名的接口,这种302的情况会被视为请求别的域名接口导致无法发起请求。 ## 3.4 微信登录 微信登录的整个过程,如图4-22所示。 ![微信登录的整个过程](https://box.kancloud.cn/1a733d375a542ff206919f05f8a9b8c3_775x441.JPG) :-: 图4-22 微信登录的整个过程 ### 3.4.1 获取微信登录凭证code 在`wx.login`调用时,会先在微信后台生成一张临时的身份证,其有效时间仅为5分钟 。然后把这个临时身份证返回给小程序方,这个临时的身份证我们把它称为微信登录凭证code。 ### 3.4.2 发送code到开发者服务器 在wx.login的success回调中拿到微信登录凭证,紧接着会通过`wx.request`把code传到开发者服务器,为了后续可以换取微信用户身份id(注意id不是指微信用户的微信号/密码等隐私信息,可以理解为id是微信用户在服务器的一个编号)。如果当前微信用户还没有绑定当前小程序业务的用户身份,那在这次请求应该顺便把用户输入的帐号密码(是用户在业务侧的帐号密码,而不是其微信的帐号密码)一起传到后台,然后开发者服务器就可以校验账号密码之后再和微信用户id进行绑定。 ### 3.4.3 到微信服务器换取微信用户身份id 开发者的后台就拿到了前边wx.login()所生成的微信登录凭证code,此时就可以拿这个code到微信服务器换取微信用户身份。 开发者服务器通过HTTPS协议,向微信服务器发起请求换取微信用户身份。微信服务器提供的接口地址是: `https://api.weixin.qq.com/sns/jscode2session?appid=<AppId>&secret=<AppSecret>&js_code=<code>&grant_type=authorization_code` AppId和AppSecret是微信鉴别开发者身份的重要信息: `AppId`是公开信息,泄露AppId不会带来安全风险,但是`AppSecret`是开发者的隐私数据不应该泄露,如果发现泄露需要到小程序管理平台进行重置AppSecret,而code在成功换取一次信息之后也会立即失效,即便凭证code生成时间还没过期。 URL的query部分的参数中 \<AppId>, \<AppSecret>, \<code> 就是前文所提到的三个信息,请求参数合法的话,接口会返回以下字段。 :-: 表4-3 jscode2session接口返回字段 | 字段 | 描述 | | --- | --- | | openid | 微信用户的唯一标识 | | session_key | 会话密钥 | | unionid | 用户在微信开放平台的唯一标识符。本字段在满足一定条件的情况下才返回。| `openid`就是前文一直提到的微信用户id,可以用这个id来区分不同的微信用户。 `session_key`则是微信服务器给开发者服务器颁发的身份凭证,开发者可以用session_key请求微信服务器其他接口来获取一些其他信息,由此可以看到,session_key不应该泄露或者下发到小程序前端。 设计session_key的原因:如果我们每次都通过小程序前端wx.login()生成微信登录凭证code去微信服务器请求信息,步骤太多造成整体耗时比较严重,因此对于一个比较可信的服务端,给开发者服务器颁发一个时效性更长的会话密钥就显得很有必要了。session_key也存在过期时间,具体内容可以参考小程序的官方文档关于session_key的相关介绍。 ### 3.4.4 绑定微信用户身份id和业务用户身份 业务侧用户如果还没绑定微信侧身份时,会让用户填写业务侧的用户名密码,这两个值会和微信登录凭证一起请求开发者服务器的登录接口,此时开发者后台通过校验用户名密码就拿到了`业务侧的用户身份id`,通过code到微信服务器获取`微信侧的用户身份openid`。微信会建议开发者把这两个信息的对应关系存起来,我们把这个对应关系称之为“绑定”。 有了这个绑定信息,小程序在下次需要用户登录的时候就可以不需要输入账号密码,因为通过wx.login()获取到code之后,可以拿到用户的微信身份openid,通过绑定信息就可以查出业务侧的用户身份id,这样静默授权的登录方式显得非常便捷。 ### 3.4.5 业务登录凭证SessionId `session_key`:是微信侧返回的,开发者服务器和微信服务器之间的会话密钥; `SessionId`:是开发者服务器返回的,开发者服务器和开发者的小程序之间的会话密钥。 >[info] hy:使用http协议,因为http协议是无状态协议,可以前端(browser)和后端(server)通过Session会话机制免去每次通信时的3次握手,从而提高通信效率。 > session_key中,前端:开发者服务器;后端:微信服务器 > SessionId中,前端:开发者的小程序;后端:开发者服务器 用户登录成功之后,开发者服务器需要生成会话密钥SessionId,在服务端保持SessionId对应的用户身份信息,同时把SessionId返回给小程序。小程序后续发起的请求中携带上SessionId,开发者服务器就可以通过服务器端的Session信息查询到当前登录用户的身份,这样我们就不需要每次都重新获取code,省去了很多通信消耗。我们在4.6.4还会提到如何利用本地数据缓存的能力把SessionId存储起来,以便在它还没过期的时候能重复利用,以提高通信的性能。 ## 3.5 本地数据缓存 本地数据缓存是小程序存储在当前设备硬盘上的数据,本地数据缓存有非常多的用途: * 存储用户在小程序上产生的操作,在用户关闭小程序重新打开时可以恢复之前的状态。 * 缓存一些服务端非实时的数据提高小程序获取数据的速度,在特定的场景下可以提高页面的渲染速度,减少用户的等待时间。 ### 3.5.1 读写本地数据缓存 小程序提供了读写本地数据缓存的接口, `wx.getStorage/wx.getStorageSync`读取本地缓存 `wx.setStorage/wx.setStorageSync`写数据到缓存 其中Sync后缀的接口表示是同步接口,执行完毕之后会立马返回。 :-: 表4-4 wx.getStorage/wx.getStorageSync详细参数 | 参数名 | 类型 | 必填 | 描述 | | --- | --- | --- | --- | | key | String | 是 | 本地缓存中指定的 key | | success | Function | 否 | 异步接口调用成功的回调函数,回调参数格式: {data: key对应的内容} | | fail | Function | 否 | 异步接口调用失败的回调函数 | | complete | Function | 否 | 异步接口调用结束的回调函数(调用成功、失败都会执行) | :-: 表4-5 wx.setStorage/wx.setStorageSync详细参数 | 参数名 | 类型 | 必填 | 描述 | | --- | --- | --- | --- | | key | String | 是 | 本地缓存中指定的 key | | data | Object/String | 是 | 需要存储的内容 | | success | Function | 否 | 异步接口调用成功的回调函数 | | fail | Function | 否 | 异步接口调用失败的回调函数 | | complete | Function | 否 | 异步接口调用结束的回调函数(调用成功、失败都会执行) | ### 3.5.2 缓存限制和隔离 小程序宿主环境会管理不同小程序的数据缓存,不同小程序的本地缓存空间是分开的,每个小程序的缓存空间上限为10MB,如果当前缓存已经达到10MB,再通过wx.setStorage写入缓存会触发fail回调。 小程序的本地缓存不仅仅通过小程序这个维度来隔离空间,考虑到同一个设备可以登录不同微信用户,宿主环境还对不同用户的缓存进行了隔离,避免用户间的数据隐私泄露。 由于本地缓存是存放在当前设备,用户换设备之后无法从另一个设备读取到当前设备数据,因此用户的关键信息不建议只存在本地缓存,应该把数据放到服务器端进行持久化存储。 ### 3.5.3 利用本地缓存提前渲染界面 * **界面渲染** 讨论一个需求:我们要实现一个购物商城的小程序,首页是展示一堆商品的列表。一般的实现方法就是在页面onLoad回调之后通过wx.request向服务器发起一个请求去拉取首页的商品列表数据,等待wx.request的success回调之后把数据通过setData渲染到界面上 当用户退出小程序再进来,界面仍然会有白屏现象,因为我们需要等待拉取商品列表的请求回来才能渲染商品列表。当然我们还可以再做一些体验上的优化,例如在发请求前,可能我们会在界面上显示一个Loading提示用户在加载中,但是并没有解决这个延迟渲染的现象,这个时候我们可以利用本地缓存来提前渲染界面。 * **提前渲染界面** 在拉取商品列表后把列表存在本地缓存里,在onLoad发起请求前,先检查是否有缓存过列表,如果有的话直接渲染界面,然后等到wx.request的success回调之后再覆盖本地缓存重新渲染新的列表 这种做法可以让用户体验你的小程序时感觉加载非常快,但是你还要留意这个做法的缺点,如果小程序对渲染的数据实时性要求非常高的话,用户看到一个旧数据的界面会非常困惑。因此一般在对数据实时性/一致性要求不高的页面采用这个方法来做提前渲染,用以优化小程序体验。 ### 3.5.4 缓存用户登录态SessionId 通常用户在没有主动退出登录前,用户的登录态会一直保持一段时间(这段时间由开发者根据自己的业务情况定义,为了安全,这段时间不宜设置太长),就无需用户频繁地输入账号密码。如果我们把SessionId记录在Javascript中某个内存变量,当用户关闭小程序再进来小程序时,之前内存的SessionId已经丢失,此时我们就需要利用本地缓存的能力来持久化存储SessionId。 在重新打开小程序的时候,我们把上一次存储的SessionId内容取出来,恢复到内存。 ## 3.6 设备能力 小程序的宿主环境提供了非常多的操作设备能力来帮助用户在特定场景下做高效的输入,例如:扫码、操控蓝牙等等能力。当然也有很多设备能力不是为了解决输入低效问题而设计的,它们更多的是解决用户侧一些体验问题,例如:获取设备网络状态;调整屏幕亮度等等, ### 3.6.1 利用微信扫码能力`wx.scanCode` 为了让用户减少输入,我们可以把复杂的信息编码成一个二维码,利用宿主环境wx.scanCode这个API调起微信扫一扫,用户扫码之后,wx.scanCode的success回调会收到这个二维码所对应的字符串信息。 应用场景: 1. 通过扫商品上的二维码做一个商品展示的小程序 2. 通过扫共享单车上的二维码去开启单车 3. 餐厅点餐的小程序,我们给餐厅中每个餐桌编号1-100号,把这个数字编码到二维码(也可以把其他信息编码进来,如餐桌人数限制,或者灯光调节等等,为了避免复杂,我们这里只考虑餐桌号)中,扫码获得编号之后,就可以知道是哪一桌点的菜,大大提高点餐体验和效率。 ### 3.6.2 获取网络状态`wx.getNetworkType` 我们知道手机连接到互联网有几种方式:Wifi、2G、3G、4G,包括很快到来的5G,每种方式的上传速度和下载速度差异很大,它们的计费方式的差异也导致用户在使用互联网服务的时候有不同的使用习惯。 通过小程序提供的获取网络状态的能力,做一些更友好的体验提示。 ### 3.6.3 动态监听网络状态变化`wx.onNetworkStatusChange` 某些情况下,我们的手机连接到网络的方式会动态变化,例如手机设备连接到一个信号不稳定的Wifi热点,导致手机会经常从Wifi切换到移动数据网络。小程序宿主环境也提供了一个可以动态监听网络状态变化的接口`wx.onNetworkStatusChange`,让开发者可以及时根据网络状况去调整小程序的体验,wx.onNetworkStatusChange这个接口的使用场景留给读者来思考。