你真的需要微信小程序吗?
首先,在入坑微信小程序之前,你需要想清楚这个问题。
微信小程序的优点有:
- 只需要写前端(Web App 一般需要前、后端),开发周期短
- 跨 Android/iOS 平台
- 有成熟的 WeUI 框架和控件可以使用,并且微信原生 API 非常多,官方文档为中文且很详尽
- 能够轻松做到 Serverless 云开发
- 云服务的各项服务都有免费额度,流量不大的开发者能够白嫖
为数不多但非常致命的缺点是:
- 需要严格审核,甚至于某些功能(如任意用户均可上传的留言板/公开相册/云盘等)无法上架小程序
- 微信小程序 API 随时可能变化,而小程序必须使用最新的 API(而不像
Pypi
、npm
等可以使用指定版本的插件),可能导致原来的程序完全无法工作。如wx.getUserInfo()
函数改版后,在用户未授权过的情况下调用此接口,将不再出现授权弹窗,会直接进入fail
回调(详见《公告》)。
常用文档链接
因为太详尽了,因此本博文很多部分都是直接复制教程。
文件结构和页面组成
文件结构
在开发者工具的编辑器里可以看到小程序源文件的根目录下有 app.js
、app.json
和 app.wxss
app.json
:小程序的公共设置,可以对小程序进行全局配置,决定页面文件的路径、窗口表现、设置多 tab 等;app.wxss
:小程序的公共样式表,可以配置整个小程序的文字的字体、颜色、背景,图片的大小等样式;app.js
:小程序的逻辑(这个可以先放着,不用管)pages
文件夹:这里存放着小程序的所有页面,展开pages
文件夹就可以看到有index
和logs
两个页面文件夹;
页面组成
在每一个页面文件夹里都有四个文件,这四个文件的名称都是一样的,它们分别为:
json
文件,和上面的app.json
作用基本相同,只是app.json
控制的是整个小程序的设置,而页面的json
文件只控制单个页面的配置(因为有时候全局配置就够用了,所以页面配置有时候是空的);wxml
文件(类似于xml
),小程序的页面结构,文字、图片、音乐、视频、地图、轮播等组件都会放在这里;wxss
文件(类似于css
),小程序的页面样式,和app.wxss
一样是控制样式,而页面的wxss
文件是控制单个页面的样式;js
文件,控制小程序页面的逻辑。
app.json
json 基本语法
- 大括号
{}
保存对象; - 中括号
[]
保存数组; - 各个数据之间由英文字符逗号
,
隔开; - 字段名称(属性名)与值之间用
:
隔开,字段名称在前,字段的取值在后; - 字段名称用
""
给包着;
app.json 配置
app.json
的配置文档:https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html
默认的 app.json
如下:
1 | { |
window
是配置全局的页面设置,见 https://developers.weixin.qq.com/miniprogram/dev/reference/configuration/app.html。
pages
则是小程序的页面。默认展示第一项(这里是 pages/index/index
)的内容。在 pages
中新增一项 pages/home/home
,编译后会自动创建 pages/home
文件及页面组成中的四个文件。
wxml 和 wxss
wxml
刚才创建的 pages/home/home.wxml
的语法类似于 xml
。
<text>pages/home/home.wxml</text>
被称作组件。<text>
被称为标签。- 组件需要被开始标签和闭合标签包含,前面的
<text>
是开始标签,后面的</text>
是结束标签。 <view>
组件是可以嵌套写的,如:
1 | <view> |
wxml 模板
WXML 支持模板,即定义好会复用的 WXML 片段,然后在其他地方调用即可。
教程:https://cloudbase.net/community/guides/handbook/tcb09.html
官方文档:https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/import.html
wxss
wxss
的官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html
wxss
美化的知识和 css
相同,css
的参考手册:https://www.w3school.com.cn/cssref/index.asp
和 CSS 相同的知识就放在 CSS 中了。
较 CSS,WXSS 定义了新的单位:rpx
。规定 750rpx
是一屏幕宽。如在 iPhone6(宽度为 375px)上,750rpx = 375px
。
对于全局样式和局部样式(来源):
定义在
app.wxss
中的样式为全局样式,作用于每一个页面。在page
的wxss
文件中定义的样式为局部样式,只作用在对应的页面,并会覆盖app.wxss
中相同的选择器。
链接和图片
链接 navigator
将一个小程序页面链接到另一个小程序页面的组件叫 navigator
。
我们添加一个页面,路径为 /page/imgshow/imgshow
。新的 app.json
部分代码如下:
1 | "pages":[ |
在 /pages/home/home.wxml
加入以下代码:
1 | <view class="index-link"> |
此时编译运行,点击让小程序显示图片
即可跳转到 /pages/imgshow/imgshow.wxml
。
但是这里的 让小程序显示图片
并没有以超链接的形式给出,可以修改 wxss
以更改其样式。在 /pages/imgshow/imgshow.wxss
中加入以下代码:
1 | .item-link{ |
编译。效果如下:
点击蓝色部分即可跳转到对应页面。
图片 image
对于图片,只需要知道怎么插入图片就行了。其他仅作了解,用到的时候再查。
插入图片
先放文档:https://developers.weixin.qq.com/miniprogram/dev/component/image.html
将以下代码复制到 imgshow.wxml
:
1 | <view class="imglist"> |
显示模式
其中,image
组件共有 13 种 mode
(其中 5 种缩放、8 种裁剪。参见 image
的文档)。如想“宽度不变,高度自动变化,保持原图宽高比不变”,则可以修改代码:
1 | <view class="imglist"> |
然后在 imgshow.wxss
给图片添加 wxss
,指定宽度为占满屏幕:
1 | .imglist .imgitem{ |
效果如下:
圆角和阴影
还可以给图片添加圆角和阴影。
1 | .imglist .img{ |
圆形图片
示例 XML:
1 | <view class="imglist"> |
示例 CSS:
1 | .imglist .circle{ |
背景
此外,还支持修改文字的背景。参见 CSS。
卡片风格示例
官方示例,有点好看。代码见网页最后。
WeUI 框架
WeUI 是一套小程序的 UI 框架,所谓 UI 框架就是一套界面设计方案。
有了组件,我们可以用它来拼接出一个内容丰富的小程序,而有了一个 UI 框架,就能让我们的小程序变得更加美观。
体验 WeUI 小程序
这是微信发布的基于 WeUI 的模板小程序。
代码地址:https://github.com/Tencent/weui-wxss
下载以后将 dist
文件夹下的代码导入 微信开发者工具
,运行,即可得到上述效果。
不过,需要注意的是,WeUI 模板小程序中很多元素是使用 view 组件模拟,其效果不如对应的原生组件(如 button
searchbar
tabbar
等)。因此,使用模板前可以在文档中搜索一下是否有对应的原生组件。
使用 WeUI
- 在我们的项目的根目录新建
style
文件夹; - 将模板中的
/dist/style/weui.wxss
导入我们项目中的style
文件夹; - 在我们项目的
app.wxss
添加一行代码:
1 | @import 'style/weui.wxss'; |
- 按照官方代码的形式编写自己的代码,就能渲染成模板小程序的样子。
需要注意的是,这个小程序只使用 view
和 input
控件就渲染了绝大多数页面。包括 button
等也是用 view
渲染的。
不过实际编写中,由于 button
在配合表单等有更多的功能,可以使用 button
,然后使用 WeUI 中的 class
作用于 button
之上。
Flex 布局(1)
Flex 是 Flexible Box 的缩写,意为”弹性布局”,用来为盒状模型提供最大的灵活性。
官方教程上对示例代码有更加清晰的解释。
WXML:
1 | <view class="flex-box"> |
WXSS:
1 | .list-item{ |
display: flex;
对于 flex-box
来说,就是让其内容左右分布;对于 list-item
来说,就是让其文字水平居中。
Flex 布局(2)
下面参考官方的 WeUI 小程序中的 Flex 重新编写以上代码。
其中 WXML 和 WXSS 都是参考 Git 仓库 的对应文件进行修改/照搬。
XML:
1 | <view class="weui-flex"> |
WXSS 直接复制对应的文件:
1 | .placeholder{margin:5px;padding:0 10px;text-align:center;background-color:var(--weui-BG-1);height:2.3em;line-height:2.3em;color:var(--weui-FG-1)} |
最后记得在 /app.wxss
中 @import "/style/weui.wxss";
。
编译运行,效果图如下:
这也太香了吧。
JavaScript 数据绑定
什么是数据绑定呢?就是把 WXML 中的一些动态数据分离出来放到对应的 js 文件的 Page 的 data
里。
只需要将 WXML 的代码写成:
1 | <view>{{username}},您已登录,欢迎</view> |
然后同名 JS 写成:
1 | Page({ |
即可导入变量。
常用变量类型有:字符串、数字、布尔值、对象、数组。
其中对象类似于 Python 字典的形式,数组类似于 Python 列表的形式(下标从 0 开始)。
在 WXML 中访问对象的方法是 .
,访问数组的方法是 [i]
。
如下是对象和数组嵌套的一个示例:
1 | data: { |
访问
1 | {{movies[0].actor[0].role}} |
可以得到 "安迪·杜佛兰"
。
由于比较简单就不多说了,详细教程可看本节开头的教程链接。
列表渲染和条件渲染
这是 WXML 的功能,可以对 JS 的数据渲染的时候进行 for
遍历和 if
判断。
下面的九九乘法表是一个很好的例子:
1 | <view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="i"> |
也可以使用 hidden
代替 if
进行条件渲染:
1 | <view hidden="{{i > j}}"> |
将上述 [1, 2, 3, 4, 5, 6, 7, 8, 9]
放到 js 的 data 中即可实现渲染 js 的数据。
不过,如果 js 的 data 是动态变化的,每次 data 改变就会导致重新渲染列表。解决方法是增加 wx:key
作为列表中 item 的唯一标识符。详见:https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/list.html
如不提供 wx:key
,会报一个 warning
。但是如果明确知道该列表是静态,或者不必关注其顺序,可以选择忽略。
wx:key
可以有两种:
- 如果是 item 本身(如上例),则可以用
wx:key="*this"
; - 如果 item 是一个对象,想用其属性如
name
,则可以用wx:key="name"
; - 除此之外,还可以用其下标:
wx:key="index"
。
1 | <view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="i" wx:key="*this"> |
获取表单内容
WeUI 小程序提供了表单,但是没有提供 JS。而按照这个小程序的写法,是不能在点击按钮触发的 bindtap
函数中获取表单的信息的。因此,对于表单,更推荐另一种写法:链接
1 | <form bindsubmit="formSubmit" bindrest="formReset"> |
这里需要重点注意的是 form
和 button
控件。form
的 bindsubmit
配合 <button form-type="submit">
,可以使得在按下这个 button 的时候,form
将表单中的内容作为参数,调用 bindsubmit
函数。
不过需要注意的是,组件中的 name
项必须写,否则是不会记录其内容的。
更多组件:轮播、音频、视频、cover、地图
如果没有需要则可以跳过。
JavaScript 微信 API
JavaScript 入门知识就放在另一篇博客了。
wx
是小程序的全局对象,用于承载小程序能力相关 API。
小程序开发框架提供丰富的微信原生 API,可以方便的调起微信提供的能力,如获取用户信息,了解网络状态等。
可以在微信开发者工具的控制台 Console 里输入 wx,了 解 一 下 这 个 对 象(共 700+ 个成员函数)。
获取信息
登录
获取网络状态
下面列举的代码,可以直接在微信开发者工具的调试器中的 Console 输入。在电脑模拟器和真机调试的输出也会不同。
文档就不给出了,随手百度/谷歌,第一个就是。
1 | wx.getNetworkType({ |
获取用户信息
1 | wx.getUserInfo({ |
获取设备信息
1 | wx.getSystemInfo({ |
获取场景值
场景值用来描述用户进入小程序的路径。如扫二维码、搜索等。场景值获取方式和对应含义可见文档。
设置当前页面
设置页面标题
1 | wx.setNavigationBarTitle({ |
下拉操作
需要在 app.json
或页面对应的 json
设置 "enablePullDownRefresh": true
。
1 | Page({ |
图片、缓存和文件
点击事件
事件是视图层到逻辑层的通信方式,当我们点击
tap
、触摸touch
、长按longpress
小程序绑定了事件的组件时,就会触发事件,执行逻辑层中对应的事件处理函数。
小程序框架的视图层由 WXML 与 WXSS 来编写的,由组件来进行展示。
将逻辑层的数据反应成视图,同时将视图层的事件发送给逻辑层。
逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈。
点击按钮触发页面滚动
首先在测试页面的 WXML 首尾增加两个 button
(两个 button
之间沿用九九乘法表的代码):
1 | <button type="primary" bindtap="scrollToPosition">滚动到指定位置</button> |
注意 button
属性里有一个 bindtap
,它的值对应的 js 函数就是当按钮按下时调用的函数。
于是我们在同名 JS 下写两个函数调用 wx.pageScrollTo
。
1 | Page({ |
然后编译。
点击“滚动到指定位置”可以滚到 6000px 的位置(由于总长没有 6000,因此就是滚到末尾);点击“滚动到页面顶部”可以滚动到顶部。
也可以滚动到指定 XML 选择器的位置。
1 | scrollToTop() { |
消息提示框 Toast
依旧是 button
通过 bindtap
绑定到调用 wx.showToast
的函数。
1 | <button type="primary" bindtap="toastTap">点击弹出消息对话框</button> |
1 | toastTap() { |
模态对话框
依旧是 button
通过 bindtap
绑定到调用 wx.showModel
的函数。不过这里增加了用户的两个选项。
1 | <button type="primary" bindtap="modalTap">显示模态对话框</button> |
1 | modalTap() { |
手机震动
一样的套路,把函数改为 wx.vibrateLong()
即可。
Navigator 组件和页面路由 API
Navigator
组件可以做到的事情,使用 JavaScript 调用小程序也能路由 API 也可以做到。Navigator
组件的内容是写死的,而 JavaScript 则可以提供动态的数据。
页面路由 API | Navigator open-type 值 | 含义 |
---|---|---|
wx.redirectTo |
redirect | 关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面。 |
wx.navigateTo |
navigate | 保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面。 |
wx.navigateBack |
navigateBack | 关闭当前页面,返回上一页面或多级页面。 |
wx.switchTab |
switchTab | 跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面 |
wx.reLaunch |
reLaunch | 关闭所有页面,打开到应用内的某个页面 |
事件对象
在前面的列表渲染里,我们知道点击电影列表里的某一部电影,要进行页面跳转显示该电影的详情,我们需要给该电影创建一个页面,那如果要显示数千部的电影的详情,一一创建电影详情页显然不合适,毕竟所有电影的详情页都是同一一个结构,有没有办法所有电影详情都共用一个页面,但是根据点击的链接的不同,渲染相应的数据?答案是肯定的,要解决这个问题,首先我们要了解链接组件的点击信息。
当点击组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象,通过
event
对象可以获取事件触发时候的一些信息,比如时间戳、detail
以及当前组件的一些属性值集合,尤其是事件源组件的id
。
如,在 WXML 中有一个按钮如下:
1 | <view id="tapTest" data-hi="Weixin" bindtap="tapName"> Click me! </view> |
在 JS 中定义一个 tapName
函数:
1 | Page({ |
点击按钮可以看到以下信息:
1 | { |
对比 WXML 可以注意到:
1 | <view id="tapTest" data-hi="Weixin" bindtap="tapName"> Click me! </view> |
event.currentTarget.id
对应 WXML 中的id
;event.currentTarget.dataset.hi
对应data-hi
;事实上,在 WXML 中,这些自定义数据以data-
开头,多个单词由连字符-
连接。在 JS 中,连字符写法会转换成驼峰写法,而大写字符会自动转成小写字符。参考
这样就可以访问 event.currentTarget.id
或 event.currentTarget.dataset.hi
来实现传递按钮的参数了。
还注意到,在本例中,currentTarget
和 target
是一样的。
但是,如果在多个组件中触发了 bindtap
(如外层的 view
和内层的 image
组件都有 bindtap
,点击了图片,则两个 bindtap
对应的函数都会被触发)。
我们点击的是图片 image 组件,却分别触发了绑定在 image 组件以及 image 的父级(上一级)组件 view 的事件处理函数,我们称这为事件冒泡。
此时,两个函数中的 currentTarget
都对应 image
组件的信息,而 target
对应的是本身组件(view
或 image
)的信息。
变量传递
这节讲的是组件是如何携带数据的,事件对象数据的作用以及数据如何跨页面渲染。
在链接中携带数据
最简单的就是在链接中携带数据。
如,新建两个页面:pages/home/detail/detail
和 pages/lifecyle/lifecycle
。
在 pages/lifecyle/lifecycle.wxml
中写一个跳转组件:
1 | <navigator id="detailshow" url="./../home/detail/detail?id=lesson&uid=tcb&key=tap&ENV=weapp&frompage=lifecycle" class="item-link">点击链接看控制台</navigator> |
点击即可跳转到 pages/home/detail/detail
。那,如何在 detail
获取数据呢?
在 detail.js
写一个 onLoad
函数即可(这个 onLoad
函数和 data
平级):
1 | onLoad: function (options) { |
显示 {id: "lesson", uid: "tcb", key: "tap", ENV: "weapp", frompage: "lifecycle"}
。即,参数被解析为了一个 JS 对象,就可以用 options.id
访问数据。
跨页面数据渲染
讲到这里,其实就已经可以实现了。流程如下:
- 在
detail.js
中的data
添加一个detail
对象,如下:
1 | detail:{ |
- 在
detail.js
的onload
函数中,将参数值赋给detail
。 - 在
detail.wxml
中使用等进行渲染。
小程序和页面的生命周期
App()
函数注册小程序,Page()
函数注册小程序中的一个页面,他们都接受的是对象Object
类型的参数,包含一些生命周期函数和事件处理函数。App()
必须在app.js
中调用,必须调用且只能调用一次。开发者可以添加任意的函数或数据变量到Object
参数中,用this
可以访问。
App.js 中的函数及触发时刻如下:
1 | onLaunch(opts) { |
onLaunch
是监听小程序的初始化,初始化完成时触发,全局只会触发一次,所以在这里我们可以用来执行获取用户登录信息的函数等一些非常核心的数据,如果onLaunch
的函数过多,会影响小程序的启动速度。onShow
是在小程序启动,或从后台进入前台显示时触发,也就是它会触发很多次,在这里就不大适合放获取用户登录信息的函数啦。这两者的区别要注意。
每个页面的 JS 中的函数及触发时刻如下:
1 | onLoad: function(options) { |
推荐自己测试一下。
globalData
app.js
中还有一个 globalData
,顾名思义就是全局数据。
可以直接在 app.js
中定义 globalData
的参数。
1 | App({ |
可以在 app.js
的其他函数中修改:
1 | App({ |
在其他页面中访问全局变量可以使用 getApp()
(文档):
1 | let app = getApp() |
网络 API
云函数
云函数即在云端(腾讯提供的临时服务器)执行的 JavaScript 函数。
某些函数需要调用 npm 包,但我们不能在每台手机上安装所有 npm 组件。
因此,我们将 npm 包和需要这些包的函数部署在云上,手机上只需要调用这些云函数即可。
wx.cloud
wx.cloud
对象有以下成员。云函数、云数据库、云存储都是通过这个对象进行使用。
1 | CloudID: ƒ () //用于云调用获取开放数据 |
开通云开发服务
见上教程。需要记住环境 ID。环境即是云函数、数据库等需要上传的一个地方。
指定小程序的 ID
以云开发的形式创建项目,然后在 app.js
可以看到:
1 | App({ |
取消注释第 12 行,并且修改为自己的环境 ID。
编写云函数
在 cloudfunctions
目录下,每个文件夹都对应一个云函数。
我们关注 login
函数。打开 cloudfunctions/login/index.js
:
1 | // 云函数模板 |
- 1-10 行初始化云环境;
- 18-35 行则导出了一个叫做
main
的箭头函数,其接受两个参数event
和context
,可通过console.log
查看:event
包含用户的openid
和小程序的appid
, openid 就相当于用户的身份证,我们可以根据openid
获取到用户的昵称、头像等信息;context
对象则是云函数的调用信息和运行状态。
- 这段函数执行过程只是调用
cloud.getWXContext()
并返回其结果,在调用云函数的地方可以接收。
需要强调的是,云函数的 console.log
的返回结果是在云开发控制台而不是开发者工具的控制台里。
部署云函数
- 右键云函数目录,选择在终端中打开,输入
npm install
命令下载依赖文件; - 然后再右键云函数目录,点击“创建并部署:所有文件”;
- 在云开发控制台–云函数–云函数列表查看云函数是否部署成功。
编写调用云函数的方法
现在将目光转向 pages/index/index.js
。这是云开发小程序模板的首页的 JS 文件。其中有一个函数 onGetOpenid
是首页中“获取 openid” 按钮通过 tapbind
绑定的函数。这个函数调用了云函数:
1 | onGetOpenid: function() { |
可见,调用云函数是通过 wx.cloud.callFunction
函数,再通过参数指定是哪个函数、参数和成功/失败的回调函数。
进阶调试
云服务选取
云数据库
微信给的云数据库是 JSON 数据库,也就是每条记录以 JSON 的形式上传。这点类似于 MongoDB,甚至某些查询语句也和 MongoDB 类似。
创建项目时,以云开发的形式创建项目,然后在 /pages/databaseGuide/databaseGuide.js
可以看到如何操作数据库。首先需要手动在云开发-数据库页面新增集合(类似于 MySQL 的表)。
云数据库使用有以下注意事项:
- 小程序一次获取的记录数不能超过 20 条,云函数不能超过 100 条。必要时可通过文档 给出的示例代码进行多次获取然后拼接;
- 小程序不支持数据批量更新(即
db.collection().where().update()
),仅云函数支持; - 云函数不兼容回调函数风格(即
.get({success, fail, complete})
),仅支持 Promise 风格(即.get().then().catch()
); - 并不是每次云函数执行都会在新的环境开始执行,连续执行云函数也可能在上一次的环境下执行,因此变量初始化一定要在 main 函数里完成,才能保证变量每次被初始化了。
新增记录
1 | onAdd: function () { |
查询记录
1 | onQuery: function() { |
更多的查询语法见链接
更新记录
1 | onCounterInc: function() { |
需要注意的是,小程序端为了避免批量更新,不允许对查询的数据进行更新。
- 如果查询的数据只有一项,可以通过查询获取其
id
,再对id
更新; - 如果需要更新很多项,可以考虑使用云函数。
删除记录
1 | onRemove: function() { |