Aitter's Blog

小程序开发入门

在体验小程序开发之后,我上线了一个小应用 527菜谱,同时也发现了小程序开发中有各种注意要点,不然一定会踩坑,小程序开发官方文档里已经有很详细说明,这里我对文档中一些要点做以下总结,方便记忆查询

小程序特点

  • 多个页面可共享JS运行环境
  • JS运行与页面渲染分离,提升页面的渲染性能
  • 提供了native的能力(扫码、离线、地图等)
  • 接近原生的用户体验
  • 自带ES6支持
  • wxss可使用 rpx 单位适配不同屏幕
  • 退出小程序后不会立即销毁,会在后台继续跑5分钟,在这期间用户切回小程序时速度快

注册与开发流程

注册流程

  • 申请账号
    账号信息 - 邮箱激活 - 信息登记

  • 设置

    • 关联公众号
    • 绑定微信开放平台账号
    • 获取 appid 与 appSecret
    • 配置服务器域名(最多配置10个,每月最多修改5次)
    • 消息推送
    • 腾讯云

    个人小程序不支持 微信支付、扫普通链接二维码打开小程序、打开网页

开发流程

  • 使用开发者工具新建项目
  • 上传项目发布为 开发版
  • 提交预览版为 审核版
  • 发布审核版为 正式版

开发者工具简介

  • 新建页面:右键新建Page / app.json 中添加页面
  • 自动保存
  • 实时预览
  • 代码自动补全
  • git状态显示
  • 支持特殊场景调试:扫码、微信支付、使用自定参数、进入场景值、转发、中转小程序
  • 小程序开发助手
  • 第三方平台
  • 云测试(直机测试)
  • 运行环境与ES支持情况

原理与框架说明

基本原理

基本框架

  • 基于webview
  • 通过 WeixinJSBridge 实现视图层和逻辑层的通信
  • 每个视图都是独立的Webview
  • 逻辑和UI运行在2个独立的Webview中

通信原理

  • 开发者工具中是基于 window.postMessage
  • IOS中基于window.webkit.messageHandlers.invokeHandler.postMessage
  • Android中基于 WeixinJSCore.invokeHandler
// 发送消息
window.postMessage({ postdata }, "*");
// 接收消息
window.addEventListener("message", receiveMessage, false);

postMessage MDN

工程目录结构

  • wxml 描述界面
  • wxss 描述样式
  • js 处理逻辑
  • json 页面配置
project
├── pages
| ├── index
| | ├── index.json index 页面配置
| | ├── index.js index 页面逻辑
| | ├── index.wxml index 页面结构
| | └── index.wxss index 页面样式表
| └── log
| ├── log.json log 页面配置
| ├── log.wxml log 页面逻辑
| ├── log.js log 页面结构
| └── log.wxss log 页面样式表
├── app.js 小程序逻辑
├── app.json 小程序公共设置
└── app.wxss 小程序公共样式表

对于每个页面下相关配置的四个文件必须具有相同的路径与文件名

应用配置

app.json

// app.json
{
// 定义小程序中有哪些页面
"pages":[
"pages/index/index",
"pages/logs/logs"
],
// 窗口的样式
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "Demo",
"navigationBarTextStyle":"black",
"enablePullDownRefresh": true
},
// 底部tab的内容和样式
"tabBar": {
// tab的具体内容
"list": [
{
"pagePath": "pages/index/index",
"text": "首页"
},
{
"pagePath": "pages/logs/logs",
"text": "日志"
}
]
},
// 设置不同请求的网络超时
"networkTimeout": {
"request": 10000,
"downloadFile": 10000
},
// 是否开启debug模式,开启debug模式之后可以在微信开发者工具的控制台中看到整个APP,以及每个页面的生命周期日志
"debug": true
}

应用注册

App({
// 监听小程序初始化
onLaunch: function(options) {
// Do something initial when launch.
// 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
},
// 监听小程序显示
onShow: function(options) {
// Do something when show.
// 当小程序启动,或从后台进入前台显示,会触发 onShow
},
// 监听小程序隐藏
onHide: function() {
// Do something when hide.
// 当小程序从前台进入后台,会触发 onHide
},
// 错误监听函数
onError: function(msg) {
console.log(msg)
// 当小程序发生脚本错误,或者 api 调用失败时,会触发 onError 并带上错误信息
},
globalData: 'I am global data'
})

页面生命周期

//index.js
Page({
// 页面状态数据
data: {
text: "This is page data."
},
// 页面加载
onLoad: function(options) {
// Do some initialize when page load.
// 一个页面只会调用一次,可以在 onLoad 中获取打开当前页面所调用的 query 参数
},
// 页面初次渲染完成
onReady: function() {
// Do something when page ready.
// 一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交
},
// 页面显示
onShow: function() {
// Do something when page show.
// 每次打开页面都会调用一次
},
// 页面隐藏
onHide: function() {
// Do something when page hide.
// 当 navigateTo 或底部 tab 切换时调用
},
// 页面卸载
onUnload: function() {
// Do something when page close.
// 当 redirectTo 或 navigateBack 的时候调用
},
// 监听 下拉刷新 事件
onPullDownRefresh: function() {
// Do something when pull down.
// 监听用户下拉刷新事件。
// 需要在 app.json 的 window 选项中或页面配置中开启enablePullDownRefresh。
// 当处理完数据刷新后,wx.stopPullDownRefresh 可以停止当前页面的下拉刷新。
},
// 监听 上拉触底 事件
onReachBottom: function() {
// Do something when page reach bottom.
// 监听用户上拉触底事件。
// 可以在 app.json 的 window 选项中或页面配置中设置触发距离onReachBottomDistance。
// 在触发距离内滑动期间,本事件只会被触发一次
},
// 监听 用户转发 事件
onShareAppMessage: function () {
// return custom share data when user share.
// 只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮
// 用户点击转发按钮的时候会调用
// 此事件需要 return 一个 Object,用于自定义转发内容
},
// 监听 页面滚动 事件
onPageScroll: function() {
// Do something when page scroll
// 返回 { scrollTop } 页面在垂直方向已滚动的距离(单位px)
},
// 监听 Tab 标签的点击事件
onTabItemTap(item) {
console.log(item.index)
console.log(item.pagePath)
console.log(item.text)
},
// Event handler.
viewTap: function() {
this.setData({
text: 'Set some data for updating view.'
}, function() {
// this is setData callback
})
},
customData: {
hi: 'MINA'
}
})

小程序开发注意要点

状态更新

  • 状态取值与更新类似于 React
  • 必须调用 this.setData 去更新状态
  • 必须使用 this.data.xxx 去获取状态值
  • 单次设置数据不能超过 1024kb
  • 状态的值不能是 undefined

路由跳转

wxml模板内用法

<navigator url = "../aaa/aaa">跳转到新页面</navigator>
<navigator url = "../aaa/aaa" open-type = "redirect">跳转到新页面</navigator>
<navigator url = "../aaa/aaa" open-type = "switchTab">跳转到新页面</navigator>

js用法

//保留当前页面,跳转到应用内的某个页面(最多打开10个页面,之后按钮就没有响应的)
wx.navigateTo({
url:"../aaa/aaa"
})
//关闭当前页面,跳转到应用内的某个页面(这个跳转有个坑,就是跳转页面后页面会闪烁一下)
wx.redirectTo({
url:"../aaa/aaa"
})
//跳转至指定页面并关闭其他打开的所有页面(这个最好用在返回至首页的的时候)
wx.reLaunch({
url:'../index/index'
})
//跳转到tabBar页面,并关闭其他所有tabBar页面
wx.switchTab({
url:"/pages/aaa/aaa"
})
//返回上一页面或多级页面
wx.navigateBack({
delta:1
})

配置在TabBar中的页面不能使用 Navigate 跳转,只能使用 wx.switchTab 跳转

页面底部的 tabBar 由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar

调用页面路由带的参数可以在目标页面的onLoad中获取

数据绑定

需要使用花括号

组件属性
<view id="item-{{id}}"> </view>
控制属性
<view wx:if="{{condition}}"> </view>
关键字
<checkbox checked="{{false}}"> </checkbox>
嵌入式样式
<view style="color:{{color}};" />

列表渲染

<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName" wx:key="idx">
{{idx}}: {{itemName.message}}
</view>
  • wx:for 指定遍历的数组
  • wx:for-indx 指定当前的索引
  • wx:for-item 指定当前的遍历的数据
  • wx:key 指定列项唯一标识

wx:key 可以取值 `this` 代表循环项本身*

<switch wx:for="{{numberArray}}" wx:key="*this" style="display: block;"> {{item}} </switch>

条件渲染

<view wx:if="{{length > 5}}"> 1 </view>
<view wx:elif="{{length > 2}}"> 2 </view>
<view wx:else> 3 </view>
<view hidden={{true}}> 3 </view>

事件绑定与传参

  • bind:tap 不会阻止冒泡事件向上冒泡
  • catch:tap 可以阻止冒泡事件向上冒泡
  • capture-bind:touchstart 触摸类事件支持捕获阶段 事件可继续传递
  • capture-catch:touchstart 触摸类事件支持捕获阶段 捕获事件不能继教传递

事件参数只能使用 dataset 传递,data- 后面不能有大写,大写会被自动转为小写,有连字符,会被转成驼峰

<view data-alpha-beta="1" data-alphaBeta="2" bindtap="bindViewTap"> DataSet Test </view>
Page({
bindViewTap:function(event){
event.target.dataset.alphaBeta === 1 // - 会转为驼峰写法
event.target.dataset.alphabeta === 2 // 大写会转为小写
}
})

模板

使用 name 定义模板名称,使用 is 引用模板,使用 data 接收数据

<template name="msgItem">
<view>
<text> {{index}}: {{msg}} </text>
<text> Time: {{time}} </text>
</view>
</template>
<import src="../../components/msgItem/msgItem.wxml"/>
<template is="msgItem" data="{{...item}}"/>
<import src="../../components/even/even.wxml"/>
<import src="../../components/odd/odd.wxml"/>
<block wx:for="{{[1, 2, 3, 4, 5]}}">
<template is="{{item % 2 == 0 ? 'even' : 'odd'}}"/>
</block>

引用模板的同时,如果有想着wxss,也要同时引用

模板的引用可以使用两种方式 importinclude

include 导入纯模板,不含逻辑,相于拷贝

WXS

WXS(WeiXin Script) 是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构

  • wxs 不依赖于运行时的基础库版本,可以在所有版本的小程序中运行。
  • wxs 与 javascript 是不同的语言,有自己的语法,并不和 javascript 一致。
  • wxs 的运行环境和其他 javascript 代码是隔离的,wxs 中不能调用其他 javascript 文件中定义的函数,也不能调用小程序提供的API。
  • wxs 函数不能作为组件的事件回调。
  • 由于运行环境的差异,在 iOS 设备上小程序内的 wxs 会比 javascript 代码快 2 ~ 20 倍。在 android 设备上二者运行效率无差异。

个人应用较少,详情查看 文档

WXSS

  • 使用 rpx 单位做屏幕适配,以 750 宽度做为参考
    如在 iphone6 下,屏幕宽度为 375, 750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素
  • 使用 @import 导入wxss,只支持相对路径

选择器支持

自定义组件

组件类似于页面,由 .js .wxml .wxss 组成

组件可以设置 slot 类似于 vue

Component({
// 组件的配置
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
},
// 组件的属性 类似于 Vue 的 props
properties: {
placeholder: {
type: String,
value: '请输入关键词搜索'
},
myProperty2: String // 简化的定义方式
/* ... */
},
data: {/* 私有数据 */},
methods: { /* ... */ }
})

属性名使用规则

  • 属性名如果是 驼峰写法(propertyName),使用时应使用 连字符写法 (property-name)
  • 属性名不能以 data 开头,会被当作 data-xxx 做为dataset 处理
  • 属性名不能与 data 中的字段冲突

组件模板

<view class="wrapper">
<slot name="before"></slot>
<view>这里是组件的内部细节</view>
<slot name="after"></slot>
</view>

组件样式

#a { } /* 在组件中不能使用 */
[a] { } /* 在组件中不能使用 */
button { } /* 在组件中不能使用 */
.a > .b { } /* 除非 .a 是 view 组件节点,否则不一定会生效 */

公共样式 app.wxss 以及 组件所在的页面样式 对组件本身无效

组件事件

// 绑定自定义事件
<component-tag-name bind:myevent="onMyEvent" />

触发自定义事件

<button bindtap="onTap">点击这个按钮将触发“myevent”事件</button>

使用 this.triggerEvent 触发自定义事件

Component({
properties: {}
methods: {
onTap: function(){
var myEventDetail = {} // detail对象,提供给事件监听函数
var myEventOption = {} // 触发事件的选项
this.triggerEvent('myevent', myEventDetail, myEventOption)
}
}
})

下面两个特性由于没有实践过,这里一笔带过,详情请查看相关文档

  • 使用 behaviors 实现类似 mixins 的功能
  • 使用 relations 关联其它组件

渲染HTML

分包加载

目前小程序分包大小有以下限制

  • 整个小程序所有分包大小不超过 4M
  • 单个分包/主包大小不能超过 2M

分包后,小程序默认先下载主包,当访问到分包的内容后,再异步加载分包展示

app.josn 中配置分包

{
"pages":[
"pages/index",
"pages/logs"
],
"subPackages": [
{
"root": "packageA",
"pages": [
"pages/cat",
"pages/dog"
]
}, {
"root": "packageB",
"pages": [
"pages/apple",
"pages/banana"
]
}
]
}

分包的目录结构与 app.json 中一一对应

├── app.js
├── app.json
├── app.wxss
├── packageA
│ └── pages
│ ├── cat
│ └── dog
├── packageB
│ └── pages
│ ├── apple
│ └── banana
├── pages
│ ├── index
│ └── logs
└── utils

打包原则

  • 声明 subPackages 后,将按 subPackages 配置路径进行打包,subPackages 配置路径外的目录将被打包到 app(主包) 中
  • app(主包)也可以有自己的 pages(即最外层的 pages 字段)
  • subPackage 的根目录不能是另外一个 subPackage 内的子目录
  • 首页的 TAB 页面必须在 app(主包)内

分包与分包之间资源相互隔离,不能相互引用,但可以引用 app 及公共资源

兼容

旧版微信客户端不兼容小程序新功能

  • 接口判断 if(wx.openBluetoothAdapter) {}

  • 参数判断 if(wx.canIUse('showModal.cancel')){}

  • 组件判断 Page({ data: {canIUse: wx.canIUse('cover-view')) })

通过 wx.getSystemInfo 或者 wx.getSystemInfoSync 获取到小程序的基础库版本号

数据统计

在小程序中通过 小程序数据助手 应用可以查看运营相关数据

生态

模板源码

开发框架

Vue风格的小程序开发框架

腾讯云

小程序开发套餐

小程序的一些限制

  • 安卓端暂时不支持 Promise,使用引用第三方 Promise
  • WebView 渲染并非原生渲染
  • 不能直接扩展原生组件
  • 不能操作 cookie
  • 依赖于浏览器环境的JS库不能使用,没有 windowdocument 对象
  • 小程序不能与公众号重名
  • 接口的合法域名不能超过10个(Nginx 转发)
  • 并发请求数不能超过5个(websocket
  • 页面层级不能超过10层
  • 编译打包后的程序包不能超过 4M (图片和视频使用CDN资源)
  • wxss样式中不能使用本地图片,wxml中可以使用本地图片资源
  • 无法访问真实的DOM节点
  • 无法绑定原生事件
  • 更新发布需要提交审核
  • 本地数据存储的大小限制为 10MB
  • 同时只能打开 10 个页面,超过时 wx.navigateTo 不能正常打开新页面
  • 输入组件,只能通过 bind:blur 事件获取用户输入
  • 无法直接渲染原生的HTML(使用 wxParse 插件)
  • 样式不支持级联选择器
  • 不支持本地图片资源(支持网络图片或base64)
  • 不支持 http, 只支持 https
  • 原生组件层级最高,且无法使用css动画
  • 省市区不支持港澳台选择
  • 只能使用微信支付
  • 本地文件存储的大小限制为 10M

由于小程序更新频繁,以上说明可能并不一定准确(2018.1.25)