微信小程序开发的一些记录……
相关参考
框架文档 | 说明 | 备注 |
---|---|---|
微信官方文档 | 微信开放文档 | |
TDesign | TDesign | ![[assets/Pasted image 20230608144221.png|102]] |
Vant | 介绍 - Vant Weapp | ![[assets/Pasted image 20230608144251.png|134]] |
Taro | Taro 文档、 Taro 文档 | ![[assets/Pasted image 20230608144347.png|95]] |
Uniapp | uni-app官网 | ![[assets/Pasted image 20230608144441.png|148]] |
后两种都是可以生成多平台的框架,并且让你可以使用 Vue 或 React 来编写。
简介
这里只是一些并不系统的摘录,你总可以在 微信开放文档 里获取更多详细的信息。
小程序的技术发展史
JS API (如 WeixinJSBridage) > JS-SDK > 微信 Web 资源离线存储 (提升移动网页体验) > 小程序
:: todo 扩展了解 WebView 相关的知识
小程序与普通网页开发的区别
网页开发渲染线程和脚本线程是互斥的,这也是为什么长时间的脚本运行可能会导致页面失去响应,而在小程序中,二者是分开的,分别运行在不同的线程中。
网页开发者可以使用到各种浏览器暴露出来的 DOM API,进行 DOM 选中和操作。小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的 DOM API 和 BOM API。这一区别导致了前端开发非常熟悉的一些库,例如 jQuery、 Zepto 等,在小程序中是无法运行的。同时 JSCore 的环境同 NodeJS 环境也是不尽相同,所以一些 NPM 的包在小程序中也是无法运行的。
运行环境 | 逻辑层 | 渲染层 |
---|---|---|
iOS | JavaScriptCore | WKWebView |
安卓 | V8 | chromium 定制内核 |
小程序开发者工具 | NWJS | Chrome WebView |
> 小程序的运行环境
小程序代码构成
小程序主要有以下四种格式的文件组成:
.json
后缀的JSON
配置文件.wxml
后缀的WXML
模板文件.wxss
后缀的WXSS
样式文件.js
后缀的JS
脚本逻辑文件
小程序的宿主环境
我们称微信客户端给小程序所提供的环境为宿主环境。
小程序的运行环境分成渲染层和逻辑层,其中 WXML 模板和 WXSS 样式工作在渲染层,JS 脚本工作在逻辑层。它们分别由 2 个线程管理:渲染层的界面使用了 WebView 进行渲染;逻辑层采用 JsCore 线程运行 JS 脚本。
一个小程序存在多个界面,所以渲染层存在多个 WebView 线程,这两个线程的通信会经由微信客户端(下文中也会采用 Native 来代指微信客户端)做中转,逻辑层发送网络请求也经由 Native 转发,小程序的通信模型下图所示。
![[assets/Pasted image 20230609100400.png]]
微信客户端在打开小程序之前,会把整个小程序的代码包下载到本地。
紧接着通过 app.json
的 pages
字段就可以知道你当前小程序的所有页面路径,继而微信客户端会把首页的代码装载进来,通过小程序底层的一些机制,就可以渲染出这个首页。
小程序启动之后,在 app.js
定义的 App
实例的 onLaunch
回调会被执行。整个小程序只有一个 App 实例,是全部页面共享的。
那么,页面是如何装载、渲染的呢?
微信客户端会先根据 page.json
配置生成一个界面,顶部的颜色和文字你都可以在这个 json
文件里边定义好。紧接着客户端就会装载这个页面的 WXML
结构和 WXSS
样式,最后客户端会装载 page.js
(一个 Page
页面构造器)。
Page
是一个页面构造器,这个构造器就生成了一个页面。在生成页面的时候,小程序框架会把 data
数据和 index.wxml
一起渲染出最终的结构,于是就得到了你看到的小程序的样子。
在渲染完界面之后,页面实例就会收到一个 onLoad
的回调,你可以在这个回调处理你的逻辑。
组件 & API
小程序提供了丰富的基础组件给开发者,使用组件的时候,还可以通过属性传递值给组件,让组件可以以不同的状态去展现,组件的内部行为也会通过事件的形式让开发者可以感知。
为了让开发者可以很方便的调起微信提供的能力,例如获取用户信息、微信支付等等,小程序提供了很多 API 给开发者去使用。需要注意的是:多数 API 的回调都是异步,你需要处理好代码逻辑的异步问题。
目录结构
小程序包含一个描述整体程序的 app
和多个描述各自页面的 page
。
一个小程序主体部分由三个文件组成,必须放在项目的根目录,如下:
文件 | 必需 | 作用 |
---|---|---|
app.js | 是 | 小程序逻辑 |
app.json | 是 | 小程序公共配置 |
app.wxss | 否 | 小程序公共样式表 |
一个小程序页面由四个文件组成,分别是:
文件类型 | 必需 | 作用 |
---|---|---|
js | 是 | 页面逻辑 |
wxml | 是 | 页面结构 |
json | 否 | 页面配置 |
wxss | 否 | 页面样式表 |
注意:为了方便开发者减少配置项,描述页面的四个文件必须具有相同的路径与文件名。
配置小程序
全局配置
小程序根目录下的 app.json
文件用来对微信小程序进行全局配置,决定页面文件的路径、窗口表现、设置网络超时时间、设置多 tab 等。
页面配置
每一个小程序页面也可以使用同名 .json
文件来对本页面的窗口表现进行配置,页面中配置项会覆盖 app.json
的 window
中相同的配置项。
sitemap 配置
微信现已开放小程序内搜索,开发者可以通过 sitemap.json
配置,或者管理后台页面收录开关来配置其小程序页面是否允许微信索引。当开发者允许微信索引时,微信会通过爬虫的形式,为小程序的页面内容建立索引。当用户的搜索词条触发该索引时,小程序的页面将可能展示在搜索结果中。
……
小程序框架
小程序开发框架的目标是通过尽可能简单、高效的方式让开发者可以在微信中开发具有原生 APP 体验的服务。
整个小程序框架系统分为两部分: 逻辑层(App Service)和视图层(View)。
小程序提供了自己的视图层描述语言 WXML
和 WXSS
,以及基于 JavaScript
的逻辑层框架,并在视图层与逻辑层间提供了数据传输和事件系统,让开发者能够专注于数据与逻辑。
:: 所有的 MVVM 系统基本上都是这个原理。
框架的核心是一个响应的数据绑定系统,可以让数据与视图非常简单地保持同步。
框架管理了整个小程序的页面路由,可以做到页面间的无缝切换,并给以页面完整的生命周期。开发者需要做的只是将页面的数据、方法、生命周期函数注册到框架中,其他的一切复杂的操作都交由框架处理。
框架提供了一套基础组件,这些组件自带微信风格的样式以及特殊的逻辑,开发者可以通过组合基础组件,创建出强大的微信小程序。还提供丰富的微信原生 API,可以方便的调起微信提供的能力,如获取用户信息,本地存储,支付功能等。
逻辑层
小程序开发框架的逻辑层使用 JavaScript
引擎为小程序提供开发 JavaScript
代码的运行环境以及微信小程序的特有功能。
逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈。
💡 开发者写的所有代码最终将会打包成一份 JavaScript
文件,并在小程序启动的时候运行,直到小程序销毁。这一行为类似 ServiceWorker,所以逻辑层也称之为 App Service。
在 JavaScript 的基础上,我们增加了一些功能,以方便小程序的开发:
- 增加
App
和Page
方法,进行程序注册和页面注册。 - 增加
getApp
和getCurrentPages
方法,分别用来获取App
实例和当前页面栈。 - 提供丰富的 API,如微信用户数据,扫一扫,支付等微信特有能力。
- 提供模块化能力,每个页面有独立的作用域。
注意:小程序框架的逻辑层并非运行在浏览器中,因此 JavaScript 在 web 中一些能力都无法使用,如 window
,document
等。
如何注册页面呢?
- 使用
Page
构造器注册页面 - 使用
Component
构造器构造页面
Page
构造器适用于简单的页面。但对于复杂的页面, Page
构造器可能并不好用。此时,可以使用 Component
构造器来构造页面。 Component
构造器的主要区别是:方法需要放在 methods: { }
里面。
在页面中使用 behaviors
页面可以引用 behaviors , behaviors 可以用来让多个页面有相同的数据字段和方法。
:: 类似 mixin 机制
页面 Page 实例的生命周期
![[assets/Pasted image 20230609111716.png|550]]
页面路由
在小程序中所有页面的路由全部由框架进行管理,框架以页面栈的形式维护了当前的所有页面,开发者可以使用 getCurrentPages()
函数获取当前页面栈。
:: 对于路由的触发方式以及页面生命周期函数,有一些小技巧可以注意一下,即:
- 所有的页面出现在前台时都会触发
onShow
- 只有新页面入栈时(页面创建),路由后页面才会触发
onLoad
- 页面出栈, 路由前页面就会触发
onUnload
- 只有打开新页面时,路由前页面才会触发
onHide
注意事项:
navigateTo
,redirectTo
只能打开非 tabBar 页面。switchTab
只能打开 tabBar 页面。reLaunch
可以打开任意页面。- 页面底部的 tabBar 由页面决定,即只要是定义为 tabBar 的页面,底部都有 tabBar。
- 调用页面路由带的参数可以在目标页面的
onLoad
中获取。
模块化?
可以将一些公共的代码抽离成为一个单独的 js 文件,作为一个模块。模块只有通过 module.exports
或者 exports
才能对外暴露接口。
在需要使用这些模块的文件中,使用 require
将公共代码引入。
:: 对,就是 CommonJS 那一套。
API ?
- 事件监听 API
- 同步 API
- 异步 API
事件监听 API。我们约定,以 on
开头的 API 用来监听某个事件是否触发。这类 API 接受一个回调函数作为参数,当事件触发时会调用这个回调函数,并将相关数据以参数形式传入。
同步 API 。我们约定,以 Sync
结尾的 API 都是同步 API。
异步 API 。大多数 API 都是异步 API,这类 API 接口通常都接受一个 Object
类型的参数,这个参数都支持按需指定以下字段来接收接口调用结果。异步 API 的执行结果需要通过 Object
类型的参数中传入的对应回调函数获取。部分异步 API 也会有返回值,可以用来实现更丰富的功能。
异步 API 可以返回 Promise 吗?
基础库 2.10.2 版本起,异步 API 支持 callback & promise 两种调用方式。当接口参数 Object 对象中不包含 success/fail/complete
时将默认返回 promise,否则仍按回调方式执行,无返回值。
注意:部分接口如 downloadFile
, request
, uploadFile
, connectSocket
, createCamera
(小游戏)本身就有返回值,它们的 promisify 需要开发者自行封装。
|
|
小程序支持去开发 API 了 ?
是的(收费 💸),开通并使用微信云开发,即可使用云开发 API,在小程序端直接调用服务端的云函数。
视图层
框架的视图层由 WXML 与 WXSS 编写,由组件来进行展示,如:
- WXML (WeiXin Markup language) 用于描述页面的结构。
- WXS (WeiXin Script) 是小程序的一套脚本语言,结合
WXML
,可以构建出页面的结构。 - WXSS (WeiXin Style Sheet) 用于描述页面的样式。
将逻辑层的数据反映成视图,同时将视图层的事件发送给逻辑层。
💡 组件 (Component)是视图的基本组成单元。
:: 微信小程序提供的这套框架,最终还是要打包成一个 js 文件,这和 Vue、React 及像 Webpack、 Rollup 这类打包工具是一样的。这也是,为什么现在有许多不使用微信小程序框架开发,可以使用相应的 Vue、 React 语法来开发的原因了,如 Taro , Uniapp 等。
WXML 是什么?
WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。
具体的能力以及使用方式在以下章节查看:
WXSS 是什么?
WXSS (WeiXin Style Sheets)是一套样式语言,用于描述 WXML 的组件样式。
为了适应广大的前端开发者,WXSS 具有 CSS 大部分特性。同时为了更适合开发微信小程序,WXSS 对 CSS 进行了扩充以及修改。
与 CSS 相比,WXSS 扩展的特性有:
- 尺寸单位
- 样式导入
尺寸单位 rpx(responsive pixel) ,可以根据屏幕宽度进行自适应。规定屏幕宽为 750rpx。如在 iPhone6 上,屏幕宽度为 375px,共有 750 个物理像素,则 750rpx = 375px = 750 物理像素,1rpx = 0.5px = 1 物理像素。
样式导入,使用 @import
语句可以导入外联样式表,@import
后跟需要导入的外联样式表的相对路径,用 ;
表示语句结束。
框架组件上支持使用 style
(动态样式)、 class
(静态样式) 属性来控制组件的样式。
WXS ? ? ?
WXS(WeiXin Script)是小程序的一套脚本语言,结合 WXML
,可以构建出页面的结构。
WXS 与 JavaScript 是不同的语言,有自己的语法,并不和 JavaScript 一致。WXS 的运行环境和其他 JavaScript 代码是隔离的,WXS 中不能调用其他 JavaScript 文件中定义的函数,也不能调用小程序提供的 API。
<!--wxml-->
<wxs module="m1">
var msg = "hello world";
module.exports.message = msg;
</wxs>
<view> {{m1.message}} </view>
:: 怎么说呢?是不是很像
<script>
? 又有点类似于computed
。
事件系统
什么是事件?
- 事件是视图层到逻辑层的通讯方式;
- 事件可以将用户的行为反馈到逻辑层进行处理;
- 事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数;
- 事件对象可以携带额外信息,如 id, dataset, touches。
也可以使用 WXS 函数响应事件,但不常用。
事件分为冒泡事件和非冒泡事件:
- 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
- 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。
有哪些冒泡事件呢?
类型 | 触发条件 | 最低版本 |
---|---|---|
touchstart | 手指触摸动作开始 | |
touchmove | 手指触摸后移动 | |
touchcancel | 手指触摸动作被打断,如来电提醒,弹窗 | |
touchend | 手指触摸动作结束 | |
tap | 手指触摸后马上离开 | |
longpress | 手指触摸后,超过 350ms 再离开,如果指定了事件回调函数并触发了这个事件,tap 事件将不被触发 | 1.5.0 |
longtap | 手指触摸后,超过 350ms 再离开(推荐使用 longpress 事件代替) | |
transitionend | 会在 WXSS transition 或 wx. createAnimation 动画结束后触发 | |
animationstart | 会在一个 WXSS animation 动画开始时触发 | |
animationiteration | 会在一个 WXSS animation 一次迭代结束时触发 | |
animationend | 会在一个 WXSS animation 动画完成时触发 | |
touchforcechange | 在支持 3D Touch 的 iPhone 设备,重按时会触发 | 1.9.90 |
注:除上表之外的其他组件自定义事件如无特殊声明都是非冒泡事件,如 form 的 submit
事件,input 的 input
事件,scroll-view 的 scroll
事件。(详见各个组件)
如何进行事件绑定呢?
事件绑定的写法类似于组件的属性,如:
<!-- 点击 view ,页面的 handleTap 会被调用 -->
<view bindtap="handleTap">
Click here!
</view>
事件绑定函数可以是一个数据绑定(并不推荐),如:
<view bindtap="{{ handlerName }}">
Click here!
</view>
此时,页面的 this.data.handlerName
必须是一个字符串,指定事件处理函数名;如果它是个空字符串,则这个绑定会失效(可以利用这个特性来暂时禁用一些事件)。
自基础库版本 1.5.0 起,在大多数组件和自定义组件中, bind
后可以紧跟一个冒号,其含义不变,如 bind: tap
。基础库版本 2.8.1 起,在所有组件中开始提供这个支持。
:: 这更新有什么用?一开始就应该用冒号!
绑定并阻止事件冒泡
除 bind
外,也可以用 catch
来绑定事件。与 bind
不同, catch
会阻止事件向上冒泡。
自基础库版本 2.8.2 起,除 bind
和 catch
外,还可以使用 mut-bind
来绑定事件。一个 mut-bind
触发后,如果事件冒泡到其他节点上,其他节点上的 mut-bind
绑定函数不会被触发,但 bind
绑定函数和 catch
绑定函数依旧会被触发。
事件的捕获阶段?
捕获阶段位于冒泡阶段之前,且在捕获阶段中,事件到达节点的顺序与冒泡阶段恰好相反。需要在捕获阶段监听事件时,可以采用 capture-bind
、capture-catch
关键字,后者将中断捕获阶段和取消冒泡阶段。
如无特殊说明,当组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象。这个事件对象是什么样的呢?
⭐ 事件对象
BaseEvent 基础事件对象属性列表:
属性 | 类型 | 说明 | 基础库版本 |
---|---|---|---|
type | String | 事件类型 | |
timeStamp | Integer | 事件生成时的时间戳 | |
target | Object | 触发事件的组件的一些属性值集合 | |
currentTarget | Object | 当前组件的一些属性值集合 | |
mark | Object | 事件标记数据 | 2.7.1 |
CustomEvent 自定义事件对象属性列表(继承 BaseEvent):
属性 | 类型 | 说明 |
---|---|---|
detail | Object | 额外的信息 |
自定义事件所携带的数据,如表单组件的提交事件会携带用户的输入,媒体的错误事件会携带错误信息,详见组件定义中各个事件的定义。
点击事件的
detail
带有的 x, y 同 pageX, pageY 代表距离文档左上角的距离。
TouchEvent 触摸事件对象属性列表(继承 BaseEvent):
属性 | 类型 | 说明 |
---|---|---|
touches | Array | 触摸事件,当前停留在屏幕中的触摸点信息的数组 |
changedTouches | Array | 触摸事件,当前变化的触摸点信息的数组 |
特殊事件: canvas 中的触摸事件不可冒泡,所以没有 currentTarget。
这里也有 target
和 currentTarget
,它们有什么不同呢?
<view id="outer" bindtap="handleTap1">
outer view
<view id="middle" catchtap="handleTap2">
middle view
<view id="inner" bindtap="handleTap3">
inner view
</view>
</view>
</view>
说明: target 和 currentTarget 可以参考上例中,点击 inner view 时,handleTap3
收到的事件对象 target 和 currentTarget 都是 inner,而 handleTap2
收到的事件对象 target 就是 inner,currentTarget 就是 middle。
原来, target
指的是触发事件的源组件;而 currentTarget
是指事件绑定的当前组件。
组件 | 属性 | 类型 | 说明 |
---|---|---|---|
target | id | String | 事件源组件的 id |
dataset | Object | 事件源组件上由 data- 开头的自定义属性组成的集合 |
|
— | — | — | — |
currentTarget | id | String | 当前组件的 id |
dataset | Object | 当前组件上由 data- 开头的自定义属性组成的集合 |
在组件节点中可以附加一些自定义数据。这样,在事件中可以获取这些自定义的节点数据,用于事件的逻辑处理。
在 WXML 中,这些自定义数据以 data-
开头,多个单词由连字符 -
连接。这种写法中,连字符写法会转换成驼峰写法,而大写字符会自动转成小写字符。
:: 基本上和 html 的一样的。
mark 又是个啥?
在基础库版本 2.7.1 以上,可以使用 mark
来识别具体触发事件的 target 节点。此外, mark
还可以用于承载一些自定义数据(类似于 dataset
)。
当事件触发时,事件冒泡路径上所有的 mark
会被合并,并返回给事件回调函数。(即使事件不是冒泡事件,也会 mark
。)
<view mark:myMark="last" bindtap="bindViewTap">
<button mark:anotherMark="leaf" bindtap="bindButtonTap">按钮</button>
</view>
在上述 WXML 中,如果按钮被点击,将触发 bindViewTap
和 bindButtonTap
两个事件,事件携带的 event.mark
将包含 myMark
和 anotherMark
两项。
|
|
mark
和 dataset
很相似,主要区别在于: mark
会包含从触发事件的节点到根节点上所有的 mark:
属性值;而 dataset
仅包含一个节点的 data-
属性值。
touches & changedTouches
touches 是一个数组,每个元素为一个 Touch 对象(canvas 触摸事件中携带的 touches 是 CanvasTouch 数组),表示当前停留在屏幕上的触摸点。
属性 | 类型 | 说明 |
---|---|---|
identifier | Number | 触摸点的标识符 |
pageX, pageY | Number | 距离文档左上角的距离,文档的左上角为原点,横向为 X 轴,纵向为 Y 轴 |
clientX, clientY | Number | 距离页面可显示区域(屏幕除去导航条)左上角距离,横向为 X 轴,纵向为 Y 轴 |
> Touch 对象
属性 | 类型 | 说明 | 特殊说明 |
---|---|---|---|
identifier | Number | 触摸点的标识符 | |
x, y | Number | 距离 Canvas 左上角的距离,Canvas 的左上角为原点,横向为 X 轴,纵向为 Y 轴 |
> CanvasTouch 对象
changedTouches 数据格式同 touches ,表示有变化的触摸点,如从无变有(touchstart),位置变化(touchmove),从有变无(touchend、touchcancel)。
为什么需要 WXS 响应事件?
有频繁用户交互的效果在小程序上表现是比较卡顿的!
……
简易双向绑定
在 WXML 中,普通的属性的绑定是单向的。例如:
<input value="{{value}}" />
如果使用 this.setData({ value: 'leaf' })
来更新 value
,this.data.value
和输入框的中显示的值都会被更新为 leaf
;但如果用户修改了输入框里的值,却不会同时改变 this.data.value
。
如果需要在用户输入的同时改变 this.data.value
呢?借助简易双向绑定机制。
此时,可以在对应项目之前加入 model:
前缀:
<input model:value="{{value}}" />
这样,如果输入框的值被改变了, this.data.value
也会同时改变。同时, WXML 中所有绑定了 value
的位置也会被一同更新, 数据监听器 也会被正常触发。
如果想在自定义组件中传递双向绑定呢?一样的。
基础组件
什么是组件?
- 组件是视图层的基本组成单元。
- 组件自带一些功能与微信风格一致的样式。
- 一个组件通常包括
开始标签
和结束标签
,属性
用来修饰这个组件,内容
在两个标签之内。
注意:所有组件与属性都是小写,以连字符 -
连接。
所有组件都有以下属性:
属性名 | 类型 | 描述 | 注解 |
---|---|---|---|
id | String | 组件的唯一标示 | 保持整个页面唯一 |
class | String | 组件的样式类 | 在对应的 WXSS 中定义的样式类 |
style | String | 组件的内联样式 | 可以动态设置的内联样式 |
hidden | Boolean | 组件是否显示 | 所有组件默认显示 |
data-* | Any | 自定义属性 | 组件上触发的事件时,会发送给事件处理函数 |
bind* catch* | EventHandler | 组件的事件 | 详见事件 |
几乎所有组件都有各自定义的属性,可以对该组件的功能或样式进行修饰,请参考各个组件的定义。
获取界面上的节点信息
……
后续还有一些章节,这里先不做讨论。
小程序运行时
微信小程序运行在多种平台上:iOS/iPadOS 微信客户端、Android 微信客户端、Windows PC 微信客户端、Mac 微信客户端、小程序硬件框架和用于调试的微信开发者工具等。
不同运行环境下,脚本执行环境以及用于组件渲染的环境是不同的,性能表现也存在差异。
运行机制
小程序的生命周期
小程序从启动到最终被销毁,会经历很多不同的状态,小程序在不同状态下会有不同的表现。
![[assets/Pasted image 20230609160107.png]]
1. 小程序的启动
从用户认知的角度看,广义的小程序启动可以分为两种情况,一种是冷启动,一种是热启动。
- 冷启动:如果用户首次打开,或小程序销毁后被用户再次打开,此时小程序需要重新加载启动,即冷启动。
- 热启动:如果用户已经打开过某小程序,然后在一定时间内再次打开该小程序,此时小程序并未被销毁,只是从后台状态进入前台状态,这个过程就是热启动。
从小程序生命周期的角度来看,我们一般讲的「启动」专指冷启动,热启动一般被称为后台切前台。
2. 前台与后台
小程序启动后,界面被展示给用户,此时小程序处于「前台」状态。
当用户「关闭」小程序时,小程序并没有真正被关闭,而是进入了「后台」状态,此时小程序还可以短暂运行一小段时间,但部分 API 的使用会受到限制。
3. 挂起
小程序进入「后台」状态一段时间后(目前是 5 秒),微信会停止小程序 JS 线程的执行,小程序进入「挂起」状态。此时小程序的内存状态会被保留,但开发者代码执行会停止,事件和接口回调会在小程序再次进入「前台」时触发。
当开发者使用了后台音乐播放、后台地理位置等能力时,小程序可以在「后台」持续运行,不会进入到「挂起」状态。
4. 小程序销毁
如果用户很久没有使用小程序,或者系统资源紧张,小程序会被「销毁」,即完全终止运行。
5. 退出状态
小程序冷启动时,如果启动时不带 path(A 类场景),默认情况下将会进入小程序的首页。在页面对应的 json 文件中(也可以全局配置在 app. json 的 window 段中),指定 restartStrategy
配置项可以改变这个默认的行为,使得从某个页面退出后,下次 A 类场景的冷启动可以回到这个页面。
可选值 | 含义 |
---|---|
homePage | (默认值)如果从这个页面退出小程序,下次将从首页冷启动 |
homePageAndLatestPage | 如果从这个页面退出小程序,下次冷启动后立刻加载这个页面,页面的参数保持不变(不可用于 tab 页) |
> restartStrategy 可选值
无论如何,页面中的状态并不会被保留,如输入框中的文本内容、 checkbox 的勾选状态等都不会还原。如果需要还原或部分还原,需要利用退出状态。
每当小程序可能被销毁之前,页面回调函数 onSaveExitState
会被调用。如果想保留页面中的状态,可以在这个回调函数中“保存”一些数据,下次启动时可以通过 exitState
获得这些已保存数据。
onSaveExitState
返回值可以包含两项:
字段名 | 类型 | 含义 |
---|---|---|
data | Any | 需要保存的数据(只能是 JSON 兼容的数据) |
expireTimeStamp | Number | 超时时刻,在这个时刻后,保存的数据保证一定被丢弃,默认为 (当前时刻 + 1 天) |
更新机制
开发者在管理后台发布新版本的小程序之后,微信客户端会有若干个时机去检查本地缓存的小程序有没有新版本,并进行小程序的代码包更新。但如果用户本地有小程序的历史版本,此时打开的可能还是旧版本。
……
注意: 开发者在后台发布新版本之后,无法立刻影响到所有现网用户,但最差情况下,也在发布之后 24 小时之内覆盖绝大多数用户。
自定义组件
开发者可以将页面内的功能模块抽象成自定义组件,以便在不同的页面中重复使用;也可以将复杂的页面拆分成多个低耦合的模块,有助于代码维护。自定义组件在使用时与基础组件非常相似。
如何创建一个自定义组件呢?
类似于页面,一个自定义组件由 json
wxml
wxss
js
4 个文件组成。要编写一个自定义组件,首先需要在 json
文件中进行自定义组件声明(将 component
字段设为 true
可将这一组文件设为自定义组件):
|
|
同时,还要在 wxml
文件中编写组件模板,在 wxss
文件中加入组件样式,它们的写法与页面的写法类似。
注意:在组件 wxss 中不应使用 ID 选择器、属性选择器和标签名选择器。
在自定义组件的 js
文件中,需要使用 Component()
来注册组件,并提供组件的属性定义、内部数据和自定义方法。
组件的属性值和内部数据将被用于组件 wxml
的渲染,其中,属性值是可由组件外部传入的。
如何创使用自定义组件呢?
使用已注册的自定义组件前,首先要在页面的 json
文件中进行引用声明。此时需要提供每个自定义组件的标签名和对应的自定义组件文件路径:
|
|
这样,在页面的 wxml
中就可以像使用基础组件一样使用自定义组件。节点名即自定义组件的标签名,节点属性即传递给组件的属性值。
组件模板和样式
组件模板的写法与页面模板相同。组件模板与组件数据结合后生成的节点树,将被插入到组件的引用位置上。
与普通的 WXML 模板类似,可以使用数据绑定,这样就可以向子组件的属性传递动态数据。
在组件模板中可以提供一个 <slot>
节点,用于承载组件引用时提供的子节点。默认情况下,一个组件的 wxml 中只能有一个 slot 。需要使用多 slot 时,可以在组件 js 中声明启用。
|
|
此时,可以在这个组件的 wxml 中使用多个 slot ,以不同的 name
来区分。
关于组件样式的及样式隔离相关方面的知识,我们先不讨论。
Component 构造器
Component
构造器可用于定义组件,调用 Component
构造器时可以指定组件的属性、数据、方法等。
事实上,小程序的页面也可以视为自定义组件。因而,页面也可以使用 Component
构造器构造,拥有与普通组件一样的定义段与实例方法。但此时要求对应 json 文件中包含 usingComponents
定义段。
此时,组件的属性可以用于接收页面的参数,如访问页面 /pages/index/index?paramA=123¶mB=xyz
,如果声明有属性 paramA
或 paramB
,则它们会被赋值为 123
或 xyz
。
页面的生命周期方法(即 on
开头的方法),应写在 methods
定义段中。
💡 使用 Component
构造器来构造页面的一个好处是可以使用 behaviors
来提取所有页面中公用的代码段。
组件间通信与事件
组件间的基本通信方式有以下几种:
- WXML 数据绑定:用于父组件向子组件的指定属性设置数据,仅能设置 JSON 兼容数据;
- 事件:用于子组件向父组件传递数据,可以传递任意数据;
- 如果以上两种方式不足以满足需要,父组件还可以通过
this.selectComponent
方法获取子组件实例对象,这样就可以直接访问组件的任意数据和方法。
监听事件
事件系统是组件间通信的主要方式之一。自定义组件可以触发任意的事件,引用组件的页面可以监听这些事件。
监听自定义组件事件的方法与监听基础组件事件的方法完全一致。
触发事件
自定义组件触发事件时,需要使用 triggerEvent
方法,指定事件名、detail 对象和事件选项。
获取组件实例
可在父组件里调用 this.selectComponent
,获取子组件的实例对象。
若需要自定义 selectComponent
返回的数据,可使用内置 behavior
: wx://component-export
。
组件生命周期
组件的生命周期,指的是组件自身的一些函数,这些函数在特殊的时间点或遇到一些特殊的框架事件时被自动触发。
其中,最重要的生命周期是 created
attached
detached
,包含一个组件实例生命流程的最主要时间点。
- 组件实例刚刚被创建好时,
created
生命周期被触发。此时,组件数据this.data
就是在Component
构造器中定义的数据data
。 此时还不能调用setData
。 通常情况下,这个生命周期只应该用于给组件this
添加一些自定义属性字段。 - 在组件完全初始化完毕、进入页面节点树后,
attached
生命周期被触发。此时,this.data
已被初始化为组件的当前值。这个生命周期很有用,绝大多数初始化工作可以在这个时机进行。 - 在组件离开页面节点树后,
detached
生命周期被触发。退出一个页面时,如果组件还在页面节点树中,则detached
会被触发。
在 behaviors 中也可以编写生命周期方法,同时不会与其他 behaviors 中的同名生命周期相互覆盖。但要注意,如果一个组件多次直接或间接引用同一个 behavior ,这个 behavior 中的生命周期函数在一个执行时机内只会执行一次。
可用的全部生命周期如下表所示:
生命周期 | 参数 | 描述 | 最低版本 |
---|---|---|---|
created | 无 | 在组件实例刚刚被创建时执行 | 1.6.3 |
attached | 无 | 在组件实例进入页面节点树时执行 | 1.6.3 |
ready | 无 | 在组件在视图层布局完成后执行 | 1.6.3 |
moved | 无 | 在组件实例被移动到节点树另一个位置时执行 | 1.6.3 |
detached | 无 | 在组件实例被从页面节点树移除时执行 | 1.6.3 |
error | Object Error |
每当组件方法抛出错误时执行 | 2.4.1 |
还有一些特殊的生命周期,它们并非与组件有很强的关联,但有时组件需要获知,以便组件内部处理。这样的生命周期称为“组件所在页面的生命周期”,在 pageLifetimes
定义段中定义。
生命周期 | 参数 | 描述 | 最低版本 |
---|---|---|---|
show | 无 | 组件所在的页面被展示时执行 | 2.2.3 |
hide | 无 | 组件所在的页面被隐藏时执行 | 2.2.3 |
resize | Object Size |
组件所在的页面尺寸变化时执行 | 2.4.0 |
routeDone | 无 | 组件所在页面路由动画完成时执行 | 2.31.2 |
注意:自定义 tabBar 的 pageLifetime 不会触发。
behaviors
behaviors
是用于组件间代码共享的特性,类似于一些编程语言中的 “mixins” 或 “traits”。
每个 behavior
可以包含一组属性、数据、生命周期函数和方法。组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。 每个组件可以引用多个 behavior
,behavior
也可以引用其它 behavior
。
自定义组件可以通过引用内置的 behavior
来获得内置组件的一些行为。
组件间关系
……
数据监听器
数据监听器(observers
)可以用于监听和响应任何属性和数据字段的变化。
纯数据字段
纯数据字段是一些不用于界面渲染的 data 字段,可以用于提升页面更新性能。
有些情况下,某些 data
中的字段(包括 setData
设置的字段)既不会展示在界面上,也不会传递给其他组件,仅仅在当前组件内部使用。
此时,可以指定这样的数据字段为“纯数据字段”,它们将仅仅被记录在 this.data
中,而不参与任何界面渲染过程,这样有助于提升页面更新性能。
指定“纯数据字段”的方法是在 Component
构造器的 options
定义段中指定 pureDataPattern
为一个正则表达式,字段名符合这个正则表达式的字段将成为纯数据字段。
抽象节点
有时,自定义组件模板中的一些节点,其对应的自定义组件不是由自定义组件本身确定的,而是自定义组件的调用者确定的。这时可以把这个节点声明为“抽象节点”。
例如,我们现在来实现一个“选框组”(selectable-group)组件,它其中可以放置单选框(custom-radio)或者复选框(custom-checkbox)。
自定义组件扩展
为了更好定制自定义组件的功能,可以使用自定义组件扩展机制。
所谓‘自定义组件的扩展’其实就是提供了修改自定义组件定义段的能力。Behavior()
构造器提供了新的定义段 definitionFilter
,用于支持自定义组件扩展。
开发第三方自定义组件
……
占位组件
基础库尝试渲染一个组件时,会首先递归检查 usingComponents
,收集其将使用到的所有组件的信息;在这个过程中,如果某个被使用到的组件不可用,基础库会先检查其是否有对应的占位组件。如果没有,基础库会中断渲染并抛出错误;如果有,则会标记并在后续渲染流程中使用占位组件替换该不可用的组件进行渲染。不可用的组件会在当前渲染流程结束后尝试准备(下载分包或注入代码等);等到准备过程完成后,再尝试渲染该组件(实际上也是在执行这个流程),并替换掉之前渲染的占位组件。
查看自定义组件数据
wxml 面板中可以查看自定义组件在渲染时的 Data 数据。在 wxml 中先选中需要查看的自定义组件,然后切换到 Component Data
即可实时查看当前自定义组件的数据。
基础能力
网络
在小程序/小游戏中使用网络相关的 API 时,需要注意下列问题,请开发者提前了解。
存储
同一个微信用户,同一个小程序 storage 上限为 10MB。
文件系统
文件系统是小程序提供的一套以小程序和用户维度隔离的存储以及一套相应的管理接口。通过 wx.getFileSystemManager() 可以获取到全局唯一的文件系统管理器,所有文件系统的管理操作通过 FileSystemManager 来调用。
文件主要分为两大类:
- 代码包文件:代码包文件指的是在项目目录中添加的文件。
- 本地文件:通过调用接口本地产生,或通过网络下载下来,存储到本地的文件。
其中本地文件又分为三种:
- 本地临时文件:临时产生,随时会被回收的文件。运行时最多存储 4GB,结束运行后,如果已使用超过 2GB,会以文件为维度按照最近使用时间从远到近进行清理至少于 2GB。
- 本地缓存文件:小程序通过接口把本地临时文件缓存后产生的文件,不能自定义目录和文件名。跟本地用户文件共计,小程序(含小游戏)最多可存储 200MB。
- 本地用户文件:小程序通过接口把本地临时文件缓存后产生的文件,允许自定义目录和文件名。跟本地缓存文件共计,小程序(含小游戏)最多可存储 200MB。
代码包文件
代码包文件的访问方式是从项目根目录开始写文件路径,不支持相对路径的写法。如:/a/b/c
、a/b/c
都是合法的,./a/b/c
../a/b/c
则不合法。
本地文件
本地文件指的是小程序被用户添加到手机后,会有一块独立的文件存储区域,以用户维度隔离。即同一台手机,每个微信用户不能访问到其他登录用户的文件,同一个用户不同 appId 之间的文件也不能互相访问。
Canvas
canvas 组件 提供了绘制界面,可以在之上进行任意绘制。
分包加载
某些情况下,开发者需要将小程序划分成不同的子包,在构建时打包成不同的分包,用户在使用时按需进行加载。
在构建小程序分包项目时,构建会输出一个或多个分包。每个使用分包小程序必定含有一个主包。所谓的主包,即放置默认启动页面/TabBar 页面,以及一些所有分包都需用到公共资源/JS 脚本;而分包则是根据开发者的配置进行划分。
在小程序启动时,默认会下载主包并启动主包内页面,当用户进入分包内某个页面时,客户端会把对应分包下载下来,下载完成后再进行展示。
前小程序分包大小有以下限制:
- 整个小程序所有分包大小不超过 20M
- 单个分包/主包大小不能超过 2M
使用分包
开发者通过在 app. json subpackages
字段声明项目分包结构。
:: 这个对于稍微大点的项目,是很必要的。
独立分包
独立分包是小程序中一种特殊类型的分包,可以独立于主包和其他分包运行。从独立分包中页面进入小程序时,不需要下载主包。当用户进入普通分包或主包内页面时,主包才会被下载。
一个小程序中可以有多个独立分包。
开发者通过在 app.json
的 subpackages
字段中对应的分包配置项中定义 independent
字段声明对应分包为独立分包。
分包预下载
开发者可以通过配置,在进入小程序某个页面时,由框架自动预下载可能需要的分包,提升进入后续分包页面时的启动速度。对于独立分包,也可以预下载主包。
分包预下载目前只支持通过配置方式使用,暂不支持通过调用 API 完成。
预下载分包行为在进入某个页面时触发,通过在 app.json
增加 preloadRule
配置来控制。
分包异步化
跨分包自定义组件引用 ,一个分包使用其他分包的自定义组件时,由于其他分包还未下载或注入,其他分包的组件处于不可用的状态。通过为其他分包的自定义组件设置 占位组件,我们可以先渲染占位组件作为替代,在分包下载完成后再进行替换。
跨分包 JS 代码引用,一个分包中的代码引用其它分包的代码时,为了不让下载阻塞代码运行,我们需要异步获取引用的结果。
按需注入和用时注入
……