1.小程序发展史
微信小程序之前,是使用weixin-sdk进行开发,调用视频,摄像头等。
微信小程序weixin up端,所以PC端的window这些没有,运行环境是IOS,安卓等,有一些特殊的调用录音功能,摄像头等
申请账号:开发账号+开发工具
2.小程序全局配置app.json
小程序根目录下的 app.json 文件用来对微信小程序进行全局配置。
- pages 页面路径列表
- window 全局的默认窗口表现
- tabBar 底部 tab 栏的表现
window
- navigationBarBackgroundColor 导航栏背景颜色,如 #000000
- navigationBarTextStyle 导航栏标题颜色
- navigationBarTitleText 导航栏标题文字内容
- enablePullDownRefresh 是否开启全局的下拉刷新
tarBar
- color tab 上的文字默认颜色,仅支持十六进制颜色
- selectedColor tab 上的文字选中时的颜色,仅支持十六进制颜色
- backgroundColor tab 的背景色,仅支持十六进制颜色
- borderStyle tabbar 上边框的颜色, 仅支持 black / white
- position tabBar 的位置,仅支持 bottom / top
- custom 自定义 tabBar
- list tab 的列表,详见 list 属性说明,最少 2 个、最多 5 个 tab
3.页面配置
页面配置高于全局配置。页面配置和全局配置相同时,页面配置会覆盖全局配置
4.小程序生命周期
分为应用的生命周期、页面的生命周期、组件的生命周期
应用的声明周期:onLaunch()
App({
/**
* 当小程序初始化完成时,会触发 onLaunch(全局只触发一次)
*/
onLaunch: function () {
console.log('onLaunch --- app')
},
/**
* 当小程序启动,或从后台进入前台显示,会触发 onShow
*/
onShow: function (options) {
console.log('onShow --- app')
},
/**
* 当小程序从前台进入后台,会触发 onHide
*/
onHide: function () {
console.log('onHide --- app')
}
})
页面的生命周期:
onLoad()页面首次加载,可接受一个参数
Page({
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
console.log('onLoad --- page')
},
/**
* 生命周期函数--监听页面初次渲染完成
*/
onReady: function () {
console.log('onReady --- page')
},
/**
* 生命周期函数--监听页面显示
*/
onShow: function () {
console.log('onShow --- page')
},
/**
* 生命周期函数--监听页面隐藏
*/
onHide: function () {
console.log('onHide --- page')
},
/**
* 生命周期函数--监听页面卸载
*/
onUnload: function () {
console.log('onUnload --- page')
}
})
组件的生命周期(包括组件本身生命周期和组件所在页面的生命周期)
组件移动,拖拽等
组件所在页面的生命周期(当小程序进入后台,后台进入前台进行操作时,调整屏幕尺寸等),如再次进入前台时,列表页数据展示的刷新,就是根据组件内页面的生命周期完成
Component({
// 组件的生命周期
lifetimes: {
// 在组件实例刚刚被创建时执行
created: function () {
console.log('created --- component')
},
// 在组件实例进入页面节点树时执行
attached: function () {
console.log('attached --- component')
},
// 在组件在视图层布局完成后执行
ready: function () {
console.log('ready --- component')
},
// 在组件实例被移动到节点树另一个位置时执行
moved: function () {
console.log('moved --- component')
},
// 在组件实例被从页面节点树移除时执行
detached: function () {
console.log('detached --- component')
}
},
// 组件所在页面的生命周期
pageLifetimes: {
// 组件所在的页面被展示时执行
show: function () {
console.log('show --- component')
},
// 组件所在的页面被隐藏时执行
hide: function () {
console.log('hide --- component')
},
// 组件所在的页面尺寸变化时执行
resize: function () {
console.log('resize --- component')
}
}
})
执行流程:
5.页面路由
页面路由:在小程序中所有页面的路由全部由框架进行管理。
页面栈:
框架以栈的形式维护了当前的所有页面。 当发生路由切换的时候,可以使用 getCurrentPages()
函数获取当前页面栈。
重加载:一般不用,小程序打开或者页面刷新时
测试:
使用wx.navigateTo()从index跳转到logs页面:
使用wx.redirectTo()跳转
注意事项:
navigateTo
,redirectTo
只能打开非 tabBar 页面。switchTab
只能打开 tabBar 页面。reLaunch
可以打开任意页面。- 页面底部的 tabBar 由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar。
- 调用页面路由带的参数可以在目标页面的
onLoad
中获取。
6.API接口
API接口可以直接使用的方法:App(),Page(),Component()以及组件中的全局接口wx.navigateTo()等方法
App()
- App()中可以监听onLaunch()等钩子函数;
- 创建全局应用实例app(类似window):App()中定义在globalData属性中,在页面通过const app= getApp()获取,然后各种方法如onLoad()钩子函数中即可使用
Page()
获取页面栈方法:getCurrentPages()。
数组中第一个元素为首页,最后一个元素为当前页面。
如订单支付后返回上级或者返回多级,即可通过getCurrentPages()获取当前页面栈信息。或替换成另一个页面
Router页面路由对象
可以通过 this.pageRouter
或 this.router
获得当前页面或自定义组件的路由器对象。
页面路由器有 switchTab
reLaunch
redirectTo
navigateTo
navigateBack
五个方法,与 wx 对象向同名的五个方法 switchTab reLaunch redirectTo navigateTo navigateBack 功能相同
页面路由器Router对象和wx对象区别:
- 页面路由器中的方法调用时,相对路径永远相对于
this
指代的页面或自定义组件。 this.pageRouter
获得的路由器对象具有更好的基路径稳定性。通常情况下,使用this.pageRouter.navigateTo
代替wx.navigateTo
是更优的。- wx对象方法跳转的新路径是绝对路径,
this.pageRouter.navigateTo
跳转后的新路径是相对路径,相对于当前this页面
this.pageRouter
和 this.router
在页面中将获得同样的页面路由器对象。但如果在自定义组件中调用, this.pageRouter
将相对于自定义组件所在的页面来进行路由跳转,而 this.router
相对于自定义组件自身的路径。
Component()
相对比较复杂,页面中如果需要使用到监听等特殊用法也需要使用Component()
7.模块化
导出:
module.exports 或者 exports,exports
是 module.exports 的一个引用,随意更改 exports
的指向会造成未知的错误。所以更推荐开发者采用 module.exports
来暴露模块接口
module.exports.sayHello = sayHello
exports.sayGoodbye = sayGoodbye
小程序目前不支持直接引入 node_modules,
使用到 node_modules
时候建议拷贝出相关的代码到小程序的目录中,或者使用小程序支持的 npm 功能。
使用:
使用这些模块的文件中,使用 require
将公共代码引入
var common = require('common.js')
8.数据绑定
简单绑定: {{ message }}
属性中绑定:<view id="item-{{id}}"></view>
控制属性: wx:if="condition"
关键字:<checkbox checked="{{false}}"></checkbox>,如果直接写字符串会进行隐式转换为true
可以进行表达式计算、算术运算
可以对对象和数组中的变量和数据进行组合
<view wx:for="{{[zero, 1, 2, 3, 4]}}"> {{item}} </view>
Page({
data: {
zero: 0
}
})
属性后面有空格会解析成空白字符串
<view wx:for="{{[1,2,3]}} ">
{{item}}
</view>
等同于
<view wx:for="{{[1,2,3] + ' '}}">
{{item}}
</view>
9.条件渲染
wx:if wx:elif wx:else,一般用在<block>标签中
wx:if
和 hidden区别【-
wx:if
之中的模板也可能包含数据绑定,所以当wx:if
的条件值切换时,框架有一个局部渲染的过程,因为它会确保条件块在切换时销毁或重新渲染。wx:if
也是惰性的,如果在初始渲染条件为false
,框架什么也不做,在条件第一次变成真的时候才开始局部渲染。hidden
就简单的多,组件始终会被渲染,只是简单的控制显示与隐藏(display属性none和block)。wx:if
有更高的切换消耗而hidden
有更高的初始渲染消耗。因此,如果需要频繁切换的情景下,用hidden
更好,如果在运行时条件不大可能改变则wx:if
较好。
10.列表渲染 wx:for
默认数组的当前项的下标变量名默认为 index
,数组当前项的变量名默认为 item(不需要重新声明)
使用 wx:for-item
可以指定数组当前元素的变量名,
使用 wx:for-index
可以指定数组当前下标的变量名:
<view wx:for="{{array}}">
{{index}}: {{item.message}}
</view>
如果是对象遍历的item和index分别是key和value
block wx:for使用block标签包裹
wx:key唯一值指定列表中项目的唯一的标识符。
wx:for
的值为字符串时,会将字符串解析成字符串数组
wx:for="array"
等同于
wx:for="{{['a','r','r','a','y']}}
11.模版渲染和引入
定义模板:
使用 name 属性,作为模板的名字。然后在<template/>
内定义代码片段
<template name="msgItem">
<view>
<text> {{index}}: {{msg}} </text>
<text> Time: {{time}} </text>
</view>
</template>
使用模板
当前页面可直接使用,如果跨页面需要进行引用。两种文件引用方式import
和include
使用 is 属性,声明需要的使用的模板,然后将模板所需要的 data 传入,
<import src="item.wxml"/>
<template is="item" data="{{text: 'forbar'}}"/>
import 的作用域
只会 import 目标文件中定义的 template,而不会 import 目标文件 import 的 template。
如:C import B,B import A,在C中可以使用B定义的template
,在B中可以使用A定义的template
,但是C不能使用A定义的template
。
include引入
include
可以将目标文件除了 <template/>
<wxs/>
外的整个代码引入,相当于是拷贝到 include
位置
<include src="header.wxml"/>
<view> body </view>
<include src="footer.wxml"/>
12.事件
bindtap点击事件;
detail:自定义事件携带数据,点击事件带有的 x, y 同 pageX, pageY 代表距离文档左上角的距离。
currentTarget:如果有事件冒泡,使用currentTarget获取的dataset,表示当前组件上由data-
开头的自定义属性组成的集合数据
阻止事件冒泡:使用catchtap即可阻止事件冒泡
<view id="outer" bindtap="handleTap1">
outer view
<view id="middle" catchtap="handleTap2">
middle view
<view id="inner" bindtap="handleTap3">
inner view
</view>
</view>
</view>
手指触摸事件:小程序中用得不多,内部有封装很多拖拽事件
13.wxss
与 CSS 相比,WXSS 扩展的特性有:
- 尺寸单位
- 样式导入
一般设计稿规范:以iPhone6 为基准,屏幕宽度为375px,共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素
样式导入:@import "common.wxss"
内联样式:如果需要动态渲染可以使用,如果纯静态样式不建议使用内联会影响渲染速度
全局样式app.wxss会作用域全局,但局部样式优先级高于全局
14.自定义组件
右击创建components创建自定义组件
使用时:页面js文件中usingComponent中引入;wxml页面中引入组件
注意:在组件wxss中不应使用ID选择器、属性选择器和标签名选择器。
注意点:
- 自定义组件名只能是小写字母、中划线和下划线的组合
- 使用
usingComponents
字段引入; - 自定义组件和页面所在项目根目录名不能以“wx-”为前缀,否则会报错。
- 出于性能考虑,使用
usingComponents
时,setData
内容不会被直接深复,即this.setData({ field: obj })
后this.data.field === obj
。(深复制会在这个值被组件间传递时发生。)
15自定义组件模版和样式
自定义组件插槽<slot>
默认插槽和具名插槽,基本和vue一致
如果要使用多个插槽需要在options中设置multipleSlots: true
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
},
组件样式:
不能使用id,属性,标签 ,子元素选择器(.a > .b)
可以设置默认样式: :host{ color: red; },当前自定义组件下所有默认样式
组件间样式隔离:
父子组件都有同一个样式,样式设置在page中,自定义组件中使用。发现自定义组件中样式没有生效。
页面和组件样式都是相互隔离的。
改变:使用options:{ styleIsolation: "isolated" }进行修改
styleIsolation属性值:
isolated
表示启用样式隔离,在自定义组件内外,使用 class 指定的样式将不会相互影响(一般情况下的默认值);apply-shared
表示页面 wxss 样式将影响到自定义组件,但自定义组件 wxss 中指定的样式不会影响页面;shared
表示页面 wxss 样式将影响到自定义组件,自定义组件 wxss 中指定的样式也会影响页面和其他设置了apply-shared
或shared
的自定义组件。(这个选项在插件中不可用。)
外部样式类
组件希望接受外部传入的样式类。此时可以在 Component
中用 externalClasses
定义段定义若干个外部样式类。
实现类似于 view
组件的 hover-class
属性:页面可以提供一个样式类,赋予 view
的 hover-class
,这个样式类本身写在页面中而非 view
组件的实现中。
注意:在同一个节点上使用普通样式类和外部样式类时,两个类的优先级是未定义的,因此最好避免这种情况。
引用页面或父组件的样式
使启用了样式隔离 isolated
,组件仍然可以在局部引用组件所在页面的样式或父组件的样式。
虚拟化组件节点
默认情况下,自定义组件本身的那个节点是一个“普通”的节点,使用时可以在这个节点上设置 class
style
、动画、 flex 布局等,就如同普通的 view 组件节点一样。
自定义组件并不希望这个节点本身可以设置样式、响应 flex 布局等,而是希望自定义组件内部的第一层节点能够响应 flex 布局或者样式由自定义组件本身完全决定。这种情况下,可以将这个自定义组件设置为“虚拟的”:
Component({
options: {
virtualHost: true
},
properties: {
style: { // 定义 style 属性可以拿到 style 属性上设置的值
type: String,
}
},
externalClasses: ['class'], // 可以将 class 设为 externalClasses
})
<!-- 页面的 WXML -->
<view style="display: flex">
<!-- 如果设置了 virtualHost ,节点上的样式将失效 -->
<custom-component style="color: blue">不是蓝色的</custom-component>
</view>
<!-- custom-component.wxml -->
<view style="flex: 1">
满宽的
<slot></slot>
</view>
需要注意的是,自定义组件节点上的 class
style
和动画将不再生效,但仍可以:
- 将 style 定义成
properties
属性来获取 style 上设置的值; - 将 class 定义成
externalClasses
外部样式类使得自定义组件 wxml 可以使用 class 值。
16.Component构造器
Component构造器有两种作用: 创建自定义组件;页面中使用 Component 构造器构造页面
Component构造器创建自定义组件:
Component
构造器可用于定义组件,调用 Component
构造器时可以指定组件的属性、数据、方法等。
页面中使用 Component 构造器构造页面
监听数据,公共逻辑复用(behaviors)等。
Component 构造器构造页面也可以实现以下功能:
onLoad()中获取页面跳转后的参数;
可以通过properties获取父级页面的数据;(properties定义的数据,可以通过this.data进行获取)
17.双向绑定
通的属性的绑定是单向的
<input value="{{value}}" />
如果使用 this.setData({ value: 'leaf' })
来更新 value
,this.data.value
和输入框的中显示的值都会被更新为 leaf
;但如果用户修改了输入框里的值,却不会同时改变 this.data.value
。
手动实现双绑
如果要实现可以使用bindinput然后手动更改this.data.value的值
<input type="text" bindinput="inputChange"/>
<span>这是输入框的值: {{inputText}}</span>
Component({
data: {
inputText: ''
},
methods: {
inputChange(e){
console.log(e.detail.value);
this.setData({
inputText: e.detail.value
})
},
}
})
实现简单双向绑定:属性前加上model:value
注意是model:value="{{inputText}}"不是model:input-text="{{inputText}}"。model:input-text="{{inputText}}"是自定义组件双绑方法
<input type="text" model:value="{{inputText}}"/>
<span>这是输入框的值: {{inputText}}</span>
用于双向绑定的表达式有如下限制
- 只能是一个单一字段的绑定,如以下是非法的
<input model:value="值为 {{value}}" />
<input model:value="{{ a + b }}" />
- 目前,尚不能 data 路径,如不支持属性深层双绑监听
<input model:value="{{ a.b }}" />
在自定义组件中传递双向绑定
属性传值必须小写字母+ “-”形式,不能使用大驼峰
父组件: model:input-text="inputText"
子组件:通过properties接收,然后通过update方法直接在子组件中修改(和vue不同,vue必须通知父级修改)
父组件:
<input type="text" model:value="{{inputText}}"/>
<my-component model:input-text="{{inputText}}"></my-component>
data: {
inputText: ''
},
子组件:
<view>
子组件:{{inputText}}
</view>
<button bindtap="updateData">重置数据</button>
properties: {
inputText: String
},
methods: {
updateData: function() {
// 更新 inputText
this.setData({
inputText: '重置数据'
})
}
},
18.组件通信
- WXML数据绑定(slot插槽方式)
- 事件方式:用于子组件像父组件传递数据
- 父组件this.selectComponent()获取子组件实例对象,直接操作子组件的数据和方法
事件方式:
父组件中传入数据和方法:input-text="{{inputText}}" bind:set-input="handleInput"。handleInput方法中监听子组件传递过来数据并更改inputText
子组件中:properties获取数据,然后在button点击时,通过this.triggerEvent("set-input",{inputText:"重置数据"})进行修改
父组件:
<input type="text" model:value="{{inputText}}"/>
<my-component input-text="{{inputText}}" bind:set-input="handleInput"></my-component>
handleInput(e){
this.setData({
inputText: e.detail.inputText
})
},
子组件:
<view>
子组件:{{inputText}}
</view>
<button bindtap="updateData">重置数据</button>
updateData: function() {
// 自定义事件实现双绑
this.triggerEvent('set-input', {inputText: "重置数据"})
},
triggleEvent触发事件选项(该方法第三个参数)
bubbles:事件是否冒泡,默认false
composed:是否可以穿越边界。如下为false时事件只在my-component组件中触发时生效,外层的another-component不会生效
<another-component bindcustomevent="pageEventListener1">
<my-component bindcustomevent="pageEventListener2"></my-component>
</another-component>
获取组件实例this.selectComponent
在父组件里调用 this.selectComponent
,获取子组件的实例对象。
因为时在父级操作子组件,所以会造成逻辑混乱。所以除特殊需要一般不要使用
const child = this.selectComponent('.my-component');
19.数据监听器observers
类似vue的watch,但是比vue的watch更加强大,在watch基础上也实现了computed的功能。
数据监听器可以用于监听和响应任何属性和数据字段的变化。 2.6.1 以上基础库版本
只能定义在Component()下面
numberA和numberB任何一个变量修改都会重新赋值,相当与vue的computed
numberA:<input model:value="{{numberA}}"> </input>
numberB:<input model:value="{{numberB}}"></input>
sum和为:{{sum}}
Component({
data: {
// inputText: ''
numberA:0,
numberB:0,
sum:0
},
observers:{
'numberA,numberB':function(newA,newB){
this.setData({
sum: parseInt(newA) + parseInt(newB)
});
}
},
})
购物车案例:
<!-- 购物车案例 -->
<view>
<view class="name" wx:for="{{item}}" wx:key="id">
<view>{{item.name}}</view>
<view>¥{{item.price}}</view>
<view class="number-btn" bindtap="changeNum" data-index="{{index}}" data-type="add">+</view>
<input type="number" model:value="{{item.num}}" class="number-input"/>
<view class="number-btn" data-index="{{index}}" data-type="sub" bindtap="changeNum">-</view>
</view>
<view>
合计:¥{{totalPrice}}
</view>
</view>
Component({
data: {
item:[
{
id:1,
name: '华为Mate 50E 4G',
price: 3999,
num:0
},
{
id:2,
name: '苹果15',
price: 7400,
num:0
},
],
totalPrice:0
},
observers:{
'item':function(newVal){
let totalPrice = this.data.item.reduce((sum,item)=>{
return sum = sum + item.price * item.num
},0)
this.setData({
totalPrice
});
}
},
methods: {
changeNum(e){
console.log(e);
let { type, index } = e.target.dataset;
let data = JSON.parse(JSON.stringify(this.data.item))
if(type==='add'){
data[index].num +=1;
}else{
data[index].num -=1;
}
this.setData({
item: data
});
},
ready(){
let totalPrice = this.data.item.reduce((sum,item)=>{
return sum = sum + item.price * item.num
},0)
this.setData({
totalPrice
})
}
}
})
20.纯数据字段
定义在data中的字段可以进行组件间传递,但是也同时会影响页面的性能。那么就可以给不需要进行页面渲染只存在逻辑处理的数据声明为纯数据字段
- options的pureDataPattern定义匹配所有纯数据字段
- this.data._b可以获取,但是页面不会进行渲染即纯数据字段不会被应用到 WXML 上
- 属性中的纯数据字段的属性 observer 永远不会触发!如果想要监听属性值变化,使用 数据监听器 代替
Component({
options: {
pureDataPattern: /^_/ // 指定所有 _ 开头的数据字段为纯数据字段
},
data: {
a: true, // 普通数据字段
_b: true, // 纯数据字段
},
methods: {
myMethod() {
this.data._b // 纯数据字段可以在 this.data 中获取
this.setData({
c: true, // 普通数据字段
_d: true, // 纯数据字段
})
}
}
})
_b: {
type: Boolean,
observer() {
// 不要这样做!这个 observer 永远不会被触发
}
},
observers: {
timestamp: function () {
// timestamp 被设置时,将它展示为可读时间字符串
var timeString = new Date(this.data.timestamp).toLocaleString()
this.setData({
timeString: timeString
})
}
}
21.滚动组件scroll-view
注意点:
- scroll-y写法
- scroll-into-view="{{scrollTo}}的值必须是动态变量
- <view id="{{item.id}}"> scroll-view里面的数据必须有id且id不能以数字开头如 id: 'test'+i,
<!-- scroll-view滚动组件 -->
<view class="header">
这是头部区域
<button bindtap="gotoPos">定位到id为50处</button>
</view>
<!-- 注意scroll-y写法 -->
<!-- scroll-into-view的值必须是动态变量,且scroll-view里面的数据必须有id且id不能以数字开头 -->
<scroll-view scroll-y class="scroll-view" scroll-with-animation="true" scroll-into-view="{{scrollTo}}">
<block wx:for="{{scrollItem}}" wx:key="id">
<view id="{{item.id}}">
{{item.id}}-{{item.name}}
</view>
</block>
</scroll-view>
.header{
width: 100%;
height: 200rpx;
background-color: darkgreen;
}
.scroll-view{
height: calc(100% - 100px);
}
Component({
data: {
scrollItem:[],
scrollTo:0
},
methods: {
onLoad(){
const data = Array(100).fill(0).map((_, i) => {
return {
id: 'test'+i,
name: i+'test'
}
});
this.setData({
scrollItem: data
});
},
gotoPos(){
this.setData({
scrollTo: 'test'+50
});
}
}
})