1.小程序的结构
1.1应用程序的结构
- 小程序结构划分:最上层App -> 多个page -> 多个组件
1.2.应用目录的结构
2.小程序的MVVM
- vue的MVVM和小程序MVVM对比
- MVVM为什么好用:
DOM Listeners:ViewModel层可以将DOM的监听绑定到Model层
Data Bindings:ViewModel层可以将数据的变量、响应式的反应到View层 - MVVM架构将我们从命令式编程转移到声明式编程
3.小程序架构和配置
3.1.project和sitemap
- 小程序的很多开发需求被规定在了配置文件中
- 可以更有利于我们的开发效率,并且保证开发出来的小程序的某些风格是比较一致的,比如导航栏,顶部tabbar等等
- 常见的配置文件:
project.config.json:项目配置文件,比如项目名称、appid等
sitemap.json:小程序搜索相关的
app.json:全局配置,小程序的入口
page.json:页面配置(eg.如果需要下拉刷新,则需要在对应的page.json中将设置打开:enablePullDownRefresh:true
)
3.2.全局配置app.json
3.2.1.pages
- pages:页面路径列表,用于指定小程序由哪些页面组成,每一项都对应一个页面的路径信息
- 小程序中所有的页面都是必须在pages中进行注册的
- 类型:string
3.2.2.window
- 全局的默认窗口展示:用户指定窗口如何展示,其中还包含了很多其他的属性
- 类型:Object
3.2.3.tabbar
- 底部tab栏的表现
- 类型:Object
格式化文档:alt+shift+F
3.3.局部配置page.json
- 每一个小程序页面也可以使用.json文件来对本页面的窗口表现进行配置
- 页面中配置项在当前页面会覆盖app.json的window中相同的配置项
3.4.小程序双线程模型
- 微信客户端是小程序的宿主环境
- 宿主环境为了执行小程序的各种文件:wxml文件、wxss文件,js文件,提供了小程序的双线程模型
- 双线程模型:wxml模块和wxss样式运行于渲染层,渲染层使webview线程渲染(一个程序有多个页面,会使用多个webview的线程);
js脚本运行于逻辑层,逻辑层使用jscore运行js脚本
这两个线程都会经由微信客户端(Native)进行中转交互 - 数据发生改变:通过setData改变数据时,产生的js对象对应的节点就会发生改变,此时可以对比前后两个js对象得到变化的部分,然后把这个差异应用到原来的Dom树上,从而达到更新UI的目的,这就是“数据驱动”的原理
3.5.小程序的启动流程
- 小程序的启动流程
3.6.注册小程序App
3.6.1.注册小程序示例
- 每个小程序都需要在app.js中调用App方法注册小程序示例
在注册时,可以绑定对应的生命周期函数,在生命周期函数中,执行对应的代码 - onLaunch:小程序初始化完成时,会执行的生命周期函数(会在后台存活两个小时,两个小时内关掉再打开不会执行函数)
- onShow:小程序界面显示出来之后会执行的生命周期函数
- onHide:界面隐藏时执行 ,监听小程序切后台
- onError:小程序中发生了一些错误时会执行
- onPageNotFound:页面不存在时监听函数
3.6.2.注册App时做什么
- 1.判断小程序的进入场景
2.监听生命周期函数,在生命周期中执行对应的业务逻辑,比如在某个生命周期函数中获取微信用户的信息
3.因为App()实例只有一个,并且是全局共享的(单例对象),所以我们可以将一些共享数据放在这里
小程序进入场景
- 小程序的打开场景较多:会话中打开,小程序列表、微信扫一扫、另一个小程序打开;如何确定场景:在onLaunch和onShow生命周期回调函数中,会有options参数,其中有scene值
onShow(options) {
switch(options.scene) {
case 1001:
break;
case 1005:
break;
}
}
获取用户信息—保存全局变量
- 方式1:wx.getUserInfo
//可以在onShow里也可以在onLaunch里,onShow中则会调用的比较频繁
onShow() {
wx.getUserInfo({
success: function(res) {
console.log(res);
}
})
}
- 方式2:button组件—将open-type改成getUserInfo,并且绑定bindgetuserinfo事件去获取
<button size="mini" open-type="getUserInfo"
bindgetuserinfo="handleGetUserInfo">获取授权</button>
//js文件中
handleGetUserInfo(event) {
console.log(event);
}
- 方式3:使用open-data组件展示用户信息
//展示用户名称
<open-data type="userNickName"></open-data>
//展示用户头像
<open-data type="userAvatarUrl"></open-data>
定义全局数据
- 在app.js的globalData中定义的全局数据,可以在其他地方共享
- 如何在其他地方使用:
//getApp获取App()产生的示例对象
const app = getApp()
const name = app.globalData.name;
3.7.注册页面Page
3.7.1注册page时做什么
- 小程序中的每个页面,都有一个对应的js文件,其中调用page方法注册页面示例;
- 在注册时,可以绑定初始化数据、生命周期回调、事件处理函数等
- 一般需要做什么:
在生命周期函数中发送网络请求,从服务器获取数据
初始化一些数据,以方便被wxml引用展示
监听wxml中的事件,绑定对应的事件函数(点击…)
其他一些监听(比如页面滚动、上拉刷新、下拉加载)
Page({
/**
* 初始化数据
*/
data: {
list:[]
},
/**
* 发送网络请求
*/
onLoad: function (options) {
wx.request({
url: 'http://……',
success:(res) => {
const data = res.data.data.list
this.setData({
list:data
})
}
})
},
//监听页面滚动
onPageScroll(obj) {
},
//监听到页面滚动到顶部
onReachBottom(obj) {
}
}
监听页面的生命周期函数
- onLoad:页面被加载时执行
- onShow:页面显示出来时
- onReady:页面初次渲染完成时
- onHide:页面隐藏起来时
- onUnload:
- 打开一个页面,执行顺序为onLoad、onShow、onReady
- Page实例生命周期
4.内置组件
4.1.Text组件
- 用于显示文本,类似于span标签,是行内元素
- 常见属性:
selectable:
- boolean,默认为false(即text中的文本长按是不能选中)
- 文本是否可选
- 示例:
<text selectable="{{false}}"></text>
(如果为true可简写为<text selectable></text>
)要用mustache语法,否则则是把字符串赋值给他;
space:
- string
- 决定文本空格的大小
- 有三个值:
//emsp,中文字符空格一半大小(默认)
<text space="ensp"></text>
//ensp,一个中文字符空格大小
<text space="emsp"></text>
//nbsp,根据字体设置的空格大小
<text space="nbsp"></text>
decode:
- boolean,默认为false
- 是否解码
- 示例
<text>5 > 3</text> //直接显示5 > 3
<text decode="true">5 > 3</text> //显示5 > 3
<text decode>5 > 3</text> //显示5 > 3
4.2.Button组件
- 用于创建按钮,默认块级元素
- 常见属性:
size:可以传入值mini,会将按钮转为行内块元素
type:按钮的类型样式
plain:按钮是否镂空,背景色透明
open-type:用户获取一些特殊性的权限,可以绑定一些特殊的事件
4.3.View组件
- 块级元素,独占一行,通常用作容器组件
- 常见属性:
hover-class:类型string,指定按下去的样式类。当hover-class="none"
时,没有点击态效果
hover-stay-time:类型number,手指松开后点击态保留时间,单位毫秒,默认为400毫秒
hover-start-time:类型number,按住后多久出现点击态,单位毫秒
hover-stop-propagation:boolean,默认false,指定是否阻止本节点的祖先节点出现点击态 - 传入number类型如果发现没有效果记得给这个数字包装mustache语法
4.4.Image组件
-
image组件可以写成单标签
<image/>
,也可以写成双标签 -
image组件默认有自己的大小:320*240
-
image是行内块元素
-
示例:用户如何在文件中选择图片
// home.wxml
<button bindtap="handleChooseAlbum">选择图片</button>
<image src="{{imagePath}}"></image>
// home.js
Page({
/**
* 页面的初始数据
*/
data: {
imagePath: ''
},
handleChooseAlbum() {
// 让用户在相册中选择图片(或者拍照)
wx.chooseImage({
success: (res) => {
// 取出路径
const path = res.tempFilePaths[0];
// 设置imagePath
this.setData({
imagePath: path
})
}
})
}
})
常见属性
- bindload:监听图片加载完成
- lazy-load:boolean,默认为false,图片懒加载
- show-menu-by-longpress:boolean,默认为false,开启长按图片显示识别小程序菜单
- mode:string,默认值为scaleToFill,图片裁剪、缩放的模式
4.5.input组件
- input组件用于接受用户的输入信息
- input的基本使用:
<input/>
- value:input中的默认值
- type:决定键盘类型(英文字母+其他符号/数字/身份证)
取值有text(文本输入键盘),number(数字输入键盘),idcard(身份证输入键盘),digit(带小数点的数字键盘) - password:暗文
- placeholder:占位文字
- input绑定事件
4.6.scroll-view组件
- 可以实现局部滚动
属性:
- 水平滚动:scroll-x,设置为true
- 垂直滚动:scroll-y,设置为true
绑定事件:
- bindscroll:滚动时触发
4.7.组件的共同属性
- id:组件的唯一标识
- class:组件的样式类
- style:组建的内联样式
- hidden:boolean,组件是否显示
- data-*:自定义属性,组件上触发的事件时,会发送给事件处理函数
bind */ catch*
:组件的事件
5.wxss
5.1.样式的三种写法
- 内联样式
- 页内样式
- 全局样式:在app.wxss中写,对所有页面的该属性都生效
- 优先级:行内样式>页面样式>全局样式
5.2.支持的选择器
5.3.尺寸单位
- rpx:可以根据屏幕宽度进行自适应,规定屏幕宽为750rpx
- 在iPhone6,1rpx = 0.5px = 1物理像素
- 在iPhone5,1rpx = 0.42px
5.4.样式导入
- 我们可以在一个wxss中导入另一个wxss文件:
使用@import进行导入
@import后跟需要导入的外联样式表的相对路径(绝对路径),用;表示语句结束
5.5.基本样式库
- https://github.com/Tencent/weui-wxss
- 导入dist文件夹
6.wxml
6.1.mustache语法
//wxml
<view class='{{isActive ? "active" : ""}}'>{{message}}</view>
<button size="mini" bindtap="changeColor">更换颜色</button>
//js
data: {
message: '你好,小程序',
isActive: false
},
changeColor() {
this.setData({
isActive : !this.data.isActive
})
}
6.2.条件判断
- wx:if :
<view wx:if="{{isShow}}"></view>
- wx:elif / wx:else
- wx:if和hidden隐藏组件的区别:
hidden隐藏组件的时候,组件仍存在,用于切换隐藏频繁时,对于自定义的组件是无效的
wx:if隐藏组件时,控制组件是否渲染
<view wx:if="{{false}}"></view>
<view hidden='{{true}}'></view>
6.3.列表渲染
- wx:for的回顾
//遍历字符串
<view wx:for="SLineee" wx:key="index">{{item}}--{{index}}</view>
//遍历数组
<view wx:for="{{['dd','abc','bb']}}" wx:key="index">{{item}}--{{index}}</view>
//遍历数字,写在mustache语法里,则会从遍历九次(从0~8)
<view wx:for="{{9}}">{{item}}</view>
- item、index起名字,用于多层遍历名字重复时
<view wx:for="{{idols}}" wx:for-item="idol" wx:for-index="i">{{idol}}--{{i}}</view>
- key作用:提高性能
6.4.block标签
- 某些情况下,我们需要使用wx:if或wx:for时,可能需要包裹一组组件标签
- 我们希望对这一组组件标签进行整体的操作
- 可以使用一个view组件包裹,性能较低
- 使用block标签包裹,它不是一个组件,只是一个包装元素,不会在页面中做任何渲染,只接受控制属性(wx:if或wx:for)
- 使用block的好处:将需要进行遍历或判断的内容进行包裹;将遍历和判断的属性放在block便签中,不影响普通属性的阅读,提高代码的可读性;性能高
6.5.wxml导入
- wxml提供模板(template),可以在模板中定义代码片段,在不同的地方调用
- 使用name属性,作为模板的名字,然后在
<template/>
内定义代码片段
<template name="contentItem">
<button size="mini">{{btnText}}</button>
</template>
- 使用方法:
<template is=" " data="{{key1:value1,key2:value2}}"/>
(is后面写name) - import导入:
<import src=""/>
可以在该文件中使用目标文件定义的template
但不能递归导入(也就是A引入了B的template,不会引入B中引入C的template) - include导入:
<include src=""/>
将目标文件中除了<template/><wxs/>
外的整个代码引入,相当于是拷贝到include的位置
可以递归导入
7.wxs
7.1.作用
- 在wxml中不能直接调用page/component中定义的函数,但是有时候我们希望可以用函数来处理wxml中的数据(类似过滤器),这个时候就使用wxs
- wxs的运行环境和其他JavaScript代码是隔离的,wxs中不能调用其他JavaScript文件定义的函数,也不能调用小程序提供的API
- wxs函数不能作为组件的事件回调
7.2.wxs的两种写法
- 直接在wxml中定义
<!--pages/wxs/wxs.wxml-->
<wxs module="info">
var message = "hello world";
function sum(num1,num2) {
return num1 + num2;
}
module.exports = {
message : message,
sum: sum
}
</wxs>
<view>{{info.message}}</view>
<view>{{info.sum(20,30)}}</view>
- 定义在单独的wxs文件中,再使用
<wxs>
标签导入
wxs/info.wxs:
var message = "hello world";
function sum(num1,num2) {
return num1 + num2;
}
module.exports = {
message : message,
sum: sum
}
wxs.wxml:
必须使用相对路径
<wxs src="" module="info"/>
<view>{{info.message}}</view>
<view>{{info.sum(20,30)}}</view>
8.事件
8.1.常见的事件类型
- 通过bind/catch这个属性绑定在组件上的
- 从1.5.0版本开始,可以在bind和catch后加上一个冒号
- 简单演练:
<button bindtap="handleBtnClick">按钮</button>
<button bind:tap="handleBtnClick">按钮</button>
<button catch:tap="handleBtnClick">按钮</button>
- 某些组件会有自己特性的事件类型
比如input有bindinput、bindblur、bindfocus等
scroll-view有bindscrolltowpper、bindscrolltolower - 比较常见的事件类型:
touchcancle在某些特定场景下才会触发
tap事件和longpress事件通常只会触发其中一个
8.2.事件对象的解析
- 当某个事件触发时,会产生一个事件对象,并且这个对象被传入到回调函数中
- 事件对象常见属性:
- touches和changedTouches的区别
touches记录当前有几个手指在小程序中触摸的以及对应的触摸点信息
changedTouches用来记录变化的
什么情况下可以看到区别:
在touchend中不同
多手指触摸时不同 - currentTarget和target的区别
currentTarget记录触发事件组件
target记录产生事件的组件
8.3.事件参数的传递
- 某些情况需要事件携带一些参数到执行的函数中,这个时候可以通过
data-
属性完成 - 格式:data-属性的名称
- 获取:e.currentTarget.dataset.属性的名称
8.4.事件冒泡和事件捕获
- 事件捕获:capture-bind:tap
- 事件冒泡:bindtap
- capture-catch:tap、catchtap:阻止事件进一步传递
9.组件化开发
9.1.创建自定义组件
- 类似于页面,自定义组件由json wxml wxss js 4个文件组成
- 根目录下创建一个components,里面存放我们之后自定义的公共组件
- 如何注册组件,在需要使用的页面的json文件里:
{
"usingComponents": {
"my-cpn" : "../../components/my-cpn/my-cpn"
}
}
- wxml中节点标签名只能是小写字母、中划线、下划线的组合,故自定义组件的标签名也只能包含这些字符
- 自定义组件和页面所在项目根目录名不能以“wx-”为前缀
- 如果在app.json的usingComponents声明某个组件,那么所有页面和组件可以直接使用该组件
9.2.组件和页面样式细节
- 组件内的class样式不会对页面产生影响
- 组件内不能使用id、属性、标签选择器
- 外部使用class样式对组件内不生效
- 外部使用id选择器、属性选择器对组件内不生效
- 外部使用标签选择器对组件内产生影响
- 组件内的class样式和组件外的class样式,默认是有一个隔离的效果;为了防止样式的错乱,官方不推荐使用id、属性、标签选择器
- 如何让class相互影响:
//自定义组件的js文件里
//styleIsolation有三个值
//1.默认为isolated,不会相互影响
//2.apply-shared:表示页面wxss样式将影响到自定义组件
// 但自定义组件wxss中指定的样式不会影响页面
//3.shared:页面会影响自定义组件,自定义组件也影响页面
Component({
options: {
styleIsolation:"isolated"
}
})
9.3.给组件传递数据和样式
- 组件和页面通信
- 给自定义组件传递数据:properties
//组件对应的js文件里
Component({
properties: {
title: {
type: String,
value: '我是标题',//默认值
observer: function(newVal,oldVal) {
console.log(newVal,oldVal);
}
}
}
})
//组件的wxml文件:
<view class="title">{{title}}</view>
//如何传递数据
<my-cpn title="哈哈哈"></my-cpn>
- 给自定义组件传递样式:externalClasses
//组件对应的js文件里
Component({
externalClasses: ['titleclass']
})
//组件的wxml文件:
<view class="titleclass">{{title}}</view>
//页面,样式green写在页面的wxss文件里
<my-cpn titleclass="green"></my-cpn>
9.4.组件向外传递事件-自定义事件
this.triggerEvent('事件名称',{数据});
- 页面:
<view bind:事件名称=" "></view>
9.5.tab-control的练习
// tab-control.wxml
<view class="outer">
<block wx:for="{{text}}" wx:key="index">
<view class="{{currentIndex==index ? 'active' : '' }}"
bind:tap="click" data-index="{{index}}">{{item}}</view>
</block>
</view>
// tab-control.js
Component({
properties: {
text: {
type: Array,
value: []
}
},
data: {
currentIndex: 0
},
methods: {
click(event) {
const index = event.currentTarget.dataset.index;
this.setData({
currentIndex: index
})
//通知页面内部的点击事件
this.triggerEvent('itemclick',{index})
}
}
})
// home.wxml
<tab-control text="{{['流行','新款','精选']}}"
bind:itemclick="tabControlClick"></tab-control>
9.6.获取组件对象的方式
- 如何在页面中点击按钮直接修改组件内定义的数据
//拿到组件对象
const obj = this.selectComponent('class/id');
//通过setData修改组件中的数据(不够规范)
obj.setData({
})
//规范写法:通过方法对数据进行修改
//该方法是在组件的methods定义的
obj.方法
9.7. 插槽slot
- 多个插槽的使用:
需要给每个插槽起个名字name
使用的时候<button slot="name"></button>
在options中添加multipleSlots:true
9.8.component构造器
- 构造器
- 组件中监听生命周期函数:
1.监听所在页面的生命周期pageLifetimes
show:监听组件所在页面显示出来时
hide:监听组件所在页面隐藏起来时
resize:监听页面尺寸的改变
2.监听组件本身的生命周期lifetimes
created:组件被创建出来时
attached:组件被添加到页面
ready:组件被渲染出来
moved:组件被移动到另外一个节点
detached:组件被移除掉
10.系统API:网络请求
10.1.基本使用过程
- 在小程序/小游戏中使用网络相关的 API 时,需要事先设置通讯域名,小程序只可以跟指定的域名进行网络通信
- 配置流程:服务器域名在小程序后台-设置-开发设置-服务器域名中进行配置
- 如何使用:
onLoad: function (options) {
wx.request({
url: 'http://123.207.32.32:8000/recommend',
method: post,//默认为get
data:{
type: 'sell',
page: 1
}
success:function(res) {
console.log(res);
}
})
}
- 一些属性:
10.2.工具函数封装
- service/network.js
export default function request(options) {
return new Promise((resolve,reject) => {
wx.request({
url: options.url,
method: options.method || 'get',
success: function(res) {
resolve(res)
},
fail: function(err) {
reject(err)
}
})
})
}
- 如何调用
import request from ''//相对路径
Page({
onLoad:function(options){
request({
url:''
})
}.then(res=>{
}).catch(err=>{
})
})
11.系统API:展示弹窗
- 小程序展示弹窗的四种方式:showToast、showModal、showLoading、showActionSheet
- 参数查文档即可
11.1.showToast
//wxml文件:
<button size="mini" bind:tap="handleShowToast">showToast</button>
//js文件:
handleShowToast() {
wx.showToast({
title: 'nihaoa'
})
}
11.2.showModal
handleShowModal() {
wx.showModal({
title: '我是标题',
content:'我是内容',
success: function(res) {
if(res.cancel) {
console.log('用户点击了取消按钮');
}
if(res.confirm) {
console.log('用户点击了确认按钮');
}
}
})
},
11.3.showLoading
- 不会自动消失
- 需要手动调用函数才会让loading消失
handleShowLoading() {
wx.showLoading({
title: '加载ing'
})
setTimeout(() => {
wx.hideLoading()//手动调用让loading消失
},1000)
}
12.系统API:页面分享
- 小程序中有两种分享方式:
点击右上角的菜单按钮,之后点击转发
点击某一按钮,直接转发 - 当我们转发给好友一个小程序时,小程序会显示一些信息,如何决定这些信息的展示,通过onShareAppMessage
- 分享按钮:
open-type=“share”
<button size="mini" open-type="share">分享</button>
13.系统API:登录流程
- 登录流程图
- 一般情况下,登录在app.js中实现
14.系统API:界面跳转
- 界面的跳转有两种方式:通过navigator组件和通过wx的API跳转
14.1.navigator组件
- 属性:
- open-type的值:
取值为navigatorBack时,可以多个属性为delta=“”
,返回多少个层级 - 代码:
<navigator url="../detail/detail"
open-type="redirect">跳到详情页</navigator>
14.2.跳转过程进行数据传递
- 传递方式
- 首页往详情页传递数据用query
详情页如何获取:在js文件的onLoad函数中的options里 - 详情页往首页传递数据:
在onUnload的生命周期函数里
//detail.js
Page({
onUnload() {
//1、获取首页的页面对象
const pages = getCurrentPages();
const home = pages[pages.length-2];
//2.调用页面对象的setData
home.setData({
title: '哈哈哈'
})
}
})
14.3.通过代码页面跳转
//home.wxml
<button size="mini" bindtap="handlePushDetail"></button>
//home.js
handlePushDetail(){
wx.navigateTo({
url: ''
})
}
15.项目实战
15.1.底部tabbar
//app.json中
"tabBar": {
"selectedColor": "#ff5777",
"list": [{
"pagePath": "pages/home/home",
"text": "首页",
"iconPath": "/assets/img/tabbar/home.png",
"selectedIconPath": "/assets/img/tabbar/home-ac.png"
},
{
"pagePath": "pages/category/category",
"text": "分类",
"iconPath": "/assets/img/tabbar/tabbar.png",
"selectedIconPath": "/assets/img/tabbar/tabbar-ac.png"
}
}
15.2.轮播图
- 顶部导航栏如果文字要求不同时可以单独再在对应页面的json文件里设置
- 数据请求:network/home.js
import request from './network'
const baseURL = "http://152.136.185.210:7878/api/hy66"
export function getMultiData() {
return request({
url: baseURL + '/home/multidata',
})
}
//pages/home/home.js
// pages/home/home.js
import {getMultiData} from '../../service/home.js'
Page({
data: {
banners: [],
recommends: []
},
onLoad: function (options) {
//1.请求轮播图以及推荐数据
getMultiData().then(res => {
//取出轮播图和推荐的数据
const banners = res.data.data.banner.list;
const recommends = res.data.data.recommend.list;
this.setData({
banners,
recommends
})
})
}
})
- 轮播图组件封装
//w-swiper.js
<!--components/w-swiper/w-swiper.wxml-->
<swiper class="swiper"
circular
autoplay
interval="3000"
duration="300"
indicator-dots
indicator-color="#fff"
indicator-active-color="#ff577"
>
<block wx:for="{{list}}" wx:key="index">
<swiper-item class="swiper-item">
<image src="{{item.image}}" mode="widthFix"/>
</swiper-item>
</block>
</swiper>
- 页面展示轮播图
<w-swiper list="{{banners}}"></w-swiper>
15.3.注意
- 在wxss中不能用本地图片,只能引用网络图片
- 上拉加载更多
onReachBottom()
监听 - 回到顶部功能
handleBackTop() {
wx.pageScrollTo({
scrollTop: 0
})
}
如果有bug则可以用如下方法:
- 监听页面的滚动:
onPageScroll
- 不要在滚动的函数回调中频繁调用this.setData
- 获取某个组件距离顶部的距离:
最好是在对高度影响最大的组件监听载入完毕(bindload)时调用获取距离
wx.createSelectorQuery().select('#id/class').boundingClientRect(rect => {
console.log(rect)
}).exec()