小程序学习-01小程序简介微信小程序基础
小程序学习 - 01小程序简介+微信小程序基础
小程序简介
小程序是一个全新的、轻量级的移动端应用。
起源
在小程序出现之前的移动端开发的解决方案有:
- Android
- iOS
- Windows Phone - 后来退出历史舞台
移动端开发发展了一段时间之后也暴漏出来一些问题,传统 App (Android / iOS)的开发和运营成本很高:
- 开发一款 App,就要开发这两个版本,既要招 Android 工程师,也要招 iOS 工程师, 人力成本非常高
- App 开发完成之后,还要发版,即发布到应用商店中(例如 App Store)。发版申请提交之后要等待审核,审核不通过就需要退回调整重新发版, 发版过程繁琐
- 发版完成后还 需要推广 ,常规做法是花钱在应用商店打广告,提高 App 的排名
为了解决传统 App 的这些问题,小程序应运而生。
发展历程
最早推出小程序的是微信团队。
- 2016 年 1 月 9 日,小程序立项
- 2016年 9 月 21 日,开始内测
- 2017 年 1 月 9 日,正式上线
小程序上线之后取得了巨大的成功,这导致一些其它平台也跟风上线了小程序的内容,如支付宝、京东、今日头条、百度。
微信小程序推出的最早且目前最有影响力(后文默认介绍微信小程序)。
优势
- 相比原生 App (Android / iOS),小程序无需单独下载
- 只需要扫一扫或搜一下即可打开使用
- 小程序寄宿在 App 中,宿主 App 自带流量,所以小程序推广成本低
- 例如 2017 年的“跳一跳”,利用朋友圈成绩排名,迅速爆红
- 小程序简单易学
- Android 工程师要学习 Android 开发和 Java
- iOS 工程师要学习 Objective-C 或 Swift
- 小程序只需要掌握前端技术栈
小程序与传统网页开发的区别
小程序的运行环境与网页开发不同:
- 网页开发的运行环境是浏览器
- 小程序的运行环境是操作系统(Android / iOS)上的 App 客户端
- 如微信小程序的运行环境是微信客户端
所以小程序与网页开发有一些区别:
- 小程序中不能使用 HTML
- 小程序中可以使用 CSS
- 小程序主要开发语言是 JavaScript
- 但是 BOM(操作 HTML) 和 DOM(与浏览器交互) 在小程序中不可用
- 基于 DOM 和 BOM 的 JS 库也不能用,如 jQuery
运行环境
小程序的运行环境主要有两个:
- 渲染层 - 主要用于渲染内容和样式
- 逻辑层 - 主要用来处理 JS
不同的操作系统,内部的支持也不一样:
运行环境 | 逻辑层 | 渲染层 |
---|---|---|
iOS | JSCore | WKWebView |
Android | V8 引擎 | Chromium 定制内核 |
虽然不同操作系统的渲染机制和逻辑不一样,但外部的表现是一样的。
微信小程序基础
下面通过案例学习微信小程序的基础使用,主要包括:
- 注册小程序账号
- 搭建开发环境
- 初始化小程序 - 类似脚手架工具生成基础代码
注册小程序账号
在小程序官网注册:
注意:注册过微信公众号的邮箱,不能再注册微信小程序账号
也可以 ,免注册快速体验小程序开发(不能发布)。
注册成功后,可以在后台获取小程序的 AppID,它是小程序的唯一标识。
搭建开发环境
去小程序官网,下载安装微信开发者工具:
详细界面介绍参考官网文档:
初始化小程序
打开微信开发者工具新建小程序项目:
“不使用模板” 与 “JavaScript - 基础模板” 基本没区别。
本文创建的项目:
- 项目名称:start
- AppID:注册 AppID
- 开发模式:小程序
- 后端服务:不使用云服务
- 语言:JavaScript
- 模板:不使用模板
解读初始化项目
下面依次解读小程序初始化项目:
- 目录结构
- 生命周期
- app 代码
- index 页面
- logs 页面
目录结构
官方文档:
全局文件
全局文件存放在根目录,文件名是固定的,不能修改,其中包括:
- app.js - 小程序入口文件,启动小程序首先加载这个文件
- app.json - 小程序全局配置
- app.wxss - 小程序全局样式,类似网页开发的 css
页面文件
页面文件存放在 pages 文件夹下,初始包括 index 和 logs 两个页面,每个页面由四个文件组成,以 index 为例:
- index.js - 页面入口(数据绑定和逻辑代码)
- index.json - 页面配置,当前页面的页面级配置
- index.wxml - 页面内容(组件+条件渲染),类似网页开发的 html
- index.wxss - 页面样式,仅对当前页面有效的样式文件
需要注意的是:
- 小程序的页面由 4 个文件组成:
.js
、.json
、.wxml
、.wxss
,四个文件的文件名必须一致 - 小程序的页面不是由开发者手动创建的,而是由小程序自动生成的,只需要配置小程序全局配置文件
app.json
的pages
,保存文件后就会自动创建页面文件
例如:
"pages": [
// 未指定 entryPagePath 时,数组的第一项代表小程序的初始页面(首页)
"pages/index/index",
"pages/logs/logs",
// pages 是页面存放目录
// test 是页面存放的子目录
// my-test 是页面文件名称
"pages/test/my-test"
],
生命周期
小程序生命周期主要指两个循环状态:前台到后台、初始化到销毁。
关于前后台的定义参考官方文档:
小程序中的生命周期有两类:
- 小程序生命周期(全局生命周期):声明在
app.js
文件中- 参考文档:
- 页面生命周期:声明在页面的入口文件
<pagename>.js
中- 参考文档:
小程序生命周期函数
常用小程序生命周期函数及触发时机:
- onLaunch - 小程序初始化,小程序冷启动时触发,全局只调用一次
- onShow - 初始化完成小程序热启动,或从后台切换到前台时触发
- onHide - 从前台切换到后台时触发
测试案例
// app.js
App({
onLaunch() {
console.log('onLaunch - 小程序加载')
// 默认初始代码...
},
onShow() {
console.log('onShow - 小程序显示')
},
onHide() {
console.log('onHide - 小程序隐藏')
},
globalData: {
userInfo: null
}
})
保存文件后,小程序会重新初始化,依次触发
onLaunch
和
onShow
。
可点击工具栏上的
切后台
切换到后台触发
onHide
。
PS:如果工具栏没有
切后台
按钮,可以通过工具 - 工具栏管理
设置显示的工具栏图标。
页面生命周期函数
常用页面生命周期函数及触发时机:
- onLoad - 页面加载时触发,整个页面生命阶段只执行依次
- onShow - 页面显示或切换到前台时触发
- onReady - 页面准备就绪(首次渲染完毕)时触发,整个页面生命阶段只执行依次
- onHide - 页面隐藏或切换后台时触发
- onUnload - 页面销毁(切换页面)时触发
测试案例
修改 test 页面的生命周期函数
// pages/test/test.js
Page({
/**
* 页面的初始数据
*/
data: {
},
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
console.log('onLoad - 页面加载')
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
console.log('onReady - 页面就绪')
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
console.log('onShow - 页面显示')
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
console.log('onHide - 页面隐藏')
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
console.log('onUnload - 页面卸载')
},
/**
* 页面相关事件处理函数--监听用户下拉动作
*/
onPullDownRefresh: function () {
},
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
},
/**
* 用户点击右上角分享
*/
onShareAppMessage: function () {
},
/**
* 重载页面
*/
goIndex: function() {
// 调用原生 API 重新加载页面
wx.reLaunch({
url: "/pages/index/index"
})
}
})
添加重载功能:
<!--pages/test/test.wxml-->
<text bindtap="goIndex">跳转到 index</text>
配置小程序的底部导航栏
tabBar
,用于切换页面:
{
"pages": [
"pages/index/index",
"pages/logs/logs",
"pages/test/test"
],
// 窗口配置
"window": {
"backgroundTextStyle": "light",
// 顶部导航栏背景
"navigationBarBackgroundColor": "#fff",
// 顶部导航栏标题
"navigationBarTitleText": "Weixin",
// 顶部导航栏文字颜色
"navigationBarTextStyle": "black"
},
// 底部导航配置
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "首页"
},
{
"pagePath": "pages/logs/logs",
"text": "日志"
},
{
"pagePath": "pages/test/test",
"text": "测试"
}
]
},
"style": "v2",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents"
}
上面的空白是图标预留,可以自行配置
iconPath
和
selectedIconPath
。
现在点击导航栏切换页面,查看控制台输出:
- onShow 在 onReady 前执行
- 切换页面会触发 onShow 和 onHide
- 切换后台时,先触发页面的 onHide 再触发小程序的 onHide
- 切换前台时,先触发小程序的 onShow 再触发页面的 onShow
微信小程序的启动流程
中有一张图分别介绍了 View(视图)和 AppService(App 服务)两个线程的生命周期顺序:
综合小程序和页面的生命周期就是微信小程序的启动流程:
- 首先执行小程序生命周期 onLaunch
- 然后执行小程序生命周期 onShow
- 接着执行页面生命周期 onLoad,执行完成后页面就加载出来了
- 接着执行页面生命周期 onShow 显示页面
- 请求数据执行完成后,就会执行页面生命周期 onReady
- onReady 执行完成后,如果切换后台就会执行页面生命周期 onHide
- onHide 执行完成后,就会执行小程序生命周期 onHide
- 如果执行页面卸载 onUnload,页面就会被销毁
- 此时如果再访问另一个页面,就会执行这个页面的 onLoad,重复上面的调用过程
微信原生 API
小程序开发框架提供了丰富的 ,这些 API 存放在云端(服务器端),不需要自己开发和部署。
除了一些 web API,还提供了一些移动端的 API,只能在移动端调用,如:
- 拍照:调用摄像头,返回照片信息
- 扫一扫:识别二维码内容
- 等
原生 API 通过
wx.<api>
调用,详细参考
。
代码解析
app.js
// app.js
App({
onLaunch() {
console.log('onLaunch - 小程序加载')
// 展示本地存储能力
// 同步方式获取本地缓存数据
const logs = wx.getStorageSync('logs') || []
// 添加当前启动时间
logs.unshift(Date.now())
// 同步方式设置本地缓存数据
wx.setStorageSync('logs', logs)
// 登录
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
}
})
},
onShow() {
console.log('onShow - 小程序显示')
},
onHide() {
console.log('onHide - 小程序隐藏')
},
// 全局数据
globalData: {
// 旧版初始化的项目将用户信息保存在这里
// 新版初始化的项目将用户信息保存在 index.js 中
userInfo: null
}
})
以前的版本会在
onLaunch
内部调用wx.getSettings
获取用户当前设置,根据返回的数据判断授权状态,如果用户已授权,则调用wx.getUserInfo
获取用户信息。现在更改为在
pages/index/index.js
中调用的wx.getUserProfile
,每次请求都会弹出授权窗口,用户同意后返回userInfo
。
pages/index/index.js
初始化项目默认启动获取用户信息,可将
canIUseOpenData
设置
false
变更为手动获取:小程序启动后页面会显示
获取头像昵称
,点击请求
wx.getUserProfile
// index.js
// 获取应用实例
// 获取的是 app.js 中生成的小程序的实例
const app = getApp()
Page({
// 绑定数据,类似 vue 的 data
data: {
motto: 'Hello World',
userInfo: {},
hasUserInfo: false,
canIUse: wx.canIUse('button.open-type.getUserInfo'),
canIUseGetUserProfile: false,
canIUseOpenData: wx.canIUse('open-data.type.userAvatarUrl') && wx.canIUse('open-data.type.userNickName') // 如需尝试获取用户信息可改为false
},
// 事件处理函数
bindViewTap() {
wx.navigateTo({
url: '../logs/logs'
})
},
onLoad() {
if (wx.getUserProfile) {
// 修改数据
this.setData({
canIUseGetUserProfile: true
})
}
},
getUserProfile(e) {
// 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认,开发者妥善保管用户快速填写的头像昵称,避免重复弹窗
wx.getUserProfile({
desc: '展示用户信息', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写
success: (res) => {
console.log(res)
this.setData({
userInfo: res.userInfo,
hasUserInfo: true
})
}
})
},
getUserInfo(e) {
// 不推荐使用getUserInfo获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息
console.log(e)
this.setData({
userInfo: e.detail.userInfo,
hasUserInfo: true
})
}
})
WXML
官方文档:
WXML (WeiXin Markup Language) 是框架设计的一套标签语言。
- WXML 相当于小程序中的模板引擎
- WXML 中展示内容的具体标签,称为组件
- 数据绑定
- 数据展示(条件渲染、列表渲染)
- WXML 与组件之间的关系,就相当于 HTML 与标签
作用 | 小程序组件 | HTML 标签 |
---|---|---|
展示区块 | view | div |
展示图片 | image | img |
展示文本 | text | p |
链接导航 | navigator | a |
… | … | … |
模板
WXML 的 可以定义代码片段,在不同地方中调用。
使用
<template>
定义模板,使用
<import>
引用模板,使用
is
声明需要使用的模板名称,使用
data
传入模板所需的数据。
注意:如果
app.json
配置了"lazyCodeLoading": "requiredComponents"
,开发者工具中使用了<import>
引入模板的页面会显示白屏,因为项目开启了按需注入,但真机预览中会正常显示,开发时可以将该配置项删除。
代码解析
pages/index/index.wxml
<!--index.wxml-->
<view class="container">
<view class="userinfo">
<!-- wx:if 是 WXML 的条件渲染语法 -->
<!-- 使用数据或表达式的语法是 {{}} -->
<block wx:if="{{canIUseOpenData}}">
<!-- bindtap 是事件属性,值是需要绑定的处理函数的名称 -->
<!-- 事件触发时会在该页面对应的 Page 中找到相应的事件处理函数 -->
<!-- 小程序绑定事件处理函数只接受字符串,及处理函数名 -->
<!-- 无法自定义调用方式或传参,如 bindtap="{{(e, 'params') => bindViewTap}}" -->
<!-- 可以将参数绑定在标签上(如 data-param="value"),通过 event 获取 -->
<view class="userinfo-avatar" bindtap="bindViewTap">
<open-data type="userAvatarUrl"></open-data>
</view>
<open-data type="userNickName"></open-data>
</block>
<!-- 注意这里是 elif 而不是 elseif -->
<block wx:elif="{{!hasUserInfo}}">
<button wx:if="{{canIUseGetUserProfile}}" bindtap="getUserProfile"> 获取头像昵称 </button>
<button wx:elif="{{canIUse}}" open-type="getUserInfo" bindgetuserinfo="getUserInfo"> 获取头像昵称 </button>
<view wx:else> 请使用1.4.4及以上版本基础库 </view>
</block>
<block wx:else>
<image bindtap="bindViewTap" class="userinfo-avatar" src="{{userInfo.avatarUrl}}" mode="cover"></image>
<text class="userinfo-nickname">{{userInfo.nickName}}</text>
</block>
</view>
<view class="usermotto">
<text class="user-motto">{{motto}}</text>
</view>
</view>
pages/logs/logs.js
// logs.js
// 小程序中的模块化开发遵循 CommonJS 规范(exports 导出,require 导入)
const util = require('../../utils/util.js')
Page({
data: {
logs: []
},
onLoad() {
// 获取日志并设置到 data 中
this.setData({
// 同步获取本地缓存
logs: (wx.getStorageSync('logs') || []).map(log => {
return {
// 格式化时间
date: util.formatTime(new Date(log)),
timeStamp: log
}
})
})
}
})
pages/logs/logs.wxml
<!--logs.wxml-->
<view class="container log-list">
<!-- 列表渲染 -->
<block wx:for="{{logs}}" wx:key="timeStamp" wx:for-item="log">
<text class="log-item">{{index + 1}}. {{log.date}}</text>
</block>
</view>
pages/logs/logs.json
{
// 优先级高于全局配置的导航标题
"navigationBarTitleText": "查看启动日志",
"usingComponents": {}
}