基础能力
小程序宿主环境
定义:宿主环境指的是微信客户端给小程序提供的环境,小程序借助该环境提供的能力,能完成许多普通网页无法完成的功能
小程序的运行环境分为:
- 渲染层(视图层,wxml、wxss):使用webbiew进行渲染
- 逻辑层(js):使用jsCore运行脚本
使用事项:
- 小程序的渲染层和逻辑层分别由2个线程管理
- 一个小程序存在多个界面,所以渲染层存在多个webview线程,线程的通信会经由客户端(native)做中转,逻辑层发送网络请求也经过native转发
- 小程序启动只会,在app.js定义的App实例的onLaunch回调会被执行
- 整个小程序只有一个App实例,被全部页面共享
逻辑层App Service
定义:逻辑层使用JavaScript引擎为小程序提供js运行的环境和特有功能
使用:
- 逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈
- 所有代码会打包成一份js文件,在小程序启动时运行,直到其被销毁,这一行为类似ServiceWorker,所以逻辑层也称为App Service
- 小程序逻辑层并非运行在浏览器中,所以一些web的能力无法使用,比如window、document等
逻辑层增加的功能:
- App和Page方法:进行程序和页面的注册
- getApp、getCurrentPages:获取App实例和页面栈
- 特有的api:用户数据、扫一扫、支付
- 模块化:让每个页面有独立的作用域
小程序注册
使用:
App()
必须在app.js中调用,只能调用一次
getApp()
:
- 获取小程序实例,用于非app.js页面,app.js中使用this即可获取到
- 获取实例后,不要私自调用生命周期函数
- 参数为objct,如
{allowDefault: true}
表示在app未定义时返回默认实现,在app被调用时,合并属性到app中,用于独立分包中
// app.js
App({
// 监听小程序初始化,只触发一次,参数可使用wx.getLaunchOptionsSync()获取
onLaunch () {},
// 监听小程序启动或切到前台,也可使用wx.onAppShow绑定监听
onShow() {},
// 监听小程序切到后台,也可使用wx.onAppHide绑定监听
onHide() {},
// 错误监听函数,也可使用wx.onError绑定监听
onError () {},
// 页面不存在的监听函数,也可使用wx.onPageNotFound绑定监听
onPageNotFound () {},
// 未处理的promise拒绝事件监听函数,也可使用wx.onUnhandledRejection绑定监听
onUnhandledRejection () {},
// 系统主题变化的监听,也可使用wx.onThemeChange绑定监听
onThemeChange () {},
// 其他函数或变量,可用this访问
globalData: {
desc: 'i am global data'
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
页面注册
使用规则:
- 简单页面使用Page构造器
- 复杂页面使用Component构造器,相比于Page,方法放在methods中,类似自定义组件
- 可以使用getCurrentPages获取当前页面栈,数组第一个元素为首页,最后一个元素为当前页面
- 不要尝试修改页面栈,会导致路由和页面状态错误
- 不要在App.onLaunch中调用getCurrentPages,此时page还未生成
组件事件处理函数:
- 获取路由器对象:
this.route
(若是组件,表示组件路由器,若是页面,表示页面路由器)、this.pageRouter
(页面路由器) - 设置数据:
this.setData({key: value})
页面路由:小程序所有页面的路由全部由框架进行管理
页面栈表现:
路由方式 | 页面栈表现 | 触发时机 | 路由前页面 | 路由后页面 | 注意事项 |
---|---|---|---|---|---|
初始化 | 新页面入栈 | 小程序打开的第一个页面 | onLoad,onShow | ||
打开新页面 | 新页面入栈 | 调用wx.navigateTo、<navigator open-type="navigateTo"/> | onHide | onLoad, onShow | 只能打开非tab页面 |
页面重定向 | 当前页面出栈,新页面入栈 | wx.redirectTo、<navigator open-type="redirectTo"/> | onUnload | onLoad, onShow | 只能打开非tab页面 |
页面返回 | 页面不断出栈,直到目标返回页 | 调用wx.navigateBack、<navigator open-type="navigateBack"/> 、按左上角返回按钮 | onUnload | onShow | |
tab切换 | 页面全部出栈,只留下新的tab页面 | 调用wx.switchTab、<navigator open-type="switchTab"/> 、用户切换tab | 只能打开tabbar页面 | ||
重加载 | 页面全部出栈,只留下新的页面 | 调用wx.reLaunch、<navigator open-type="reLaunch"/> | onUnload | onLoad, onShow | 可以打开全部页面 |
页面路由器的方法,与wx对象同名的方法功能相同,唯一区别是页面路由器方法调用时,相对路径永远相对于this指代的页面/组件,5个方法是:
- switchTab
- reLaunch
- redirectTo
- navigateTo
- navigateBack
// pages/index
Page({
wxNavAction () {
// 相对于当前路由所在组件,若调用该方法时已经跳转到了packA/index,此时却是packA/new-page
wx.navigateTo({
url: './new-page'
})
},
routerNavAction () {
// 相对于当前代码所在组件,即pages/index,所以此时是pages/new-page
this.router.navigateTo({
url: './new-page'
})
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
页面通信:
- 若页面从另一个页面通过wx.navigateTo打开,两个页面间会建立数据通道,被打开的页面可通过
this.getOpenerEventChannel()
获取一个eventChannel对象。wx.navigateTo()
的success回调中也包含一个EventChannel对象,这两个eventchannel对象间可使用emit和on方法相互发送监听事件
// 页面通信
// index.js
Page({
jump () {
wx.navigateTo({
url: "./test",
events: {
// 接收从打开页面的消息
fromTestData (data) {
console.log(data)
}
},
// 成功后发送消息
success (res) {
res.eventChannel.emit(
'fromIndexData',
{
data: '这是index的内容'
}
)
}
})
}
})
// test.js
Page({
onLoad (option) {
const eventChannel = this.getOpenerEventChannel()
// 发送数据
eventChannel.emit('fromTestData', {data: '这是test的数据' })
// 监听数据
eventChannel.on('fromIndexData', data => {
console.log(data)
})
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Page({
// 页面初始数据
// 加载时,将会以JSON字符串形式由逻辑层传给渲染层,所以其内容必须符合json规范
data: {},
// 页面组件选项
options: {},
// 页面行为,需导入
behaviors: [behavior1, behavior2],
// 生命周期回调-----start
// 监听页面加载,query路径参数
onLoad (query) {},
// 监听页面显示
onShow () {},
// 监听页面初次渲染完成
onReady () {},
// 监听页面隐藏
onHide () {},
// 监听页面卸载
onUnload () {},
// 监听下拉动作,需在app.json中配置
onPullDownRefresh () {},
// 监听触底上拉动作
onReachBottom () {},
// 右上角转发,只有定义了此事件函数,才会显示转发按钮
onShareAppMessage ({ from, target, webViewUrl}) {
return {
title: '',
// 转发路径
path: '',
// 页面截图,可以是本地文件、网络路径,支持png、jpg
imageUrl: '',
// 该参数存在,则以resolve结果为准,三秒内不resolve,会使用上面传入的默认参数
promise: new Promise(resolve => {
setTimeout(() => {
resolve({
title: ''
})
}, 2000)
})
}
},
// 右上角转发到朋友圈,只有定义了此事件函数,才会显示该按钮
onShareTimeline () {
return {
title: '',
query: '',
imageUrl: ''
}
},
// 右上角收藏,包含web-view组件时,返回当前web-view的url
onAddToFaviorites ({ webViewUrl }) {
return {
// 自定义标题
title: '',
// 页面截图
imageUrl: "http://demo.png",
// 当前页面的query
query: "name=xxx&age=xxx"
}
},
// 监听页面滚动,页面在垂直方向滚动的距离,不要定义空方法,避免不必要的派发对渲染层-逻辑层通信的耗时影响
onPageScroll (scrollTop) {},
// 页面尺寸改变
onResize () {},
// 当前是tab页时,点击tab时触发
onTabItemTap ({ index, pagePath, text }) {},
// 页面销毁前保留状态回调
onSaveExitState () {},
// 其他方法或数据属性,通过this可以访问到,属性会在页面实例创建时进行深拷贝
viewTap () {
// setData
},
customData: {}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
页面生命周期
场景值
定义:描述用户进入小程序的路径
获取场景值的方式:
- 对于小程序,可以在App的onLaunch和onShow或wx.getLaunchOptionsSync中获取
- 对于小游戏,可在wx.getLaunchOptionsSync和wx.onShow获取
场景值列表:https://developers.weixin.qq.com/miniprogram/dev/reference/scene-list.html
API
场景:
- 提供了丰富的原生API,能方便调用微信提供的能力,如用户信息、本地存储、支付等
使用:
- 以on开头的API用来监听某个事件是否触发,接受一个回调作为参数,事件触发时会调用该回调函数
- 以Sync结尾的API是同步API,这些API的执行结果通过函数返回值直接获取,执行出错会抛出异常
- 其他大部分为异步api(当然也可能是同步的):这类api接受一个对象类型的参数,基本上都支持success/fail/complete这三个属性回调
- 异步api的属性回调通常包含一个Object类型参数,包括errMsg(fail)、errCode(fail)、执行结果(success)等属性
- 异步api支持callback和promise两种调用方式,接口参数不包含success、fail、complete时默认返回promise,否则是回调形式
- 云开发api,调用云函数,形式为
wx.cloud.xxx
,支持回调和promise的形式
// 事件监听类型的api
wx.onCompassChange(res => {
console.log(res)
})
// 同步api
try {
wx.setStorageSync('key', 'value')
} catch (e) {
console.error(e)
}
// 异步api
wx.login({
success (res) {
console.log(res)
},
fail ({ errMsg, errCode }){
console.log(errMsg, errCode)
}
})
wx.login().then(res => console.log(red)).catch(({errMsg, errCode}) => { xxx })
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
视图层 view
定义:
- 视图层由wxml和wxss编写,由组件进行展示
- 组件是视图的基本组成单元
WXML
功能:
- 数据绑定:
- 列表渲染:
wx:for="" wx:for-item="item" wx:for-index="index" wx:key="*this"
,item指定当前元素的变量名,index指定当前下标的变量名 - 条件渲染:
wx:if/elif/else=""
- 模板:定义template
<template name="template-name">xxx</template>
,使用template<template is="template-name" data=""/>
,和组件类似,可进行代码复用,data表示模板所需的数据对象,is可以是语法,可用于动态决定渲染哪个模板,模板拥有自己的作用域,只能使用传入的data以及模板定义文件中定义的
<wxs/>
模块 - 引用:wxml支持引入文件内容,有import和include两种方式
- import可使用导入的文件的template,但不能使用导入的文件导入的template
- include将导入的文件除template和wxs的部分外的内容整个引入,然后拷贝到该位置
<!-- 定义模板 -->
<template name="t1">
<view>{{index}}: {{msg}}</view>
<view>{{time}}</view>
</template>
<template name="t2">
<view>{{index}}: {{msg}}</view>
</template>
<!-- 使用模板 -->
<template is="{{isExist ? 't1' : 't2' }}" data="{{...{index: 0, msg: '', time: '2022' }}}"/>
2
3
4
5
6
7
8
9
10
11
12
<!-- item.wxml -->
<text>哈哈哈</text>
<template name="item">
<text>{{text}}</text>
</template>
<view>哈哈哈</view>
<!-- index.wxml -->
<!-- 使用导入的模板 -->
<import src="item.wxml"/>
<template is="item" data="{{ text: 'forbar' }}"/>
<!-- index2.wxml -->
<include src="item.wxml"/>
<view>footer</view>
<!-- 相当于 -->
<text>哈哈哈</text>
<view>哈哈哈</view>
<view>footer</view>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
使用事项:
- 可以在
内部进行简单运算,比如三元运算、算术运算、逻辑判断、字符串运算、数据路径运算(类似选择数组第一个元素arr[0])
- 可以在block上使用上述功能
- wx:key指定项目唯一标识符,值可以是字符串(表示item中某个属性名称),该属性名称需要具有唯一性的,比如id;也可以是
*this
(表示item本身,这种情形适合item是一个唯一的字符串或数字) - wx:for的值是一个字符串时,会解析为字符串数组
- wx:if表示有或没有,而元素上的hidden属性表示是否显示/隐藏
注意:
- 在wxml中使用变量,需要用
括起来,否则表示一个字符串
在标签上使用时,前后必须是引号,不能有空格,否则被当成字符串
WXS
定义:是小程序的脚本语言,和WXML一起构建页面结构
注意事项:
- WXS不依赖运行时的基础库版本,可在所有版本内运行
- WXS和js不一致,有自己的语法,其运行环境和js代码是隔离的,WXS中不能调用JS文件中定义的函数,也不能调用小程序提供的API
- WXS函数不能作为组件的事件回调
- 由于运行环境的差异,ios上的wxs比js快2-20倍
功能:
- WXS模块:wxs代码可以编写在wxml文件的
<wxs/>
标签内,也可以在以wxs
为后缀的文件内- 每一个wxs都是一个单独的模块,每个模块都有自己独立的作用域,暴露内部私有变量和函数只能通过module.exports实现
- wxs可以使用
require(path)
引用其他.wxs
文件模块,path是相对路径,第一次引用该模块时,该模块会自动初始化为单例对象,多个页面,多个地方,多次引用都是同一个模块对象;若定义之后未被引用,该模块不会解析和运行
- 变量:
- 变量都是值的引用
- 未声明变量直接赋值使用,会定义成全局变量
- 只声明不赋值,默认为undefined
- var会变量提升
- 变量命名首字符必须是字母和下划线,其他字符可以是数字
- 注释:单行注释、多行注释、无尾部闭合的多行注释(结尾注释,后面代码都被注释)
- 语句:支持if、switch、常规的for、while
- 数据类型:支持number、string、boolean、object、function、array、date、regexp,对应类型只支持部分方法(ES5)
- 基础类库:console、Math、JSON、Number、Date、Global(类似window)
- global:NaN、Infinity、undefined、parseInt、parseFloat、isNaN、decodeURI、decodeURIComponent、encodeURI、encodeURIComponent
wxs函数可以作为事件响应的函数:
- wxs导出的函数可以用于事件绑定,接受2个参数,分别是event(具有instance属性,也是组件描述符对象)、ownerInstance(组件描述符对象)1
- 只能响应内置组件的事件,不支持自定义组件事件
<!-- 引入wxs模块: logic.wxs -->
const tools = require('./tools.wxs')
console.log(tools)
module.exports = {
tapName (event, ownerInstance) {
// xxx
}
}
<!-- 使用 -->
<!--
src: 只能引用.wxs文件,必须是相对路径
module: 同一个文件需保持唯一性,重名会被后者覆盖,不同文件的wxs不会覆盖
命名规则:首字母必须是字母/下划线,其他内容还可以是数字
-->
<wxs src="./logic.wxs" module="logic"/>
<view bindtap="{{logic.tapName}}">点击我</view>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
事件系统
事件分类:
- 冒泡事件:组件事件触发,会传向父节点
- 非冒泡事件:不会传向父节点
事件列表:
- tap:手指触摸后马上离开,手指触摸过程:
- touchstart
- touchmove
- touchcancel,触摸被打断,如来电
- touchend
- longpress,触摸后超过350ms离开会触发,推荐
- longtap,,触摸后超过350ms离开会触发
事件绑定:
- 用法:使用
bind:tap
或bindtap
的形式,或使用catch(阻止冒泡)、mut-bind(互斥事件)等代替bind- catch绑定会阻止向该节点的父级冒泡
- mut-bind会冒泡到上级,只会触发上级的bind和catch,上级的mut-bind不会触发
- capture-bind:支持事件捕获阶段,在冒泡之前执行
- capture-catch:中断后续的捕获和冒泡阶段
事件对象常用属性:
- type
- target
- currentTarget:目标对象,有属性dataset、mark(识别具体事件的target节点,类似唯一性标识,也和dataset功能类似),其属性取值在
<view bindtap="bindTap" data-a="a" mark:mk1="ba">点击</view>
上 - detail(自定义事件的数据)
- touches、changedTouches(触摸事件)
双向数据绑定
使用:
- 使用
model:value=""
的形式进行数据绑定,这样输入框的值改变,页面上传入的该值也会发生改变 - 在自定义组件上使用数据绑定时,在使用组件的时候需要使用model,同时组件内部也需要使用model、声明properties
- 自定义组件也可自己触发(设置预想的值)双向绑定变更,使用setData设置自身属性
// cus-com.wxml
<input model:value="{{myVal}}"/>
// cus-com.js
Component({
properties: {
myVal: String
},
methods: {
update () {
this.setData({
myVal: 'leaf'
})
}
}
})
// 使用
<cus-com model:my-val="{{cusVal}}" />
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
注意:
- 只能是单一字段的绑定,只能含有一个字段变量,不能是对象属性,数组元素等
获取节点信息
场景:用于获取节点属性、样式、界面上位置或者是监听多个节点在布局上的相交状态,推断节点是否被用户看见
// 节点信息查询API
// 返回一个selectorquery(查询节点信息)对象实例,在自定义组件/包含自定义组件的页面中,应使用this替代wx
const query = wx.createSelectorQuery()
// 在component的范围内选取, query.in(Component component),参数是一个自定义组件的实例,返回selectorquery
query.in(this)
// 返回第一个匹配的节点的NodesRef对象实例,该实例用于获取节点信息
// 参数为选择器,和css选择器类似,支持id、class、子元素选择器`>`、后代选择器、跨自定义组件的后代选择器(.the-ancestor >>> .the-deescendant、多个选择器`,`
// 下面三个均返回nodesref对象实例
const idselect = query.select('#id')
query.selectAll('.class')
// 选择页面显示的区域,获取显示区域的尺寸、滚动位置等
query.selectViewport()
// nodesref对象的方法,因为他们都返回该对象,所以可以链式调用该对象方法,比如exec
// 查询节点相对于显示区域的位置信息,类似dom的getBoundingClientRect,返回selectorquery
idselect.boundingClientRect(nodeinfo => console.log(nodeinfo))
// 查询节点的context对象,支持videocontext、canvascontext、liveplayercontext、editorcontext、mapcontext
idselect.context({context} => console.log({context}))
// 获取节点的相关信息,需获取的字段必须在参数中指定,返回selectorquery
idselect.fields({
id: true,
dataset: true,
// 优先级高于size
computedStyle: true,
// 返回width、height
size: true
// ...
}, res => console.log(res))
// 获取node节点实例,支持canvas、scrollviewcontext,返回selectorquery
idselect.node(res => {})
// 获取节点的滚动位置,支持scroll-view、viewport,,返回selectorquery
idselect.scrollOffset(res => {})
// 执行nodesref的所有请求,结果按请求次序返回一个数组,请求以调用该函数为结束点,否则不会返回结果
query.exec((res) => {
console.log(res)
})
// 也可以是idselect请求完成后面添加exec方法,代表结束,这时会马上返回res结果
idselect.scrollOffset(res => console.log(res)).exec()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 用于监听两个或多个节点在布局位置上的相交状态,推断节点是否被用户看见,能看见多大比例
// 涉及的概念:
// 参照节点:取它的布局区域作为参照区域,多个参照节点则取布局区域的交集作为参照区域,页面显示区域可以作为参照
// 目标节点:监听的目标,只能是一个,当使用observeAll选项时,可监听多个
// 相交区域:目标节点区域和参照区域的相交区域
// 相交比例:相交区域占参照区域的比例
// 阈值:相交比例达到阈值,会触发监听器回调函数,阈值可以有多个
// 创建一个IntersectionObserver(相交区域监听器)对象, 返回一个 IntersectionObserver 对象实例,可用this代替wx
const sectionObv = wx.createIntersectionObserver(组件实例, {
// 选项对象
// 阈值number数组
thresholds: [0.2, 0.5],
// 是否同时观测多个目标节点,为true,observe的targetSelector将选中多个节点
observeAll: false
})
// 使用选择器选取参照节点作为参照区域,返回IntersectionObserver,由于是IntersectionObserver,所以可以调用其下的方法,构成链式调用
sectionObv.relativeTo('#id', {
// 扩展(收缩)参照节点布局区域的边界,left、right、top、bottom
// 当目标节点进入设置的参照区域时边界范围内时,会触发监听器回调函数
left: number
})
// 指定页面显示区域作为参照区域,将上面方法名换成relativeToViewport,其他都一样
sectionObv.relativeToViewport('#id', {})
// 指定目标节点,并开始监听相交状态变化
sectionObv.observe('#id2', res => {
// 监听变化的回调函数
})
// 停止监听,调用后监听的回调函数将不会再触发
sectionObv.disconnect()
// 链式调用
wx.createIntersectionObserver(this, {
thresholds: [0.2, 0.5]
}).relativeTo('#id').relativeToViewport().observe('#id2', res => {
console.log(res)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
响应显示区域的变化
显示区域:指小程序界面上可以自由布局展示的区域,默认情况下其尺寸在页面初始化后就不在变更
引起显示区域尺寸变更的方式:
- 启用屏幕旋转支持(在手机上),即在
app.json
的window上设置pageOrientation为auto,或者页面的json配置,配置后在屏幕旋转时,该页面将随着旋转 - 启用屏幕旋转支持(在ipad上):在app.json中设置resizable为true,页面不能配置
media query(媒体查询):解决不同尺寸显示区域的布局差异问题,和css的媒体查询类似,同时:
- 在wxml上,可以使用match-media组件根据media query匹配状态展示、隐藏节点
- 在页面或自定义组件的js中使用
this.createMediaQueryObserver()
创建一个媒体查询监听器对象,用于监听指定的media query的匹配状态
const mq = this.createMediaQueryObserver()
mq.observe({
// media query描述符,当满足指定的属性时,会进行回调监听
maxWidth: number,
orientation: 'landscape'
}, res => {console.log(res.matches)})
mq.disconnect()
2
3
4
5
6
7
8
屏幕旋转事件:用于对media query无法控制的精细布局变化的补充,使用生命周期函数onResize
, pageLifetimes.resize
、wx.onWindowResize
(不推荐)监听,返回显示区域的尺寸信息
动画
https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html
初始渲染缓存
页面初始化分为:
- 逻辑层初始化:载入必须的代码,初始化页面this对象及其涉及到的组件的this对象,将数据发送给视图层
- 视图层初始化:载入必须的代码,等待逻辑层初始化完毕后接受其发送的数据,然后渲染页面
场景:
- 启动第一个页面,尤其是冷启动时,逻辑层初始化时间过长,在整个初始化过程中,用户将看到带入画面或轻微的白屏
启动初始化渲染缓存:使视图层不需要等待逻辑层初始化完毕,而是直接提前将页面初始的data数据展示给用户,缩短面呈现给用户的时间,工作原理:
- 第一次打开时,记录下页面初始数据的渲染结果,写入持久化的缓存
- 第二次打开时,检查缓存是否存有这个页面上一次初始数据的渲染结果,有则直接渲染
- 若展示了渲染结果,页面暂时不能响应用户事件,需等待逻辑层初始化完毕后才能响应
初始渲染缓存的优势:
- 快速展示页面永远不会变的部分,如导航栏
- 预先展示一个骨架页,提升用户体验
- 展示自定义的加载提示
- 提前展示广告等
初始渲染缓存中,复杂组件不能被展示或不能响应其交互,暂只支持部分内置组件,有:view、text、button、image、scroll-view、rich-text
静态初始渲染缓存:在app或页面的json添加配置项"initialRenderingCache": "static"
,这种情况下记录的只是data里面的初始数据,不包括setData的数据
在初始渲染缓存中添加动态内容:将配置属性值static改为dynamic,同时在ready或其后面的生命周期中调用this.setInitialRenderingCache(dynamicData)
才能启用,其中参数dynamicData是一组数据,与data一起参与页面的渲染,即使用形式和data内的变量一致,禁用初始渲染缓存,将参数改为null即可
小程序开发指南
https://developers.weixin.qq.com/ebook?action=get_post_info&docid=000224e19b4cd83b0086dba765940a
页面构成
注意:
- JSON不能有注释,且它的值只能是number、string、Boolean、object、array、Null,其他值会报错
- 项目中,js、app.json、wxml、wxss文件会被编译,上传后无法直接访问
- 除上面5中类型,只有下列文件能够被上传,有:wxs、png、jpg、jpeg、gif、svg、json、cer、map3、aac、m4a、mp4、wav、ogg、silk、wasm、br、cert
app.json
定义:小程序的全局配置,包括小程序页面路径、界面表现、网络超时时间、底部tab等
注意:页面内的json配置内容是app.json里面window字段内的内容
{
// 小程序默认的启动首页,未设置时,则指pages的第一项,该页面不支持带路径参数
"entryPagePaath": "pages/index/index",
// 页面路径列表,小程序增减页面时,需要动态对其进行修改
"pages": [
"pages/index/index",
"pages/logs/logs"
],
// 全局自定义组件配置
"usingComponents": {
"comp-name": "comp-path"
},
// 自定义组件代码按需注入,只支持requiredComponent
"lazyCodeLoading": "requiredComponent",
// 全局的默认窗口表现,设置小程序的状态栏、导航条、标题、窗口背景色
"window": {
// hexColor,导航栏背景色
"navigationBarBackgroundColor": "#000000",
// 导航栏标题文本色,只有black和white
"navigationBarTextStyle": "white",
// 导航栏标题文字内容
"navigationBarTitleText": "测试数据",
// 导航栏样式,只有default、custom
"navigationStyle": "default",
// 在非首页、非最底层页面、非tabbar页面的导航栏展示home键
"homeButton": false,
// 窗口背景色
"backgroundColor": "#ffffff",
// 下拉loading的样式,只有dark、light
"backgroundTextStyle": "dark",
// 是否支持开启全局下拉刷新
"enablePullDownRefresh": false,
// 页面上触发触底事件时距页面底部的距离
"onReachBottomDistance": 50,
// 屏幕旋转设置,只有auto、portrait、landscape
"pageOrientation": "portrait",
// 重新启动策略配置,只有:
// homePage下次从首页冷启动、
// homePageAndLatestPage下次冷启动后立刻加载上次退出的页面,页面参数保持不变(不可用在tab页)
"restartStrategy": "homePage",
// 页面初始渲染缓存配置,支持static、dynamic
"initialRenderingCache": "",
// 切入系统后台时,隐藏页面内容,保护用户隐私,支持hidden、none
"visualEffectInBackground": "",
// 控制预加载下个页面的时机,支持static、manual、auto
"handleWebviewPreload": "static"
},
// touch事件监听是否是passive
// 开启该事件监听后,会导致某些内置组件出现非预期行为,推荐使用到这些内置组件的页面中将对应事件的enablePassiveEvent为false避免该情况
// 在页面js中支持使用getPassiveEvent/setPassiveEvent获取/切换页面或组件所在页面的passive设置
"enablePassiveEvent": {
// 设置具体的事件是否支持passive
"touchstart": false,
"touchmove": false,
"wheel": false
} or boolean,
// 底部tab栏的表现
"tabBar": {
"color": hexColor,
"selectedColor": hexColor,
"backgroundColor": hexColor,
"borderStyle": "black/white",
"position": "bottom/top",
// tab列表,[2, 5]
"list": [
"pagePath": "",
"text": "",
// icon <= 40kb,建议尺寸81*81px,只支持本地图片
"iconPath": "",
"selectedIconPath": ""
],
// 是否自定义tabBar
"custom": boolean
},
// 网络超时时间,ms
"networkTimeout": {
"request": 60000,
"connectSocket": 60000,
"uploadFile": 60000,
"downloadFile": 60000
},
// 是否开启debug模式
"debug": true,
// 调试相关配置,是否开启FPS面板
"debugOptions": {
"enableFPSPanel": "custom"
},
// 是否启用插件功能页
"functionalPages": false,
// 分包结构配置
"subpackages": [
{
// 分包根目录
"root": "packageA",
"name": "pckA",
// 相对于root的路径
"pages": [
"pages/a",
"pages/b"
],
"independent": false
}
],
// 分包预下载规则
"preloadRule": {
"pages/index/index": {
"network": "all/wifi",
// 进入pages/index/index需要预先下载的包
"packages": ["subpackage-name", "subpackage-root", "__APP__"]
}
},
// worker代码放置的目录
"workers": "workers-dict",
// 需要在后台使用的能力,比如音乐播放和定位
"requiredBackgroundModes": [
"audio",
"location"
],
// 调用的地理位置相关的隐私接口,使用这些接口需要在这里声明,且需要在管理后台开通接口权限
"requiredPrivateInfos": [
"getFuzzyLocation", // 获取模糊位置
"getLocation", // 获取精确位置
"onLocationChange", // 监听地理位置变化事件
"startLocationUpdate", // 接收位置消息
"startLocationUpdateBackground", // 接受位置消息(前后台)
"chooseLocation", // 打开地图选择位置
"choosePoi", // 打开poi选择位置
"chooseAddress" // 获取用户地址信息
],
// 小程序接口权限相关设置:https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/authorize.html
"permission": {
"scope.userLocation": {
"desc": "小程序获取权限时展示的接口用途说明"
}
},
// 使用到的插件
"plugins": {},
// 指定需要引用的扩展库,只支持kbone、weui
"useExtendedLib": {
"kbone": true,
"weui": true
},
// 指明sitemap.json的位置
"sitemapLocation": "",
// 指定使用升级后的weui样式
"style": "",
// pc端是否支持用户改变窗口大小,ipad是否支持屏幕旋转
"resizable": false,
// 微信消息用小程序打开
"entranceDeclare": {
"locationMessage": {
"path": "pages/index/index",
"query": "foo=bar"
}
},
// 是否支持黑暗模式
"darkmode": false,
// 指明theme.json位置,支持黑暗模式时必填
"themeLocation": "",
// 单页模式配置
"singlePage": {},
// 聊天素材小程序打开相关配置
"supportedMaterials": {},
// 半屏小程序appId
"embeddedAppIdList": [
""
],
// 自定义模块映射规则,在使用该能力时,需要在工具上关闭上传代码保护功能,命中多条规则,则取最长的名字规则
// 路径匹配,key和value必须以 /* 结尾
"resolveAlias": {
"~/*": "/*",
"~/origin/*": "origin/*",
"@utils/*": "utils/*",
"subBUtils/*": "subpackageB/utils/*"
},
// 全局默认的渲染后端
"renderer": "webview/skyline"
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
wxml
定义:充当类似html的角色,由标签、属性等构成
wxml和html不一样的地方:
- 标签:由小程序特有标签构成
- 多了一些特有属性和规则,类似vue,比如wx:if、模板语法
wxss
与css不同的地方:
- 只支持部分css特性
- 新增了尺寸单位:rpx,规定屏幕宽度为750rpx(任何屏幕)
- 样式导入:使用
@import "common.wxss";
的形式,路径为相对路径 - 支持使用style、class控制组件样式,比如
<view style="color:" />
、<view class="normal_view"/>
- 支持的选择器有:
#id
,.class
,view
,view text
,::after
,::before
- 提供了全局样式app.wxss和局部样式page.wxss
js
sitemap.json
定义:
- 可以通过sitemap.json配置或者管理后台页面收录开关配置小程序页面是否允许微信索引
- 当允许索引时,微信会通过爬虫的形式,为小程序页面内容建立索引。
- 若爬虫发现的页面数据和真实用户的程序不一致,则该页面不会进入索引
- 未设置该json,默认所有页面都能被索引
{"action": "allow", "page": "*"}
是优先级最低的默认规则
配置:在小程序根目录建立一个sitemap.json的文件
{
// 索引规则列表
"rules": [
{
// 命中该规则的页面是否能被索引
"action": "allow/disallow",
// 页面路径,* 表示所有页面,且不能用作通配符
"page": "pages/index/index",
// 指定的页面在本规则匹配时可以使用的页面参数列表
"params": ['paramA', 'paramB'],
// 页面参数的匹配方式,
// exact:参数列表等于params时命中规则
// inclusive:包含params时命中规则
// exclusive:参数列表与params交集为空时,命中规则
// partial:交集不为空时,命中规则
"matching": "inclusive",
// 匹配优先级,值越大匹配优先级越高
"priority": 0
}
]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
project.config.json
定义:微信开发者工具的配置文件,比如界面颜色、编译配置等,https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html
模块化
注意:
- 小程序目前不支持直接引入node_modules,建议拷贝出相关代码到小程序目录中,或者使用小程序支持的npm
- 在js文件中声明的变量和函数仅在该文件有效 ,不同文件可声明相同名称,不会互相影响
- 全局的数据可以在
App()
中设置
require(module, callback, err)
定义:引入模块,返回模块通过module.exports或exports暴露的接口
- 其中module表示当前模块对象,其有一个属性exports,表示模块向外暴露的对象,使用require引用该模块时可以获取到
- exports表示module.exports的一个引用
// 在同一个包内调用
// 第一个参数path:表示引入模块文件相对于当前文件的相对路径、npm模块名、npm模块路径,默认不支持绝对路径,但可通过配置resolveAlias自定义路径映射
// commom代表common.js导出的module.exports对象
const common = require('common.js')
Page({
helloJade () {
// 相当于调用module.exports.sayHello方法
common.sayHello('Jade')
}
})
// 跨分包异步调用(不在同一个包内)
let common
// 第一个参数为path
// 第二个参数是异步加载成功的回调函数callback,该callback的参数是成功加载的模块对象
// 第三个参数是异步加载失败的回调函数error,该error的参数是包含错误信息和模块名的对象
require('../packageA/common.js', mod => {
common = mod
}, ({ errMsg, mod }) => {
console.log(`path: ${mod}, ${errMsg}`)
})
Page({
helloJade () {
common && common.sayHello('Jade')
}
})
// 以promise形式链式调用
require.async('../packageA/common.js').then(mod => {
console.log(mod)
}).catch(({ errMsg, mod }) => {
console.log(`path: ${mod}, ${errMsg}`)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
requirePlugin(module):
- 语法和require类似,表示引入一个插件,返回插件通过main暴露的接口
- 第一个参数module,字符串类型,表示需要引入的插件的alias,也可以是插件的AppID
requireMiniProgram():
- 插件引入 当前使用该插件的小程序,返回小程序通过插件配置中export暴露的接口,无参
分包
场景:
- 某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载
分包小程序的构成:
- 主包:放置默认启动页面/tabbar页面、分包用到的公共资源(图片、js)等,只有一个,即pages页面的内容
- 分包:根据开发者配置(功能模块)划分的,可以有多个,所有分包总计不能超过20MB,单个分包/主包不能超过2MB。分包有助于优化首次启动的下载时间,以及协同开发时的解耦协作
- 声明subpackages后,将按照该字段配置的路径进行打包,路径外的目录将打进主包中
- subpackage的根目录不能是其他subpackage的子目录
其他知识点:
- 小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内页面时,客户端会把对应的分包下载,下载完成后再展示内容
使用分包:通过在app.json中配置subpackages
或subPackages
字段生命项目分包结构`
分包预下载:
- 在进入小程序某个页面时,由框架自动预下载可能需要的分包,提升进入后续分包页面时的启动速度,对于独立分包来说,可以预下载主包
- 预下载只支持通过配置
preloadRule
字段的方式使用,不支持调用API - vConsole中有preloadSubpackages开头的日志信息验证预下载的情况
- 同一个分包的页面总计的预下载限额为2MB,限额会在工具中打包时校验,比如一个分包中预下载页面有A、B,A预下载大小为0.5MB,则B的预下载大小只有1.5MB
// 小程序结构
├── app.js
├── app.json
├── app.wxss
├── packageA
│ └── pages
│ ├── cat
│ └── dog
├── packageB
│ └── pages
│ ├── apple
│ └── banana
├── pages
│ ├── index
│ └── logs
└── utils
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 分包配置:app.json
{
"pages": [
"pages/index"
]
"subPackages": [
{
// 分包根目录
"root": "packageA",
// 分包别名,可用于预下载时
"name": "packA",
// 分包页面路径,相对于分包根目录
"pages": [
"index",
// 完整路径是:packageA/pages/xxx
"pages/cat",
"pages/dog"
],
// 是否是独立分包:为true时表示是独立分包
"independent": false
},
{
"root": "packageB",
"pages": ["index"]
},
{
"root": "packageC",
"name": "packC",
"pages": ["index"],
"independent": true
}
],
"preloadRule": {
// 其中对象的key表示页面路径,value表示页面的预下载配置
"pages/index": {
// network表示仅在指定网络预下载,有all、wifi
"network": "all",
// 进入预下载分包的root或name,主包为__APP__
"packages": ["packageA"]
},
"packageA/index": {
"packages": ["packA", "packageB"]
},
"packageC/index": {
"packages": ["__APP__"]
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
分包的引用规则:
- packageA的分包无法require packageB的js文件、无法import packageB的template、无法使用packageB的资源,但是可以require packageA和主包的js文件、可以import packageA和主包的template、可以使用packageA和主包的资源
版本兼容性:会由微信后台编译来处理旧版本客户端的兼容,后台会编译2份代码包,一份是分包后代码,一份是整包的兼容代码,新客户端会用分包代码,旧客户端用整包代码
独立分包:
- 是小程序中一种特殊类型的分包,普通分包的限制条件对独立分包依然有效,可独立于主包和其他分包单独运行
- 从独立分包页面进入小程序不需要下载主包,只有用户进入普通分包或主包页面时,主包才会被下载
- 开发者可将某些具有一定功能独立性的页面配置到独立分包中,这将可以提升分包页面的启动速度
- 一个小程序中可以有多个独立分包
- 独立分包中不能依赖于主包和其他分包的内容,包括js、template、wxss、custom component、plugin(使用分包异步化时,js、custom component、plugin不受这限制)
- 主包的app.wxss对独立分包无效
App
只能在主包中定义,独立分包中不能定义App,会造成无法预期的行为- 独立分包运行时,
App
不一定被注册,所以getApp()
不一定可以获取App对象 - 当从独立分包页面进入小程序时,主包不存在,App也不存在,此时调用getApp获取到的是undefined。只有从其他页面进入时,主包才会下载,App才会被注册,由于这个限制,开发者无法通过App对象实现独立分包与其他页面的全局变量共享
- 当从其他页面跳转到独立分包页面时,由于主包已经存在,此时可以获取到App对象
- 为了实现独立分包的全局变量共享,支持
allowDefault
参数,在App未定义时返回一个默认实现值,在主包加载后,该值会被覆盖合并到真正的App对象中
- 独立分包运行时,
- 独立分包中暂不支持使用插件
- 在低于6.7.2版本的微信中,独立分包视为普通分包处理,在兼容模式下,主包的app.wxss可能会对独立分包页面产生影响,避免独立分包使用主包样式
// 独立分包
const app = getApp({allowDefault: true}) // {}
app.data = { a: 456 }
app.global = {}
// app.js
// { data: {a: 456, b: 123 }, global: {}, other: 'hello' }
App({
data: { b: 123},
other: 'hello'
})
2
3
4
5
6
7
8
9
10
11
使用独立分包时,App的生命周期:
- 从独立分包启动小程序时,主包的App的onLaunch和首次onShow会从独立分包页面首次进入主包/其他页面时调用
- 由于独立分包中无法定义App,小程序的生命周期的监听可以使用
wx.onAppShow
,wx.onAppHide
完成,其他事件的监听可以用wx.onError
,wx.onPageNotFound
监听
分包异步化:
- 小程序中,不同的分包对应不同的下载单元,除了非独立分包可以依赖主包外,其他分包不能互相使用自定义组件/require,分包异步化就是打破这一限制,允许通过特定的配置和接口,使部分跨分包内容可以等待下载后异步使用
- 跨分包自定义组件的引用:一个分包中使用了其他分包的自定义组件时,先用占位组件替代,分包下载后再替换回来,可以使用
wx.onLazyLoadError
监听加载事件 - 跨分包js引用:一个分包引用了其他分包的代码时,为了不让下载阻塞代码运行,需要异步获取引用的结果
// 跨分包引用自定义组件,某分包的json文件
{
"usingComponents": {
"button": "../packageA/components/button",
"list": "../packageB/components/full-list",
"simple-list": "./components/simple-list"
},
"componentPlaceholder": {
"button": "view",
"list": "simple-list"
}
}
// 跨分包js代码引用,插件调用则将require换成requirePlugin即可
// 回调函数风格
require('../packageA/utils.js', utils => {
console.log(utils.whoami)
}, ({mod, errMsg}) => {
console.error(`path: ${mod}, ${errMsg}`)
})
// promise风格
require.async('../packageA/utils.js').then(pkg => {
pkg.getPackageName()
}).catch(({mod, errMsg}) => {
console.error(`path: ${mod}, ${errMsg}`)
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
代码注入
场景:在小程序启动的过程中,主要的耗时环节有代码包的下载、代码注入。其中注入代码量的大小和内容占用与注入的耗时正相关。利用按需注入和用时注入的特性,可以优化代码注入环境的耗时和内存占用。
按需注入:
场景:通常情况下,小程序启动时,启动页依赖的所有代码包(主包、分包、插件包、扩展库等)的所有js代码会全部合并注入,包括其他未访问到的页面和未用到的组件,同时所有页面和组件的js代码会立刻执行,这会造成很多没使用的代码在小程序运行环境中注入执行,影响耗时和内存占用
开启按需注入:通过在app.json中配置一级字段"lazyCodeLoading": "requiredComponents"
,有选择的注入必要的代码,降低启动耗时和运行内存
注意:
- 启用按需注入后,仅注入当前页使用的内容,其他未使用的内容不会被加载和初始化,这时需要确认小程序的表现是否正常
- 启用按需注入后,页面json配置的所有组件和app.json中usingComponents配置的全局组件,都会是为页面的依赖并进行注入加载,这可能会影响按需注入的效果。建议移除未使用的组件生命,避免在全局声明使用率低的组件。
- 插件包和扩展库不支持按需注入,若需实现插件按需加载,将该插件放在一个分包中,通过分包异步化形式异步引入
用时注入:
定义:在开启按需注入的前提下,用时注入可以指定一部分组件不在小程序启动时注入,而在真正渲染时进行注入
使用:指定按需注入后,给某组件配置占位组件,某组件就会自动被视为用时注入组件,即:
- 每个页面内,第一次渲染该组件前,该组件不会被注入
- 每个页面内,第一次渲染该组件时,该组件会被渲染成其占位组件,占位组件汇总渲染流程结束后开始注入
- 注入结束后,占位组件被替换回对应的组件
存储
定义:每个小程序都有自己的本地缓存,可以通过storage进行增删改查
隔离策略:
- 同一个微信用户,同一个小程序的storage上限为10MB
- storage以用户为维度,不同用户数据、不同小程序数据无法相互调取
- 同一小程序使用不同插件,这些不同的插件之间、插件和小程序之间的storage无法相互调取
- 不同小程序使用同一插件,这个插件的storage不互通
清理策略:本地缓存的清理时机和代码包一样,只有在代码包被清理时storage才会被清理
用法:
wx.setStorage(obj)
:将数据存在指定的key中,会覆盖原有key的内容,若非用户主动删除或因存储原因被系统删除,该数据一直可用。单个key上限1MB(开启加密后上限0.7MB),所有key上限为10MB(开启加密后上限为7.1MB),支持promise形式的回调,obj的参数有:- key:键名
- data:键值,支持原生类型、Date、能被JSON.stringify序列化的对象
- encrypt:是否开启加密存储,只有异步的接口支持开启,开启后,接口回调耗时增加,同时setStorage和getStorage同时需要设置该属性,且加密后数据比原始数据膨大1.4倍
- success/fail/complete:接口调用成功、失败、结束时的回调
wx.setStorageSync(key, data)
:同步存储,只应用于数据的持久化存储,太多会影响启动耗时wx.getStorage(obj)
:obj参数比setStorage少了一个data属性,支持promise形式的回调,其中:- success:接口返回res,使用res.data获取该key的值
wx.getStorageSync(key)
;同步获取缓存wx.removeStorage(obj)
:移除指定key的缓存,参数比setStorage少了一个data、encrypt属性,支持promise形式的回调wx.removeStorageSync(key)
:同步移除缓存,支持promise形式的回调wx.clearStorage(obj)
:清理本地所有缓存,参数obj只有success、fail、complete三个函数属性,支持promise形式的回调wx.clearStorageSync()
:同步清理所有缓存,无参,支持promise形式的回调
// 设置缓存
wx.setStorage({
key: 'user-info',
value: data,
// 同时开启
encrypt: true,
success () {
wx.getStorage({
key: 'user-info',
// 同时开启
encrypt: true,
success (res) {
console.log(res.data)
}
})
}
})
// 同步存储
try {
wx.setStorageSync('user-info', data)
} catch (e) {
console.log(e)
}
// 获取缓存
wx.getStorage({
key: 'user-info',
encrypt: true,
success (res) {
console.log(res.data)
}
})
wx.getStorageSync('user-info')
// 清除key为user-info的缓存
wx.removeStorage({
key: 'user-info',
success () {}
})
wx.removeStorageSync('user-info')
// 清空缓存
wx.clearStorage({
success () {}
})
wx.clearStorageSync()
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
文件系统
定义:
- 文件系统是小程序提供的一套以小程序和用户维度隔离的存储以及一套相应的管理接口
文件分类:
- 代码包文件:指的是项目源代码目录中的文件,由于代码包大小的限制,适用于放置首次加载时需要的文件,访问代码包文件只能以绝对路径方式引入,从项目根目录开始,比如(
/images/xxx.png
或images/xxx.png
) - 本地文件:指的是小程序被用户添加到手机后,会有一块独立的文件存储区域,以用户和小程序的维度隔离。通过调用接口本地产生,或是网络下载保存到本地的文件,文件路径均为
wxfile/http://path
的格式,其中http是开发者工具上的,wxfile是客户端上的。本地文件分为:- 本地临时文件:临时产生,随时会被回收的文件,不能直接写入内容,仅在当前生命周期内保证是有效的,若想保证以后优先,可使用
saveFile
、copyFile
等接口将其转成下面两者文件形式。运行时最多存储4GB,运行结束后超过2GB的部分会按文件维度从时间远近被清理。所有小程序占用不能超过6GB,超过则按小程序维度进行清理。在下载临时文件时,可先使用access
检查该文件是否存在,减少重复下载。 - 本地缓存文件:通过接口方式
saveFile()
将本地临时文件缓存后的文件,不能自定义存放的目录和名称,和本地用户文件一起最多可存放200MB,仅在代码包清理时才被清理 - 本地用户文件:比本地缓存文件功能更完整,通过接口方式(特定API)将本地临时文件缓存后的文件,能自定义存放的子目录和名称,有完全的读写权限,可通过
wx.env.USER_DATA_PATH
获取存放目录根路径,和本地缓存文件一起最多可存放200MB,仅在代码包清理时才被清理
- 本地临时文件:临时产生,随时会被回收的文件,不能直接写入内容,仅在当前生命周期内保证是有效的,若想保证以后优先,可使用
使用:通过fs = wx.getFileSystemManager()
获取全局唯一的文件系统管理器,所有的文件操作都可以通过对象fs进行调用
api使用:
const fs = wx.getFileSystemManager()
// 注意,下列的接口,若后缀带Sync,则为同步接口,否则是异步的
// 保存文件到系统磁盘(PC)
wx.saveFileToDisk({
filePath: '',
success/fail/complete() {}
})
// 新开页面打开文档
wx.openDocument({
filePath: '',
// 是否显示右上角菜单
showMenu: boolean,
// doc,docx,xls,xlsx,ppt,pptx,pdf
fileType: '',
success/fail/complete() {}
})
// 判断文件/目录是否存在
fs.access({
// 是否存在的本地文件/目录路径
path: '',
success/fail/complete(res) {}
})
// 文件存在时,会返回undefined,否则会报错,应该使用try-catch或其他错误处理函数包裹
try {
fs.accessSync(path)
} catch (e) {
console.log(e)
}
// 创建目录
fs.mkdir({
dirPath: '',
// 是否递归该目录的上级目录后在创建目录,上级目录不存在时,会创建上级目录
recursive: true,
success/fail/complete() {}
})
try {
fs.mkdir(dirPath, recursive)
} catch (e) {
console.log(e)
}
// 删除目录
fs.rmdir({
dirPath: '',
recursive: true,
success/fail/complete() {}
})
const res = fs.rmdirSync(dirpath, recursive)
// 打开、关闭文件
fs.open({
filePath: '',
// 文件系统标志,有r(默认,打开文件读取,不存在,则会发生异常)、r+(打开文件读取和写入,不存在,则会发生异常)、w(打开文件写入,不存在则创建)、w+(打开文件读取和写入,不存在则创建)、a(打开文件追加,不存在则创建)、a+(打开文件用于读取和追加,不存在则创建)、as/as+(效果同a/a+,同步模式,不存在则创建)
flag: 'a+',
// 参数res的属性有fd:文件描述符
success (res) {
fs.close({
// 需要被关闭的文件描述符,通过open/openSync函数获取
fd: res.fd,
success/fail/complete() {}
})
},
fail/complete() {}
})
const fd = fs.openSync({
filePath: '',
flag: 'a+'
})
fs.closeSync({
fd: fd
})
// 读取文件
fs.read({
fd: '',
// 数据写入的缓冲区
arrayBuffer: arrayBuffer,
// 缓冲区写入的偏移量
offset: number,
// 从文件中读取的字节数
length: number,
// 从文件中读取的起始位置
position: number,
success (res) {
const { bytesRead, arrayBuffer } = res
},
fail/complete() {}
})
res = fs.readSync({
fd: '',
arrayBuffer: arrayBuffer,
offset_length_position
})
// 读取本地文件内容
fs.readFile({
filePath: '',
encoding: '',
position_length,
success (res) {
console.log(res.data)
},
fail/complete() {}
})
try {
const res = fs.readFileSync(filePath, encoding, position, length)
} catch (e) {
console.log(e)
}
// 读取目录内文件列表
fs.readdir({
dirPath: '',
success (res) {
console.log(res.files)
},
fail/complete() {}
})
try {
const res = fs.readdirSync(dirpath)
} catch (e) {
console.log(e)
}
// 写入文件
fs.write({
fd: '',
data: string_arrayBuffer,
offset_length_position,
encoding: '',
success (res) {
const { bytesWritten } = res
},
fail/complete () {}
})
const res = fs.writeSync({
fd: '',
data,
offset_length_position,
encoding
})
// 写入本地文件
fs.writeFile({
filePath: '',
data: string_arrayBuffer,
encoding: '',
success/fail/complete () {}
})
const res = fs.writeFileSync(filePath, data, encoding)
// 复制文件
fs.copyFile({
srcPath: '',
// 目标文件路径
destPath: '',
success/fail/complete() {}
})
try {
fs.copyFileSync(srcPath, destPath)
} catch (e) {
console.log(e)
}
// 删除保存的本地缓存文件
fs.removeSavedFile({
filePath: '',
success/fail/complete() {}
})
// 重命名文件,文件移动
fs.rename({
oldPath: '',
newPath: '',
success/fail/complete() {}
})
try {
const res = fs.renameSync(oldPath, newPath)
} catch (e) {}
// 在文件结尾追加内容
fs.appendFile({
filePath: '',
// 要追加的文本、二进制数据
data: string/arrayBuffer,
// 指定写入文件的字符编码,值有uft8(默认)、ascii、base64、ucs2、uth16le
encoding: string,
success/fail/complete() {}
})
try {
fs.appendFileSync(path, data, encoding)
} catch (e) {
console.log(e)
}
// 保存文件
fs.saveFile({
tempFilePath: '',
filePath: '',
success (res) {
const { savedFilePath } = res
},
fail/complete () {}
})
const savedFilePath = FS.saveFileSync(tempFilePath, filePath)
// 删除文件
fs.unlink({
filePath: '',
success/fail/complete() {}
})
const res = fs.unlinkSync(filePath)
// 解压文件
fs.unzip({
zipFilePath: '',
targetPath: '',
success/fail/complete () {}
})
// 获取已保存的本地缓存文件列表
fs.getSavedFileList({
success (res) {
const { fileList } = res
const { filePath, size, createTime } = for(fileList)
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
// 获取文件状态信息
fs.fstat({
// 文件描述符,同上
fd: '',
success/fail/complete() {}
})
// stats表示文件状态的对象
const stats = fs.fstatSync({fd: fd})
const {
// 文件类型和存取的权限
mode: '',
size: number,
// 最近一次操作的unix时间
lastAccessedTime: number,
lastModifiedTime: number
// 判断当前文件是否是目录
isDirectory(),
// 当前文件是否是普通文件
isFile()
} = stats
// 获取小程序下的本地临时文件、本地缓存文件的信息
fs.getFileInfo({
filePath: '',
success (res) {
console.log(res.size)
},
fail/complete() {}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
多线程Worker
场景:一些异步处理的任务,可以放置在Worker中运行,待运行结束后,再把结果返回给小程序主线程。
使用事项:
- Worker运行在一个单独的全局上下文和线程中,不能直接调用主线程的方法
- Worker与主线程之间传输数据,双方使用
Worker.postMessage()
来发送数据,使用Worker.onMessage()
来接收数据,传输的数据不是直接共享的(内存地址不一样),而是复制后的值传递
使用流程:
// 1. 在app.json中配置Worker放置的 目录workers ,目录下的代码将被打包成一个文件
{
"workers": "workers"
}
// 2. 添加Worker代码文件,添加后目录结构如:
/**
├── app.js
├── app.json
├── project.config.json
└── workers
├── request
│ ├── index.js
│ └── utils.js
└── response
└── index.js
*/
// 3. 编写Worker代码
// 在workers/request/index.js中编写Worker响应代码
const utils = require('./utils')
// 在Worker线程执行上下文中会暴露一个worker对象,直接调用数据发送接收方法即可
worker.onMessage((res) => {
console.log(res)
})
// 4. 在主线程(即在app.js)中初始化Worker,path是worker入口的绝对路径
const worker = wx.createWorker('workers/request/index.js')
// 5. 主线程向Worker发送信息
worker.postMessage({
msg: 'hello worker'
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
注意事项:
- Worker最大并发是1个,创建下一个前需用
Worker.terminate()
结束当前的Worker - Worker内代码只能require指定的Worker路径内的文件,无法引用其他路径
- Worker的入口文件由
wx.createWorker()
指定,可动态指定入口文件地址 - Worker内不支持
wx.xx
的api - Worker之间不支持发送消息
- Worker目录中只能放置js文件,其他文件需在其目录外
- 插件使用worker前需要在plugin.json中放在workers的代码相对路径path
自定义tabBar
场景:自定义tabBar可以更加灵活的设置tabBar的样式结构,以满足更多个性化的场景
在自定义tabBar模式下的使用事项:
- 为了低版本兼容和区分哪些页面是tab页,tabBar相关配置项(即tabbar字段)需要完整的声明
- 使用tabBar需要提供一个自定义组件(即与pages同级的目录custom-tab-bar)渲染tabBar,所有tabBar的样式都由该组件渲染,推荐使用fixed在底部的cover-view+cover-image渲染样式,保证其层级相对较高
- 原来的与tabBar样式相关的接口,都将失效,比如
wx.setTabBarItem
- 每个tabBar页下的自定义tabBar组件实例是不同的,可通过自定义组件下的getTabBar获取当前页面自定义tabBar组件实例
- 若想实现tab选中态,要在当前tab页面下,通过getTabBar接口获取tabbar实例,调用setData更新选中态。
使用步骤:
// 1. 在app.json中的tabBar字段指定custom字段,同时其他的配置需要补充完整。且所有tab的页面的json文件都需要声明usingComponents项(即使是空对象),也可以在app.json全局开启
{
"tabBar": {
"custom": true,
"color": "#000000",
"selectedColor": "#000000",
"backgroundColor": "#000000",
"list": [
{
// tabBar页面路径,该页面的json需要配置usingComponents字段
"pagePath": "page/component/index",
// 该页面下tabBar的展示名称
"text": "组件"
},
{
// xxx
}
]
},
// 也可以在全局配置该字段
"usingComponents": {}
}
// 2. 添加tabBar代码文件(和普通组件类似),必须是在与pages同级的custom-tab-bar目录下,组件名为index,比如 custom-tab-bar/index.json
// 3. 编写tabBar代码,和组件类似,当切换tab时,在custom-tab-bar/index.js中
Component({
methods: {
switchTab(e) {
const data = e.currentTarget.dataset
const url = data.path
// 跳转到该tabBar所在页面
wx.switchTab({
url: url || 'page/component/index'
})
// 第一次设置该tabBar选中状态,tabBar的唯一标识符,比如id等
this.setData({
selected: data.selected
})
}
}
})
// 4. 在tabBar所在页面(比如:page/component/index页面)再次设置选中状态,不然选不中有小bug
// page/component/index.js
Component({
pageLifeTimes: {
show() {
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({
selected: 1
})
}
}
}
})
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
数据预拉取
场景:预拉取能够在小程序冷启动时通过微信后台提前向第三方服务器拉取业务数据,当代码包加载完成时可以更快渲染页面,减少用户等待时间,提升小程序打开速度
黑暗模式DarkMode
https://developers.weixin.qq.com/miniprogram/dev/framework/ability/darkmode.html
屏幕适配指南
https://developers.weixin.qq.com/miniprogram/dev/framework/ability/adapt.html