小程序01 入门

准备工作

申请账号

点击 https://mp.weixin.qq.com/wxopen/waregister?action=step1 申请账号,拥有自己的小程序账号

安装开发工具

前往 开发者工具下载页面 下载开发工具并安装

app.json

app.json扮演的了静态配置的角色,在小程序运行之前就决定了小程序一些表现,需要注意的是小程序是无法在运行过程中去动态更新JSON 配置文件从而发生对应的变化的。

{
  "pages":[
    "pages/index/index",
    "pages/logs/logs"
  ],
  "window":{
    "backgroundTextStyle":"light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "WeChat",
    "navigationBarTextStyle":"black"
  }
}
配置IDE

小程序的WXML文件相当于HTML文件,WXSS文件相当于CSS文件,所以,在webstorm的setting中找到File TypesCascading Style Sheet,添加*.wxss

同理,在File TypesHTML,添加*.wxml

Github上有人编写了wx的语法提醒,下载下来,然后在webStorm 的 File -> import settings 中导入即可

WXML

WXML 全称是 WeiXin Markup Language,是小程序框架设计的一套标签语言,结合小程序的基础组件、事件系统,可以构建出页面的结构。

数据绑定

使用{{}}绑定数据,在JS中通过data属性传入数据

<text>当前时间:{{time}}</text>
<text data-test="{{test}}"> hello world</text>

支持在模版内进行逻辑运算、字符串拼接

条件逻辑

条件逻辑使用wx:if/wx:elif/wx:else

<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>
列表渲染

使用wx:for进行列表渲染,默认数组的当前项的下标变量名默认为index,数组当前项的变量名默认为item

<view wx:for="{{arr}}">{{index}} ---- {{item}}</view>

可以通过wx:for-indexwx:for-item指定数组当前下标的变量名

<view wx:for="{{arr}}" wx:for-index="idx" wx:for-item="itemName">
  {{idx}} ---- {{itemName}}
</view>

为保证列表的可复用性,增加wx:key来作为列表项目的唯一标识符。

<switch wx:for="{{objectArray}}" wx:key="unique" > {{item.id}} </switch>

注意,wx:for后面变量有{{}},而wx:for-indexwx:for-itemwx:key后面变量没有{{}}

模版

将内容用<template>标签包裹起来,并增加name属性,<template>中内容不会显示,会在使用<template>标签增加对应的is属性时使用

<template name="test">模板内容<template>

<template is="test"></tempate>

可以对is属性后面绑定数据进行动态模板渲染。

引用

引用的方式有两种:importinclude

使用import引用时不会递归引用,就是说C引用B,B引用A,在C中可以使用B定义的template,在B中可以使用A定义的template,但是C不能使用A定义的template

<import src="item.wxml"/>

<template is="item" data="{{text: 'forbar'}}"/>

使用include应用时,会将目标文件中除了<template/>``<wxs/>外的整个代码引入,相当于是拷贝到include位置

<!-- index.wxml -->
<include src="header.wxml"/>

<view> body </view>

<include src="footer.wxml"/>
共同属性

所有wxml标签都支持的一些属性:
- id
- class
- style
- hidden,布尔值,组件是否显示,默认显示
- data-*,自定义属性,组件上触发的事件会发送给事件处理函数
- bind*/catch*:绑定事件

WXSS

WXSS(WeiXin Style Sheets)是一套用于小程序的样式语言,用于描述WXML的组件样式,与CSS类似。为了更适合小程序开发,WXSS对CSS做了一些补充以及修改。

组成

image

  • 项目公共样式:位于根目录的app.wxss,它会被注入到小程序的每个页面
  • 页面样式:与app.json注册过的页面同名且位置同级的WXSS文件
  • 其它样式:其它样式可以被项目公共样式和页面样式引用,
尺寸单位

WXSS引入了rpx(responsive pixel)作为尺寸单位,可以适配不同宽度屏幕。小程序编译后,会将rpx换算为px,换算是以375个物理像素为基准,也就是在一个宽度为375物理像素的屏幕下,1rpx = 1px。

举个例子:iPhone6屏幕宽度为375px,共750个物理像素,那么1rpx = 375 / 750 px = 0.5px。

引用

可以使用@import实现样式的引用

@import './test_0.wxss'

由于WXSS最终会被编译打包到目标文件中,用户只需要下载一次,在使用过程中不会因为样式的引用而产生多余的文件请求。

内联样式

WXSS内联样式与Web开发一致:

<!--index.wxml-->

<!--内联样式-->
<view style="color: red; font-size: 48rpx"></view>

小程序支持动态更新内联样式

选择器

目前支持的选择器有:
- id选择器
- 类选择器
- 元素选择器
- 伪元素选择器::after/::before

WXSS选择器权重与CSS类似

官方样式库

WeUI.wxss是官方提供的基础样式库,与微信原生视觉体验一致

JavaScript脚本

组成

小程序中的JavaScript包含三个部分:
1. ECMAScript
2. 小程序框架
3. 小程序API

与浏览器中运行的JavaScript没有BOM和DOM对象。

小程序执行环境

小程序可以运行在三个平台:

  1. iOS平台,包括iOS9、iOS10、iOS11
  2. Android平台
  3. 小程序IDE

在小程序中, iOS9和iOS10所使用的运行环境并没有完全的兼容到ES6标准,小程序IDE提供语法转码工具帮助开发者,将ES6代码转ES5代码(开发者需要在项目设置中,勾选 ES6 转 ES5 开启此功能)

模块化

小程序提供了类似webpack的模块化机制

执行顺序

小程序的执行的入口文件是app.js 。并且会根据其中require的模块顺序决定文件的运行顺序

app.js执行结束后,小程序会按照开发者在app.json中定义的pages的顺序,逐一执行。

作用域

同浏览器中运行的脚本文件有所不同,小程序的脚本的作用域同NodeJS更为相似。

在文件中声明的变量和函数只在该文件中有效,不同的文件中可以声明相同名字的变量和函数,不会互相影响。

当需要使用全局变量的时,通过使用全局函数getApp()获取全局的实例,并设置相关属性值,来达到设置全局变量的目的

// 获取全局变量
var global = getApp()
global.globalValue = 'globalValue'

当需要保证全局的数据可以在任何文件中安全的被使用到,那么可以在App()中进行设置,

// app.js
App({
  globalData: 1
})
// a.js
// 局部变量
var localValue = 'a'

// 获取 global 变量
var app = getApp()

// 修改 global 变量
app.globalData++  // 执行后 globalData 数值为 2

理解小程序宿主环境

渲染层和逻辑层

小程序的运行环境分成渲染层和逻辑层了,渲染层渲染数据,逻辑层在Page实例里产生、处理数据(setData)

通信模型

小程序的渲染层和逻辑层分别由2个线程管理:
- 渲染层的界面使用了WebView进行渲染
- 逻辑层采用JsCore线程运行JS脚本

一个小程序存在多个界面,所以渲染层存在多个WebView线程,这两个线程的通信会经由微信客户端(下文中也会采用Native来代指微信客户端)做中转,逻辑层发送网络请求也经由Native转发。
image

数据驱动

和流行的前端框架一样,小程序也是使用数据驱动页面视图的。

逻辑层和渲染层

双线程下的界面渲染

小程序的逻辑层和渲染层是分开的两个线程。

在渲染层,宿主环境会把WXML转化成对应的JS对象,在逻辑层发生数据变更的时候,我们需要通过宿主环境提供的setData方法把数据从逻辑层传递到渲染层,再经过对比前后差异,把差异应用在原来的Dom树上,渲染出正确的UI界面

程序和页面

程序
APP()构造器

宿主环境提供了App()构造器来注册一个程序App,App()必须卸载根目录的app.js里,是一个单利对象,在其他脚本中使用getApp()获取程序实例

var appInstance = getApp()

App()接受一个Object参数:

App({
  onLaunch: function(options) {},
  onShow: function(options) {},
  onHide: function() {},
  onError: function(msg) {},
  globalData: 'I am global data'
})
  • onLaunch:小程序初始化完成后全局执行一次
  • onShow:小程序启动或从后台进入前台显示时触发
  • onHide:小程序从前台进入后台时触发
  • onError:当小程序发生脚本错误,或者API调用失败时,会触发并带上错误信息
  • globalData:将全局变量定义在其中
  • 其他:可以添加任意的函数或数据到Object参数中,在App实例回调用this可以访问

进入小程序之后,用户可以点击左上角的关闭,或者按手机设备的Home键离开小程序,此时小程序并没有被直接销毁,我们把这种情况称为“小程序进入后台状态”,App构造器参数所定义的onHide方法会被调用。

App的生命周期是由微信客户端根据用户操作主动触发的。为了避免程序上的混乱,我们不应该从其他代码里主动调用App实例的生命周期函数

在微信客户端中打开小程序有很多途径:从群聊会话里打开,从小程序列表中打开,通过微信扫一扫二维码打开,从另外一个小程序打开当前小程序等,针对不同途径的打开方式,小程序有时需要做不同的业务处理,所以微信客户端会把打开方式带给onLaunchonShow的调用参数options

需要留意小程序的宿主环境在迭代更新过程会增加不少打开场景,因此要获取最新的场景值说明请查看官方文档

小程序全局数据

小程序的JS脚本是运行在JsCore的线程里,小程序的每个页面各自有一个WebView线程进行渲染,所以小程序切换页面时,小程序逻辑层的JS脚本运行上下文依旧在同一个JsCore线程中。

所有页面的脚本逻辑都跑在同一个JsCore线程,所以页面使用setTimeout或者setInterval的定时器,然后跳转到其他页面时,这些定时器并没有被清除,需要开发者自己在页面离开的时候进行清理。

页面
组成

一个页面是分三部分组成:
- 界面:界面由WXML文件和WXSS文件来负责描述
- 配置:由JSON文件进行描述
- 逻辑:页面逻辑则是由JS脚本文件负责

一个页面的文件需要放置在同一个目录下,其中WXML文件和JS文件是必须存在的,JSON和WXSS文件是可选的。

页面路径需要在小程序代码根目录app.json中的pages字段声明,否则这个页面不会被注册到宿主环境中。默认pages字段的第一个页面路径为小程序的首页。

Page()构造器

宿主环境提供了Page()构造器用来注册一个小程序页面,Page()在页面脚本page.js中调用,接受一个Object参数

Page({
  data: { text: "This is page data." },
  onLoad: function(options) { },
  onReady: function() { },
  onShow: function() { },
  onHide: function() { },
  onUnload: function() { },
  onPullDownRefresh: function() { },
  onReachBottom: function() { },
  onShareAppMessage: function () { },
  onPageScroll: function() { }
})
  • data:页面的初始数据
  • 生命周期函数:
    • onLoad:监听页面加载,触发时机早于onShowonReady,在页面没被销毁之前只会触发1次,在回调中可以获取当前页面所调用的打开参数option
    • onShow:监听页面显示,触发事件早于onReady,一般从别的页面返回到当前页面时,都会被调用。
    • onReady:监听页面初次渲染完成,在页面没被销毁前只会触发1次,onReady触发时,表示页面已经准备妥当,在逻辑层就可以和视图层进行交互了。
    • onHide:监听页面隐藏,会在使用wx.naviagteTo切换到其他页面、底部tab切换时触发。
    • onUnload:监听页面卸载,使用wx.redirectTowx.navigateBack返回到其他页时,当前页面会被微信客户端销毁回收
  • 页面事件函数:
    • onPullDownRefresh:监听用户下拉动作,需要在app.jsonwindow选项中或页面配置page.json中设置enablePullDownRefreshtrue。当处理完数据刷新后,wx.stopPullDownRefresh可以停止当前页面的下拉刷新。
    • onReachBottom:页面上拉触底事件的处理函数,可以在app.jsonwindow选项中或页面配置page.json中设置触发距离onReachBottomDistance。在触发距离内滑动期间,本事件只会被触发一次。
    • onShareAppMessage:用户点击右上角转发,只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮,在用户点击转发按钮的时候会调用,此事件需要return一个Object,包含titlepath两个字段,用于自定义转发内容,
    • onPageScroll:页面滚动触发事件的处理函数,参数为Object,包含scrollTop字段,表示页面在垂直方向已滚动的距离(单位px)。
  • 其他:可以添加任意的函数或数据,在Page实例的其他函数中用this可以访问

Page的生命周期是由微信客户端根据用户操作主动触发的。为了避免程序上的混乱,我们不应该在其他代码中主动调用Page实例的生命周期函数。

setData()方法

setData是一个异步函数,第二个参数是一个callback回调,在这次setData对界面渲染完毕后触发。

// page.js
Page({
  onLoad: function(){
    this.setData({
      text: 'change data'
    }, function(){
      // 在这次setData对界面渲染完毕后触发
    })
  }
})

data中的key还可以非常灵活,以数据路径的形式给出,例如:

this.setData({"d[0]": 100});
this.setData({"d[1].text": 'Goodbye'});

我们只要保持一个原则就可以提高小程序的渲染性能:每次只设置需要改变的最小单位数据

此外需要注意以下3点:

  • 直接修改Page实例的this.data而不调用this.setData是无法改变页面的状态的,还会造成数据不一致。
  • 由于setData是需要两个线程的一些通信消耗,为了提高性能,每次设置的数据不应超过1024kB。
  • 不要把data中的任意一项的value设为undefined,否则可能会有引起一些不可预料的bug。
页面跳转

小程序把页面的打开路径定义成页面URL,其组成格式和网页的URL类似,在页面Page构造器里onLoadoption可以拿到当前页面的打开参数,其类型是一个Object,其键值对与页面URL上query键值对一一对应

和网页URL一样,页面URL上的value如果涉及特殊字符(例如:&字符、?字符、中文字符等),需要采用UrlEncode后再拼接到页面URL上。

通过wx.navigateTo推入一个新的页面,在首页使用2次wx.navigateTo后,页面层级会有三层,我们把这样的一个页面层级称为页面栈。

wx.navigateTo({url: '/pages/index/index'})

image

我们采用这样的方式进行描述页面栈:[pageA, pageB, pageC],其中pageA在最底下,pageC在最顶上,也就是用户所看到的界面

  • 使用wx.navigateTo({ url: 'pageD' })可以往当前页面栈多推入一个pageD,此时页面栈变成[pageA, pageB, pageC, pageD]
  • 使用wx.navigateBack()可以退出当前页面栈的最顶上页面,此时页面栈变成[pageA, pageB, pageC]
  • 使用wx.redirectTo({ url: 'pageE' })是替换当前页变成pageE,此时页面栈变成[pageA, pageB, pageE ],当页面栈到达10层没法再新增的时候,往往就是使用redirectTo这个API进行页面跳转。

小程序提供了原生的Tabbar支持,我们可以在app.json声明tabBar字段来定义Tabbar

{
  "tabBar": {
    "list": [
      { "text": "Tab1", "pagePath": "pageA" },
      { "text": "Tab1", "pagePath": "pageF" },
      { "text": "Tab1", "pagePath": "pageG" }
    ]
  }
}

wx.switchTab({ url: 'pageF' }),此时原来的页面栈会被清空(除了已经声明为TabbarpageA外其他页面会被销毁),然后会切到pageF所在的tab页面,页面栈变成 [pageF],此时点击Tab1切回到pageA时,pageA不会再触发onLoad,因为pageA没有被销毁。

wx.navigateTowx.redirectTo只能打开非TabBar页面,wx.switchTab只能打开Tabbar页面。

总结一下,切换路由的方式有:
1. navigateTo
2. navigateBack
3. redirectTo
4. switchTab

前三者只能打开非TabBar页面,最后一个只能打开Tabbar页面。

image

image

注意Tabbar页面初始化之后不会被销毁。

组件

提供一些现成的组件,具体参考:https://developers.weixin.qq.com/miniprogram/dev/component/

API

宿主环境提供了丰富的API,可以很方便调起微信提供的能力。几乎所有小程序的API都挂载在wx对象底下(除了Page/App等特殊的构造器)

小程序提供的API按照功能主要分为几大类:网络、媒体、文件、数据缓存、位置、设备、界面、界面节点信息还有一些特殊的开放接口,API一般调用的约定:

  1. wx.on*开头的API是监听某个事件发生的API接口,接受一个Callback函数作为参数。当该事件触发时,会调用Callback函数。
  2. 如未特殊约定,多数API接口为异步接口 ,都接受一个Object作为参数。
  3. API的Object参数一般由successfailcomplete三个回调来接收接口调用结果。
  4. wx.get*开头的API是获取宿主环境数据的接口。
  5. wx.set*开头的API是写入数据到宿主环境的接口。

有部分API会拉起微信的原生界面,此时会触发PageonHide方法,当用户从原生界面返回到小程序时,会触发PageonShow方法。

API列表:https://developers.weixin.qq.com/miniprogram/dev/api/

事件

事件类型

常见的事件类型
- touchstart
- touchmove
- touchcancel:手指触摸动作被打断,如来电提醒,弹窗
- touchend
- tap:手指触摸后马上离开
- longpress:手指触摸后,超过350ms再离开,如果指定了事件回调函数并触发了这个事件,tap事件将不被触发
- longtap:(推荐使用longpress事件代替)
- transitionend:会在WXSS transition或wx.createAnimation动画结束后触发
- animationstart
- animationiteration:会在一个 WXSS animation 一次迭代结束时触发
- animationend

事件的回调函数会受到一个事件对象,具备以下属性:
- type
- timeStamp:页面打开到触发事件所经过的毫秒数
- target:触发事件的组件的一些属性值集合
- id
- tagName
- dataset:当前组件上由data-开头的自定义属性组成的集合
-
currentTarget:当前组件的一些属性值集合
-
detail:额外的信息
-
touches:触摸事件,当前停留在屏幕中的触摸点信息的数组
-
identifier:触摸点的标识符
-
pageX,pageY:距离文档左上角的距离,文档的左上角为原点 ,横向为X轴,纵向为Y轴
-
clientX,clientY:距离页面可显示区域(屏幕除去导航条)左上角距离,横向为X轴,纵向为Y轴
-
changedTouches`:触摸事件,当前变化的触摸点信息的数组

事件绑定

bindcatch开头,在基础库1.5版本后,bindcatch后面可以加一个冒号,后面是事件类型,如bind:tapcatch:touchstart

同时bindcatch前还可以加上capture-来表示捕获阶段。

image

以下示例中,点击inner view会先后调用handleTap1handleTap2handleTap3handleTap4

<view id="outer" bind:tap="handleTap4" capture-bind:tap="handleTap1">
  outer view
  <view id="inner" bind:tap="handleTap3" capture-bind:tap="handleTap2">
    inner view
  </view>
</view>

bind不会阻止事件冒泡,catch会阻止冒泡

如果将以上代码的capture-bind:tap="handleTap1"改成capture-catch:tap="handleTap1",点击inner view只会触发handleTap1(catch事件阻止了tap事件捕获和冒泡)。

除上面列举的事件类型之外的其他组件自定义事件,如无特殊声明都是非冒泡事件,如<form/>submit事件,<input/>的input事件,<scroll-view/>scroll事件(即旨在本身触发一次,不会向内或向外传播)。

兼容

可以使用wx.getSystemInfo或者wx.getSystemInfoSync来获取手机品牌、操作系统版本号、微信版本号以及小程序基础库版本号等,通过这个信息,我们可以针对不同平台做差异化的服务。

wx.getSystemInfoSync()
/*
  {
    brand: "iPhone",      // 手机品牌
    model: "iPhone 6",    // 手机型号
    platform: "ios",      // 客户端平台
    system: "iOS 9.3.4",  // 操作系统版本
    version: "6.5.23",    // 微信版本号
    SDKVersion: "1.7.0",  // 小程序基础库版本
    language: "zh_CN",    // 微信设置的语言
    pixelRatio: 2,        // 设备像素比
    screenHeight: 667,    // 屏幕宽度
    screenWidth: 375,     // 屏幕高度
    windowHeight: 667,    // 可使用窗口宽度
    windowWidth: 375,     // 可使用窗口高度
    fontSizeSetting: 16   // 用户字体大小设置
  }
 */

可以通过判断此API是否存在来做程序上的兼容。

if (wx.openBluetoothAdapter) {
  wx.openBluetoothAdapter()
} else {
  // 如果希望用户在最新版本的客户端上体验您的小程序,可以这样子提示
  wx.showModal({
    title: '提示',
    content: '当前微信版本过低,无法使用该功能,请升级到最新微信版本后重试。'
  })
}

小程序还提供了wx.canIUse这个API,用于判断接口或者组件在当前宿主环境是否可用,其参数格式为: ${API}.${method}.${param}.${options}或者${component}.${attribute}.${option}

各个段的含义如下:

  • ${API} 代表 API 名字
  • ${method} 代表调用方式,有效值为return, success, object, callback
  • ${param} 代表参数或者返回值
  • ${options} 代表参数的可选值
  • ${component} 代表组件名字
  • ${attribute} 代表组件属性
  • ${option} 代表组件属性的可选值

调用的示例代码如下。

// 判断接口及其参数在宿主环境是否可用
wx.canIUse('openBluetoothAdapter')
wx.canIUse('getSystemInfoSync.return.screenWidth')
wx.canIUse('getSystemInfo.success.screenWidth')
wx.canIUse('showToast.object.image')
wx.canIUse('onCompassChange.callback.direction')
wx.canIUse('request.object.method.GET')

 // 判断组件及其属性在宿主环境是否可用
wx.canIUse('contact-button')
wx.canIUse('text.selectable')
wx.canIUse('button.open-type.contact')

我们可以选择合适的判断方法来做小程序的向前兼容,以保证我们的小程序在旧版本的微信客户端也能工作正常。

在不得已的情况下(小程序强依赖某个新的API或者组件时),还可以通过在小程序管理后台设置“基础库最低版本设置”来达到不向前兼容的目的。例如你选择设置你的小程序只支持1.5.0版本以上的宿主环境,那么当运行着1.4.0版本宿主环境的微信用户打开你的小程序的时候,微信客户端会显示当前小程序不可用,并且提示用户应该去升级微信客户端。

场景应用

开发流程

  1. 首先通过交互图或者收稿描绘小程序界面交互和界面间的跳转关系
  2. 紧接着,我们优先完成WXML+WXSS还原设计稿,把界面涉及到的元素和视觉细节先调试完成
  3. 最后我们把按照页面交互梳理出每个页面的data部分,填充WXML的模板语法,还有完成JS逻辑部分。

布局

建议使用flex布局

截面常见的交互反馈

触摸反馈

点击按钮时需要给一个反馈:小程序的view容器组件和button组件提供了hover-class属性,触摸时会往该组件加上对应的class改变组件的样式。

/*page.wxss */
.hover{
  background-color: gray;
}

<!--page.wxml -->
<button type="default" hover-class="hover"> 点击button </button>
<view hover-class="hover"> 点击view</view>

button组件可以增加loading属性,在按钮文字前出现loading,让用户感觉操作会比较耗时:

<!--page.wxml -->
<button loading="{{loading}}" bindtap="tap">操作</button>

// page.js
Page({
  data: { loading: false }
  tap: function() {
    // 把按钮的loading状态显示出来
    this.setData({
      loading: true
    })
    // 接着做耗时的操作
  }
})
Toast和模态框

Toast告知用户操作成功并且不打断接下来的操作,默认1.5s后消失

wx.showToast({
  title: '已发送',
  icon: 'success',
  duration: 1599
})

特别要注意,我们不应该把Toast用于错误提示,因为错误提示需要明确告知用户具体原因,因此不适合用这种一闪而过的Toast弹出式提示。一般需要用户明确知晓操作结果状态的话,会使用模态对话框来提示,同时附带下一步操作的指引。

wx.showModal({
  title: '标题',
  content: '告知当前状态,信息和解决方法',
  confirmText: '主操作',
  cancelText: '次要操作',
  success: function(res) {
    if (res.confirm) {
      console.log('用户点击主操作')
    } else if (res.cancel) {
      console.log('用户点击次要操作')
    }
  }
})
界面滚动

下拉整个界面刷新:需要通过配置开启当前页面的下拉刷新,用户往下拉动界面触发下拉刷新操作时,Page构造器的onPullDownRefresh回调会被触发,

//page.json
{
  "enablePullDownRefresh": true
}

//page.js
Page({
  onPullDownRefresh: function() {
    // 用户触发了下拉刷新操作
    // 拉取新数据重新渲染界面
    // wx.stopPullDownRefresh() // 可以停止当前页面的下拉刷新。
  }
})

上拉触底:

//page.json
// 界面的下方距离页面底部距离小于onReachBottomDistance像素时触发onReachBottom回调
{
  "onReachBottomDistance": 100
}

//page.js
Page({
  onReachBottom: function() {
    // 当界面的下方距离页面底部距离小于100像素时触发回调
  }
})

页面中某一小块区域需要可滚动需要使用scroll-view可滚动视图组件。可以通过组件的scroll-xscroll-y属性决定滚动区域是否可以横向或者纵向滚动

网络请求
wx.request接口

发起网络请求使用wx.request接口

wx.request({
  url: 'https://test.com/getinfo',
  success: function(res) {
    console.log(res) // 服务器回包信息
  }
})

参数:
- url:接口地址
- data:请求的参数
- header:设置请求的header,header中不能设置Referer,默认header['content-type'] = 'application/json'
- method:(需大写)有效值:OPTIONS, GET(默认), HEAD, POST, PUT, DELETE, TRACE, CONNECT
- dataType:回包的内容格式,如果设为json,会尝试对返回的数据做一次 JSON解析
- success
- fail
- complete

小程序宿主环境要求request发起的网络请求必须是https协议请求

请求参数

请求参数有两种形式:直接在url中增加参数或通过data参数

// 通过url参数传递数据
wx.request({
  url: 'https:/ / test.com / getinfo ? id = 1 & version = 1.0.0 ',
  success: function(res) {
    console.log(res) // 服务器回包信息
  }
})

// 通过data参数传递数据
wx.request({
  url: '
https : //test.com/getinfo',
  data: {
    id: 1,
    version: '1.0.0'
  },
  success: function(res) {
    console.log(res) // 服务器回包信息
  }
})

有时候需要传一些比较复杂的数据结构到后台的时候,用JSON格式会更加合适。此时我们可以在wx.requestheader参数设置content-type头部为application/json,小程序发起的请求的包体内容就是data参数对应的JSON字符串

收到回包

回调函数中的参数信息包含的字段:
- data: 服务器返回数据
- statusCode:开发者服务器返回的 HTTP 状态码
- header:开发者服务器返回的 HTTP Response Header

尤其注意,只要成功收到服务器返回,无论HTTP状态码是多少都会进入success回调。因此开发者自己通过对回包的返回码进行判断后再执行后续的业务逻辑。

success回调的参数data字段类型是根据header['content-type']决定的,默认header['content-type']'application/json',在触发success回调前,小程序宿主环境会对data字段的值做JSON解析,如果解析成功,那么data字段的值会被设置成解析后的Object对象,其他情况data字段都是String类型,其值为HTTP回包包体。

一般使用技巧
设置超时时间:

小程序request默认超时时间是60秒,在小程序项目根目录里边的app.json可以指定request的超时时间。

{
  "networkTimeout": {
    "request"3000
  }
}

wx.request常见的示例代码

var hasClick = false;
Page({
  tap: function() {
    if (hasClick) {
      return
    }
    hasClick = true
    wx.showLoading()
    wx.request({
      url: 'https://test.com/getinfo',
      method: 'POST',
      header: {
        'content-type': 'application/json'
      },
      data: {},
      success: function(res) {
        if (res.statusCode === 200) {
          console.log(res.data) // 服务器回包内容
        }
      },
      fail: function(res) {
        wx.showToast({
          title: '系统错误'
        })
      },
      complete: function(res) {
        wx.hideLoading()
        hasClick = false
      }
    })
  }
})

为了防止用户极快速度触发两次tap回调,我们还加了一个hasClick的“锁”,在开始请求前检查是否已经发起过请求,如果没有才发起这次请求,等到请求返回之后再把锁的状态恢复回去。

排查异常的方法
  1. 检查手机网络状态以及wifi连接点是否工作正常。
  2. 检查小程序是否为开发版或者体验版,因为开发版和体验版的小程序不会校验域名。
  3. 检查对应请求的HTTPS证书是否有效,同时TLS的版本必须支持1.2及以上版本,可以在开发者工具的console面板输入showRequestInfo()查看相关信息。
  4. 域名不要使用IP地址或者localhost,并且不能带端口号,同时域名需要经过ICP备案。
  5. 检查app.json配置的超时时间配置是否太短,超时时间太短会导致还没收到回报就触发fail回调。
  6. 检查发出去的请求是否302到其他域名的接口,这种302的情况会被视为请求别的域名接口导致无法发起请求。
微信登陆

通过wx.loginsuccess中拿到登陆凭证,然后通过wx.request把code传到开发者服务器。如果当前微信用户还没有绑定当前小程序业务的用户身份,那在这次请求应该顺便把用户输入的帐号密码一起传到后台,然后开发者服务器就可以校验账号密码之后再和微信用户id进行绑定,小程序端的示例代码如下所示。

Page({
  tapLogin: function() {
    wx.login({
      success: function(res) {
        if (res.code) {
          wx.request({
            url: 'https://test.com/login',
            data: {
              username: 'zhangsan', // 用户输入的账号
              password: 'pwd123456', // 用户输入的密码
              code: res.code
            },
            success: function(res) {
              // 登录成功
              if (res.statusCode === 200) {
                console.log(res.data.sessionId) // 服务器回包内容
              }
            }
          })
        } else {
          console.log('获取用户登录态失败!' + res.errMsg)
        }
      }
    });
  }
})

res.code就是登陆凭证

开发者的后台就拿到了前边wx.login()所生成的微信登录凭证code,此时就可以拿这个code到微信服务器换取微信用户身份。微信服务器为了确保拿code过来换取身份信息的人就是刚刚对应的小程序开发者,到微信服务器的请求要同时带上AppId和AppSecret,

开发者服务器和微信服务器通信也是通过HTTPS协议,微信服务器提供的接口地址是:https://api.weixin.qq.com/sns/jscode2session?appid=<AppId>&secret=<AppSecret>&js_code=<code>&grant_type=authorization_code

  • openid:微信用户的唯一标识
  • session_key:会话密钥
  • unionid:用户在微信开放平台的唯一标识符。本字段在满足一定条件的情况下才返回。

开发者服务器和开发者的小程序应该也有会话密钥SessionId。用户登录成功之后,开发者服务器需要生成会话密钥SessionId,在服务端保持SessionId对应的用户身份信息,同时把SessionId返回给小程序。小程序后续发起的请求中携带上SessionId,开发者服务器就可以通过服务器端的Session信息查询到当前登录用户的身份,这样我们就不需要每次都重新获取code,省去了很多通信消耗。

本地数据

小程序提供了读写本地数据缓存的接口,通过wx.getStorage/wx.getStorageSync读取本地缓存,通过wx.setStorage/wx.setStorageSync写数据到缓存,其中Sync后缀的接口表示是同步接口,执行完毕之后会立马返回

读取:
wx.getStorage({
  key: 'key1',
  success: function(res) {
    // 异步接口在success回调才能拿到返回值
    var value1 = res.data
  },
  fail: function() {
    console.log('读取key1发生错误')
  }
})


try {
  // 同步接口立即返回值
  var value2 = wx.getStorageSync('key2')
} catch (e) {
  console.log('读取key2发生错误')
}
存储:
// 异步接口在success/fail回调才知道写入成功与否
wx.setStorage({
  key: "key",
  data: "value1"
  success: function() {
    console.log('写入value1成功')
  },
  fail: function() {
    console.log('写入value1发生错误')
  }
})

try {
  // 同步接口立即写入
  wx.setStorageSync('key', 'value2')
  console.log('写入value2成功')
} catch (e) {
  console.log('写入value2发生错误')
}

每个小程序的缓存空间上限为10MB,如果当前缓存已经达到10MB,再通过wx.setStorage写入缓存会触发fail回调。

由于本地缓存是存放在当前设备,用户换设备之后无法从另一个设备读取到当前设备数据,因此用户的关键信息不建议只存在本地缓存,应该把数据放到服务器端进行持久化存储。

利用本地缓存提前渲染截面

讨论一个需求:我们要实现了一个购物商城的小程序,首页是展示一堆商品的列表。一般的实现方法就是在页面onLoad回调之后通过wx.request向服务器发起一个请求去拉取首页的商品列表数据,等待wx.requestsuccess回调之后把数据通过setData渲染到界面上,如下代码所示。

Page({
  onLoad: function() {
    var that = this
    wx.request({
      url: 'https://test.com/getproductlist',
      success: function(res) {
        if (res.statusCode === 200) {
          that.setData({
            list: res.data.list
          })
        }
      }
    })
  }
})

设想一下当用户退出小程序再进来,界面仍然会有白屏现象,因为我们需要等待拉取商品列表的请求回来才能渲染商品列表。当然我们还可以再做一些体验上的优化,例如在发请求前,可能我们会在界面上显示一个Loading提示用户在加载中,但是并没有解决这个延迟渲染的现象,这个时候我们可以利用本地缓存来提前渲染界面。

我们在拉取商品列表后把列表存在本地缓存里,在onLoad发起请求前,先检查是否有缓存过列表,如果有的话直接渲染界面,然后等到wx.requestsuccess回调之后再覆盖本地缓存重新渲染新的列表,如下代码所示。

Page({
  onLoad: function() {
    var that = this
    var list = wx.getStorageSync("list")
    if (list) { // 本地如果有缓存列表,提前渲染
      that.setData({
        list: list
      })
    }
    wx.request({
      url: 'https://test.com/getproductlist',
      success: function(res) {
        if (res.statusCode === 200) {
          list = res.data.list
          that.setData({ // 再次渲染列表
            list: list
          })
          wx.setStorageSync("list", list) // 覆盖缓存数据
        }
      }
    })
  }
})

这种做法可以让用户体验你的小程序时感觉加载非常快,但是你还要留意这个做法的缺点,如果小程序对渲染的数据实时性要求非常高的话,用户看到一个旧数据的界面会非常困惑。因此一般在对数据实时性/一致性要求不高的页面采用这个方法来做提前渲染,用以优化小程序体验。

缓存SessionId

通常用户在没有主动退出登录前,用户的登录态会一直保持一段时间[10],就无需用户频繁地输入账号密码。如果我们把SessionId记录在Javascript中某个内存变量,当用户关闭小程序再进来小程序时,之前内存的SessionId已经丢失,此时我们就需要利用本地缓存的能力来持久化存储SessionId。

利用本地缓存持久存储用户登录态SessionId:

//page.js
varapp = getApp()
Page({
  onLoad: function() {
    // 调用wx.login获取微信登录凭证
    wx.login({
      success: function(res) {
        // 拿到微信登录凭证之后去自己服务器换取自己的登录凭证
        wx.request({
          url: 'https://test.com/login',
          data: {
            code: res.code
          },
          success: function(res) {
            var data = res.data

            // 把 SessionId 和过期时间放在内存中的全局对象和本地缓存里边
            app.globalData.sessionId = data.sessionId
            wx.setStorageSync('SESSIONID', data.sessionId)

            // 假设登录态保持1天
            var expiredTime = +new Date() + 12460601000
            app.globalData.expiredTime = expiredTime
            wx.setStorageSync('EXPIREDTIME', expiredTime)
          }
        })
      }
    })
  }
})

在重新打开小程序的时候,我们把上一次存储的SessionId内容取出来,恢复到内存。

//app.js
App({
  onLaunch: function(options) {

    var sessionId = wx.getStorageSync('SESSIONID')
    var expiredTime = wx.getStorageSync('EXPIREDTIME')
    var now = +new Date()

    if (now - expiredTime <= 12460601000) {
      this.globalData.sessionId = sessionId
      this.globalData.expiredTime = expiredTime
    }
  },

  globalData: {
    sessionId: null,
    expiredTime: 0
  }
})
设备能力
  • wx.scanCode:获取二维码的数据
  • wx.getNetworkType:获取网络状态(networkType字段的有效值:wifi/2g/3g/4g/unknown(Android下不常见的网络类型)/none(无网络)
  • wx.onNetworkStatusChange:可以动态监听网络状态变化的接口

小程序的协同工作和发布

暂时略略略

底层框架

暂时略略略

性能优化

暂时略略略

开发者工具

暂时略略略

参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值