小程序的逻辑层和渲染层是分开的,逻辑层运行在 JSCore 中,并没有一个完整浏览器对象,因而缺少相关的DOM API和BOM API。小程序开发主要面对的是两大操作系统ios和android的微信客户端; MVVM
是关键;
###文件支持
支持wxml,wxss,js,json,wxs
以及图片文件的预览;
###JSON 配置文件
在小程序中JSON是用于静态配置;
app.json
和project.config.json
和pages/logs
目录下还有一个logs.json
;
小程序配置 app.json
工具配置 project.config.json
页面配置 page.json
pages/logs
中的logs.json这类和小程序页面相关的配置;###WXML 模板文件 weixin markup language
在小程序中WXML相当于网页开发中的HTML;不同点在于:
wx:if
这样的属性以及类似于{ {}}
表达式;
MVVM
的开发模,将渲染和逻辑分离,数据绑定;###WXSS 样式文件 weixin style sheet
类似于网页开发中的css; 扩展:
rpx
;(responsive pixel)可以根据屏幕宽度进行自适应;@import
导入外联样式表, 外联样式表的相对路径;app.wxss
表示全局样式;局部样式 page.wxss
仅对当前页面生效;
/** common.wxss **/
.small-p {
padding:5px;
}
/** app.wxss **/
@import "common.wxss";
.middle-p {
padding:15px;
}
###wxs 小程序脚本语言; weixin script
可构建出页面的结构;
// page.js
Page({
data: {
array: [1, 2, 3, 4, 5, 1, 2, 3, 4]
}
})
var getMax = function(array) {
var max = undefined;
for (var i = 0; i < array.length; ++i) {
max = max === undefined ?
array[i] :
(max >= array[i] ? max : array[i]);
}
return max;
}
module.exports.getMax = getMax;
{
{m1.getMax(array)}}
###JS 脚本逻辑文件 javascript
用于和用户交互;
小程序的运行环境可分为 渲染层(View)和逻辑层(App Service); wxml和wxss工作在渲染层,js脚本工作在逻辑层; 分别由两个线程管理,渲染层的界面使用webview 进行渲染;逻辑层采用JsCore线程运行JS脚本;
小程序存在多个界面,渲染层存在有多个webview线程,这两个线程的通信会经由微信客户端(Native代指微信客户端)做中转,逻辑层发送网络请求也经由Native转发;
wx客户端 在打开小程序之前,会把整个小程序的代码包下载到本地;
app.json
的pages字段可以知道当前小程序的所有页面路径; wx客户端把首页的代码装载进来,通过底层的一些机制,渲染整个首页;
小程序启动后, app.js
定义的app实例 的onLaunch
回调会被执行; 整个小程序只有一个app实例 是全部页面共享的;
js,json,wxml,wxss
首先wx客户端会根据json配置生成一个界面,配置整体页面的颜色和文字;页面 js文件中 Page
是一个页面构造器,这个构造器就生成了一个页面; 在生成页面时,小程序框架会把data数据和index.wxml 一起渲染出最终的结构; 在渲染完界面之后,页面实例就会收到一个onLoad
的回调,可以在这个回调中处理你的逻辑;
###目录结构
app : 描述整体程序;
page : 描述各自页面;
一个小程序主体部分构成: app.js, app.json, app.wxss;
一个小程序页面构成: js, wxml, json, wxss; 描述页面的四个文件必须具有相同的路径和文件名;
小程序根目录下 sitemap.json 文件用来配置小程序及其页面是否允许被微信索引;
默认是都能被索引;
###多个简便写法
微信官方文档 behaviors
###页面路由
框架进行页面的路由管理,以栈的形式维护了当前的所有页面;
路由方式 | 页面栈表现 | 触发时机 | 路由前页面 | 路由后页面 |
---|---|---|---|---|
初始化 | 新页面入栈 | 小程序打开的第一个页面 | onLoad, onShow | |
打开新页面 | 新页面入栈 | 调用 API wx.navigateTo 使用组件
|
onHide | onLoad, onShow |
页面重定向 | 当前页面出栈,新页面入栈 | 调用 API wx.redirectTo 使用组件
|
onUnload | onLoad, onShow |
页面返回 | 页面不断出栈,直到目标返回页 | 调用 API wx.navigateBack 使用组件 用户按左上角返回按钮 |
onUnload | onShow |
Tab切换 | 页面全部出栈,只留下新的tab页面 | 调用 API wx.switchTab 使用组件 用户切换 Tab |
各种情况请参考下表 | |
重加载 | 页面全部出栈,只留下新的页面 | 调用 API wx.reLaunch 使用组件
|
onUnload | onLoad, onShow |
getCurrentPages()
获取当前页面栈;
tips:
###模块化 (相当于library或者工具类)
将公共的代码抽离成为一个单独的js文件,作为一个模块; 模块只需要通过module.exports
或者exports
才能对外暴露接口;
module.exports
来暴露接口; 在使用这些模块文件中,使用require
将公共代码引入;
// common.js
function sayHello(name) {
console.log(`Hello ${name} !`)
}
function sayGoodbye(name) {
console.log(`Goodbye ${name} !`)
}
module.exports.sayHello = sayHello
exports.sayGoodbye = sayGoodbye
---------------------
var common = require('common.js')
Page({
helloMINA: function() {
common.sayHello('MINA')
},
goodbyeMINA: function() {
common.sayGoodbye('MINA')
}
})
###微信原生API
事件监听API
on
开头的api 用来监听某个事件是否触发,这类api接受一个回调函数作为参数,当事件触发时会调用这个回调函数,并将相关数据以参数形式传入;同步API
Sync
结尾的api 通过函数返回值直接获取,执行出错会抛出异常; 需要try catch;异步API
wx.login({
success(res) {
console.log(res.code)
}
})
wx.chooseImage({
success(res) {
console.log('res:', res)
}
})
wx.chooseImage().then(res => console.log('res: ', res))
###事件系统
使用方式:
组件中绑定一个事件处理函数; bindtap
在页面对应的page中找到对应的事件处理函数; 后期可以改为 bind:tap
wxs函数响应事件:
wxs函数接受2个参数,第一个是event
,在原有的event的基础上加event.instance对象; 第二个参数是ownerInstance
,和event.instance一样是一个ComponentDescriptor
对象;
event.instance 表示触发事件的组件的ComponentDescriptor实例;
ownerInstance 表示的是触发事件的组件所在的组件的 ComponentDescriptor实例;如果触发事件的组件是在页面内的,则表示的是页面实例;
wxs运行在视图层(webview),需要有一个机制和逻辑层开发者(App Service)的代码通信,ComponentDescriptor 的callMethod
是wxs里面调用逻辑层(appservice)定义的方法;
WxsPropObserver
是逻辑层(appservice)调用wxs逻辑的机制; wxs函数必须由{ {}}
括起来;当 prop 的值被设置 WXS 函数就会触发,而不只是值发生改变,所以在页面初始化的时候会调用一次WxsPropObserver的函数。
--------
Click me!
**注:绑定的WXS函数必须用{
{}}括起来**
--------
function tapName(event, ownerInstance) {
console.log('tap wechat', JSON.stringify(event))
}
module.exports = {
tapName: tapName
}
//`change:prop`是在prop属性被设置的时候触发wxs函数;
module.exports = {
touchmove: function(event, instance) {
console.log('log event', JSON.stringify(event))
},
propObserver: function(newValue, oldValue, ownerInstance, instance) {
console.log('prop observer', newValue, oldValue)
}
}
---------
//阻止事件冒泡 点击内部只有handTap3和handTap2会响应;
outer view
middle view
inner view
//互斥事件,点击内部 handleTap3和handleTap2会响应;点击middle,响应handTap2和handTap1;
outer view
middle view
inner view
//捕获事件,点击内部 hand2 -> hand4 -> hand3 ->hand1
outer view
inner view
事件分类
bind
外,也可以用catch
绑定事件; catch 会阻止事件向上冒泡;mut-bind
如果事件冒泡到其他节点上,其他节点的mut-bind绑定函数不会被触发,但bind绑定和catch的绑定函数依旧会被触发;capture-bind
,capture-catch
后者将中断捕获阶段和取消冒泡阶段;事件对象
触发事件时,逻辑层绑定该事件的处理函数会受到一个事件对象,BaseEvent / customEvent(detail) / touchevent(touches/changedTouches) ; 组成:
data-
开头,连字符写法最后会转为驼峰写法;mark
会包含从触发事件的节点到根节点上所有的mark:
属性值; 而dataset
仅包含一个节点的data-
属性值;
DataSet Test
Page({
bindViewTap:function(event){
event.currentTarget.dataset.alphaBeta === 1 // - 会转为驼峰写法
event.currentTarget.dataset.alphabeta === 2 // 大写会转为小写
}
})
---------
Page({
bindViewTap: function(e) {
e.mark.myMark === "last" // true
e.mark.anotherMark === "leaf" // true
}
})
###组件
组件时视图层的基本组成单元;组件包括一个开始标签和结束标签,属性用来修饰这个组件,内容在两个标签之间;
属性类型: boolean,number,string,array,object,eventHandler,any;
公共属性:
###动画
this.animate('#container', [
{ opacity: 1.0, rotate: 0, backgroundColor: '#FF0000' },
{ opacity: 0.5, rotate: 45, backgroundColor: '#00FF00'},
{ opacity: 0.0, rotate: 90, backgroundColor: '#FF0000' },
], 5000, function () {
this.clearAnimation('#container', { opacity: true, rotate: true }, function () {
console.log("清除了#container上的opacity和rotate属性")
})
}.bind(this))
this.animate('.block', [
{ scale: [1, 1], rotate: 0, ease: 'ease-out' },
{ scale: [1.5, 1.5], rotate: 45, ease: 'ease-in', offset: 0.9},
{ scale: [2, 2], rotate: 90 },
], 5000, function () {
this.clearAnimation('.block', function () {
console.log("清除了.block上的所有动画属性")
})
}.bind(this))
###重启策略和更新机制
重启策略
pages 对应的json文件中配置(也可以配置在app.json的window字段中),指定restartStrategy
配置项,使得某个页面退出后,下次A类场景(不包括重定向和冷启动下带path跳入的场景) 的冷启动可以回到这个页面;
注意: 但不包括退出过久的情况,因为存在退出状态的保存
中有expireTimeStamp 字段哟用户超时丢弃数据;
{
"restartStrategy": "homePage"
}
退出状态
: 类似于android的保存状态 ,小程序可能销毁时调用onSaveExitState
保存页面状态,下次启动时刻通过exitState
获得这些数据;
onSaveExitState
返回值包括两项: data:Any
,expireTimeStamp:Number
(默认当前时刻+1天)
更新机制
wx client会有若干个时机检查本地缓存的小程序有没有更新版本,如果有会静默更新; 最差的情况下 发布之后24小时内更新;
使用 wx.getUpdataManager
Api
类似于页面,一个自定义组件由 json,wxml,wxss,js
;
要编写一个自定义组件, 首先需要在
json
文件中进行自定义组件声明(将component
字段设为true 可将一组文件设为自定义组件;)
{
"component": true
}
wxml
文件中加入组件样式, 在wxss
文件中加入组件样式;
styleIsolation
Component
构造器用于构造页面,默认值为shared;且还有额外的隔离选项可用:
styleIsolation
(这样不需在js中的options中再配置)addGlobalClass
选项,即在Component的options中设置 addGlobalClass:true
; 这个选项等价于设置styleIsolation:apply-shared
,但设置了styleIsolation选项后这个选项会失效;:host
选择器externalClasses
定义段定义若干个外部样式类; 避免同一个节点上使用普通样式类和外部样式类;isolated
,组件仍然可以在局部引用组件所在页面的样式或父组件的样式; 可以使用~
引用所在页面的样式; 可以使用^
引用父组件的样式; 如果组件是比较独立通用的组件,优先使用外部样式类的方式;在组件模板中可以提供一个
节点,用于承载组件引用时提供的子节点。 默认情况下,一个组件wxml中只能有一个slot,需要使用多slot时,可以在组件js中声明启用;
这里是组件的内部节点
组件的属性 propA 和 propB 将收到页面传递的数据。页面可以通过 setData 来改变绑定的数据字段
这里是插入到组件slot name="before"中的内容
这里是插入到组件slot name="after"中的内容
//引用外部样式:
/* 组件 custom-component.js */
Component({
externalClasses: ['my-class']
})
这段文本的颜色由组件外的 class 决定
在自定义组件的js文件中,需要使用
component
来注册组件,并提供组件的属性定义,内部数据和自定义方法;
Component({
options:{
multipleSlots:true, // 在组件定义时的选项中启用多slot支持
styleIsolation:'isolated'
}
properties: {
// 这里定义了innerText属性,属性值可以在组件使用时指定
innerText: {
type: String,
value: 'default value',
}
},
data: {
// 这里是一些组件内部数据
someData: {}
},
methods: {
// 这里是一个自定义方法
customMethod: function(){}
}
})
使用自定义组件,首先在
页面的json
文件中进行引用声明;
{
"usingComponents": {
"component-tag-name": "path/to/the/custom/component"
}
}
tips:
Component 构造器可用于定义组件,调用Component构造器可以指定组件的属性,数据,方法等;
Component({
behaviors: [],
properties: {
myProperty: { // 属性名
type: String,
value: ''
},
myProperty2: String // 简化的定义方式
},
data: {}, // 私有数据,可用于模板渲染
lifetimes: {
// 生命周期函数,可以为函数,或一个在methods段中定义的方法名
attached: function () { },
moved: function () { },
detached: function () { },
},
// 生命周期函数,可以为函数,或一个在methods段中定义的方法名
attached: function () { }, // 此处attached的声明会被lifetimes字段中的声明覆盖
ready: function() { },
pageLifetimes: {
// 组件所在页面的生命周期函数
show: function () { },
hide: function () { },
resize: function () { },
},
methods: {
onMyButtonTap: function(){
this.setData({
// 更新属性和数据的方法与更新页面数据的方法类似
})
},
// 内部方法建议以下划线开头
_myPrivateMethod: function(){
// 这里将 data.A[0].B 设为 'myPrivateData'
this.setData({
'A[0].B': 'myPrivateData'
})
},
_propertyChange: function(newVal, oldVal) {
}
}
})
使用Component
构造器构造页面 ,代替 Page
,需要在json中包含usingComponents
定义段; 使用Component构造器来构造页面的一个好处是可以使用behaviors
提取所有页面中公用的代码段; 可以在所有页面被创建和销毁时都会执行同一段代码,可以提取到behaviors;
注意: behavior
如果有同名的属性或方法,组件本身的属性或方法会覆盖behavior中的属性或方法;多个behavior,靠后的覆盖前的; 同名的数据字段如果是对象进行合并,非对象互相覆盖; 生命周期函数不会相互覆盖,而是在对应触发事件被逐个调用; 同一个behavior被一个组件多次引用,定义的生命周期函数只会被执行一次;
{
"usingComponents": {}
}
// page-common-behavior.js
module.exports = Behavior({
attached: function() {
// 页面创建时执行
console.info('Page loaded!')
},
detached: function() {
// 页面销毁时执行
console.info('Page unloaded!')
}
})
// 页面 A
var pageCommonBehavior = require('./page-common-behavior')
Component({
behaviors: [pageCommonBehavior],
data: { /* ... */ },
methods: { /* ... */ },
})
触发事件: triggerEvent
方法,指定事件名,detail对象和事件选项; 支持冒泡和捕获处理:
内置behavior
Component({
behaviors: ['wx://form-field']
})
关联
relations
包含目标组件路径及其对应选项;
数据监听器
observers
**
通配符 表示监听所有子数据字段的变化; 如’some.field.**'监听前缀;
纯数据字段
纯数据字段是一些不用于界面渲染的 data 字段,可以用于提升页面更新性能。 wxml不会渲染;
Component构造器的options定义段中指定pureDataPattern为一个正则表达式;字段名符合这个正则则成为纯数据字段;
Component({
options: {
pureDataPattern: /^_/ // 指定所有 _ 开头的数据字段为纯数据字段
},
data: {
a: true, // 普通数据字段
_b: true, // 纯数据字段
},
methods: {
myMethod() {
this.data._b // 纯数据字段可以在 this.data 中获取
this.setData({
c: true, // 普通数据字段
_d: true, // 纯数据字段
})
}
}
})
这行会被展示
这行不会被展示
自定义组件模板中的一些节点,对应的自定义组件不是由自定义组件本身确定的,而是自定义组件的调用者确定的;
“selectable”不是任何在 json 文件的 usingComponents 字段中声明的组件,而是一个抽象节点。它需要在 componentGenerics 字段中声明:
{
"componentGenerics": {
"selectable": true
}
}
指定抽象节点为custom-radio; (也需要注册在Components) `generic:xxx="yyy"`值只能是静态值,不能包含数据绑定;抽象节点不适用于动态决定节点名的场景;
{
"usingComponents": {
"custom-radio": "path/to/custom/radio"
}
}
definitionFilter
提供修改自定义组件定义段的能力,用于支持自定义组件扩展; definitionFilter
是一个函数,是被调用时会被注入两个参数,第一个参数是使用该behavior的component/behavior的定义对象;第二个参数是该behavior所使用的behavior的definitionFilter函数列表;
自定义组件扩展详情