微信小程序 框架(MINA)
小程序开发框架的目标是通过尽可能简单、高效的方式让开发者可以在微信中开发具有原生APP体验的服务。
框架提供了自己的视图层描述语言WXML和WXSS,以及基于JavaScript的逻辑层框架,并在视图层与逻辑层间提供了数据传输和事件系统,可以让开发者可以方便的聚焦于数据与逻辑上。
主体文件
在每个小程序框架中最关键也是必不可少的文件就是 app.js、app.json、app.wxss 这三个。其中,.js后缀的是脚本文件,.json后缀的文件是配置文件,.wxss后缀的是样式表文件。微信小程序会读取这些文件,并生成小程序实例。
app.js是小程序的脚本代码。我们可以在这个文件中监听并处理小程序的生命周期函数、声明全局变量。调用框架提供的丰富的 API,如本例的同步存储及同步读取本地数据。
//app.js App({ onLaunch: function () { //调用API从本地缓存中获取数据 var logs = wx.getStorageSync('logs') || [] logs.unshift(Date.now()) wx.setStorageSync('logs', logs) }, getUserInfo:function(cb){ var that = this; if(this.globalData.userInfo){ typeof cb == "function" && cb(this.globalData.userInfo) }else{ //调用登录接口 wx.login({ success: function () { wx.getUserInfo({ success: function (res) { that.globalData.userInfo = res.userInfo; typeof cb == "function" && cb(that.globalData.userInfo) } }) } }); } }, globalData:{ userInfo:null } })
app.json 是对整个小程序的全局配置。我们可以在这个文件中配置小程序是由哪些页面组成,配置小程序的窗口背景色,配置导航条样式,配置默认标题。注意该文件不可添加任何注释。
app.json 配置项列表
属性 | 类型 | 必填 | 描述 |
---|---|---|---|
pages | String Array | 是 | 设置页面路径 |
window | Object | 否 | 设置默认页面的窗口表现 |
tabBar | Object | 否 | 设置底部 tab 的表现 |
networkTimeout | Object | 否 | 设置网络超时时间 |
debug | Boolean | 否 | 设置是否开启 debug 模式 |
以下是一个包含了所有配置选项的简单配置app.json :
{ "pages": [ "pages/index/index", "pages/logs/index" ], "window": { "navigationBarTitleText": "Demo" }, "tabBar": { "list": [{ "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/logs/logs", "text": "日志" }] }, "networkTimeout": { "request": 10000, "downloadFile": 10000 }, "debug": true }
app.wxss 是整个小程序的公共样式表。我们可以在页面组件的 class 属性上直接使用 app.wxss 中声明的样式规则。
/**app.wxss**/ .container { height: 100%; display: flex; flex-direction: column; align-items: center; justify-content: space-between; padding: 200rpx 0; box-sizing: border-box; }
响应式的数据绑定
框架的核心是一个响应的数据绑定系统。
整个系统分为两块视图层(View)和逻辑层(App Service)。
框架可以让数据与视图非常简单地保持同步。当做数据修改的时候,只需要在逻辑层修改数据,视图层就会做相应的更新。
MINA 文件结构
MINA程序包含一个描述整体程序的app和多个描述各自页面的page。一个MINA程序主体部分由三个文件组成,必须放在项目的根目录,如下:
文件 | 必需 | 作用 |
---|---|---|
app.js | 是 | 小程序逻辑 |
app.json | 是 | 小程序公共设置 |
app.wxss | 否 | 小程序公共样式表 |
一个MINA页面由四个文件组成,分别是:
文件类型 | 必须 | 作用 |
---|---|---|
wxml | 是 | 页面结构 |
wxss | 否 | 页面样式表 |
json | 否 | 页面配置 |
js | 是 | 页面逻辑 |
逻辑层(App Service)
小程序开发框架的逻辑层是由JavaScript编写。逻辑层将数据进行处理后发送给视图层,同时接受视图层的事件反馈。
注册程序 App()函数
App()函数用来注册一个小程序。接受一个object参数,其指定小程序的生命周期函数等。
object参数说明:
属性 | 类型 | 描述 | 触发时机 |
---|---|---|---|
onLaunch | Function | 生命周期函数--监听小程序初始化 | 当小程序初始化完成时,会触发 onLaunch(全局只触发一次) |
onShow | Function | 生命周期函数--监听小程序显示 | 当小程序启动,或从后台运行进入前台显示,会触发 onShow |
onHide | Function | 生命周期函数--监听小程序隐藏 | 当小程序从前台进入后台,会触发 onHide |
onError | Function | 错误监听函数 | 当小程序发生脚本错误,或者 api 调用失败时,会触发 onError 并带上错误信息 |
onPageNotFound | Function | 页面不存在监听函数 | 当小程序出现要打开的页面不存在的情况,会带上页面信息回调该函数,详见下文 |
其他 | Any | 开发者可以添加任意的函数或数据到 Object 参数中,用 this 可以访问 |
示例代码:
App({ onLaunch: function(options) { // Do something initial when launch. }, onShow: function(options) { // Do something when show. }, onHide: function() { // Do something when hide. }, onError: function(msg) { console.log(msg) }, globalData: 'I am global data' })
getApp()
我们提供了全局的getApp()
函数,可以获取到小程序实例。
// other.js var appInstance = getApp() console.log(appInstance.globalData) // I am global data
注意:
App() 必须在app.js中注册,且不能注册多个。
不要在定义于App() 内的函数中调用getApp() ,使用this就可以拿到app实例。
不要在onLaunch的时候调用getCurrentPage(),此时page还没有生成。
getApp()通过获取实例之后,不要私自调用生命周期函数。
注册页面 Page()函数
Page() 函数用来注册一个页面。接受一个 object 参数,其指定页面的初始数据、生命周期函数、事件处理函数等。
示例代码:
//index.js Page({ data: { // 页面的初始数据 text: 'init data', array: [{msg: '1'}, {msg: '2'}] }, // 生命周期函数 onLoad: function(options) { // 生命周期函数--监听页面加载 // 一个页面只会调用一次,可以在 onLoad 中获取打开当前页面所调用的 query 参数。 }, onReady: function() { // 生命周期函数--监听页面初次渲染完成 // 一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。 }, onShow: function() { // 生命周期函数--监听页面显示 // 每次打开页面都会调用一次。 }, onHide: function() { // 生命周期函数--监听页面隐藏 // 当navigateTo或底部tab切换时调用。 }, onUnload: function() { // 生命周期函数--监听页面卸载 // 当redirectTo或navigateBack的时候调用。 }, // 页面相关事件处理函数 onPullDownRefresh: function() { // 监听用户下拉动作 // 监听用户下拉刷新事件。 // 需要在config的window选项中开启enablePullDownRefresh。 // 当处理完数据刷新后,wx.stopPullDownRefresh可以停止当前页面的下拉刷新。 }, onReachBottom: function() { // 页面上拉触底事件的处理函数 // 监听用户下拉触底事件。 }, onPageScroll: function() { // 页面滚动触发事件的处理函数 // 监听用户滑动页面事件。 }, onShareAppMessage: function () { // 用户点击右上角转发 // 只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮。 // 用户点击转发按钮的时候会调用。 // 此事件需要 return 一个 Object,用于自定义转发内容。 return { title: '自定义转发标题', path: '/page/user?id=123' } }, // 事件处理函数。 // 在渲染层可以在组件中加入事件绑定,当达到触发事件时,就会执行 Page 中定义的事件处理函数。 viewTap: function() { this.setData({ text: 'Set some data for updating view.' }) } })
Page.prototype.route
route 字段可以获取到当前页面的路径。
Page.prototype.setData()
setData 函数用于将数据从逻辑层发送到视图层,同时改变对应的 this.data 的值。
微信小程序 模块化
文件作用域
在JavaScript文件中声明的变量和函数只在该文件中有效;不同的文件中可以声明相同名字的变量和函数,不会互相影响。
通过全局函数getApp()可以获取全局的应用实例,如果需要全局的数据可以在App()中设置,如:
// app.js App({ globalData: 1 }) // b.jsa.js. var localValue = 'b' console.log(getApp().globalData)
模块化
exports是module.exports的一个引用,因此在模块里边随意更改exports的指向会造成未知的错误。所以我们更推荐开发者采用module.exports来暴露模块接口,除非你已经清晰知道这两者的关系。
// common.js function sayHello(name) { console.log('Hello ${name} !') } function sayGoodbye(name) { console.log('Goodbye ${name} !') } module.exports.sayHello = sayHello exports.sayGoodbye = sayGoodbye
在需要使用这些模块的文件中,使用require(path)将公共代码引入(require暂时不支持绝对路径)。
var common = require('common.js') Page({ helloMINA: function() { common.sayHello('MINA') } goodbyeMINA: function() { common.sayGoodbye('MINA') } })
视图层(View) - WXML
MINA的视图层由WXML与WXSS编写。
将逻辑层的数据反应成视图,同时将视图层的事件发送给逻辑层。
WXML(WeiXin Markup language)用于描述页面的结构。
WXSS(WeiXin Style Sheet)用于描述页面的样式。
组件(Component)是视图的基本组成单元。
WXML
WXML(WeiXin Markup Language)是框架设计的一套标签语言,结合基础组件、事件系统,可以构建出页面的结构。
数据绑定
WXML中的动态数据均来自对应Page的data。
内容
<view> {{ message }} </view> Page({ data: { message: 'Hello MINA!' } })
组件属性(需要在双引号之内)
<view id="item-{{id}}"> </view> Page({ data: { id: 0 } })
控制属性(需要在双引号之内)
<view wx:if="{{condition}}"> </view> Page({ data: { condition: true } })
关键字(需要在双引号之内)
<checkbox checked="{{false}}"> </checkbox>
特别注意:不要直接写 checked="false",其计算结果是一个字符串,转成 boolean 类型后代表真值。
运算
可以在 {{}} 内进行简单的运算,支持的有如下几种方式:
三元运算:
<view hidden="{{flag ? true : false}}"> Hidden </view>
算数运算:
<view> {{a + b}} + {{c}} + d </view>
数据路径运算:
<view>{{object.key}} {{array[0]}}</view>
列表渲染 wx:for
在组件上使用 wx:for 控制属性绑定一个数组,即可使用数组中各项的数据重复渲染该组件。
默认数组的当前项的下标变量名默认为 index,数组当前项的变量名默认为 item。
使用 wx:for-item 可以指定数组当前元素的变量名。
使用 wx:for-index 可以指定数组当前下标的变量名。
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName" wx:key="unique"> {{idx}}: {{itemName.message}} </view>
wx:key
如果列表中项目的位置会动态改变或者有新的项目添加到列表中,并且希望列表中的项目保持自己的特征和状态(如 <input/> 中的输入内容,<switch/> 的选中状态),需要使用 wx:key 来指定列表中项目的唯一的标识符。当数据改变触发渲染层重新渲染的时候,会校正带有 key 的组件,框架会确保他们被重新排序,而不是重新创建,以确保使组件保持自身的状态,并且提高列表渲染时的效率。
注意:
当 wx:for 的值为字符串时,会将字符串解析成字符串数组
<view wx:for="array"> {{item}} </view>
等同于
<view wx:for="{{['a','r','r','a','y']}}"> {{item}} </view>
条件渲染 wx:if
在框架中,我们用wx:if="{{condition}}"来判断是否需要渲染该代码块,也可以用wx:elif和wx:else来添加一个else块:
<view wx:if="{{length > 5}}"> 1 </view> <view wx:elif="{{length > 2}}"> 2 </view> <view wx:else> 3 </view>
模板(template)
WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用。
定义模板
使用name属性,作为模板的名字。然后在<template/>
内定义代码片段,如:
<template name="msgItem"> <view> <text> {{index}}: {{msg}} </text> <text> Time: {{time}} </text> </view> </template>
使用模板
使用is属性,声明需要的使用的模板,然后将模板所需要的data传入,如:
<template is="msgItem" data="{{...item}}"/>
is 属性可以使用 Mustache 语法,来动态决定具体需要渲染哪个模板:
<template name="odd"> <view> odd </view> </template> <template name="even"> <view> even </view> </template> <block wx:for="{{[1, 2, 3, 4, 5]}}"> <template is="{{item % 2 == 0 ? 'even' : 'odd'}}"/> </block>
模板的作用域
模板拥有自己的作用域,只能使用data传入的数据。
事件
事件是视图层到逻辑层的通讯方式。
事件可以将用户的行为反馈到逻辑层进行处理。
事件可以绑定在组件上,当达到触发事件,就会执行逻辑层中对应的事件处理函数。
事件对象可以携带额外信息,如id, dataset, touches。
事件的使用方式
- 在组件中绑定一个事件处理函数。
如 bindtap,当用户点击该组件的时候会在该页面对应的Page中找到相应的事件处理函数。
<view id="tapTest" data-hi="WeChat" bindtap="tapName"> Click me! </view>
- 在相应的Page定义中写上相应的事件处理函数,参数是event。
Page({
tapName: function(event) {
console.log(event)
}
})
事件分类
事件分为冒泡事件和非冒泡事件
- 冒泡事件:当一个组件上的事件被触发后,该事件会向父节点传递。
- 非冒泡事件:当一个组件上的事件被触发后,该事件不会向父节点传递。
WXML的冒泡事件列表:
类型 | 触发条件 |
---|---|
touchstart | 手指触摸动作开始 |
touchmove | 手指触摸后移动 |
touchcancel | 手指触摸动作被打断,如来电提醒,弹窗 |
touchend | 手指触摸动作结束 |
tap | 手指触摸后马上离开 |
longtap | 手指触摸后,超过350ms再离开 |
事件绑定
事件绑定的写法同组件的属性,以key、value的形式。
key以bind或catch开头,然后跟上事件的类型,如bindtap, catchtouchstart。
value是一个字符串,需要在对应的Page中定义同名的函数。不然当触发事件的时候会报错。
bind事件绑定不会阻止冒泡事件向上冒泡,catch事件绑定可以阻止冒泡事件向上冒泡。
<view id="outter" bindtap="handleTap1"> outer view <view id="middle" catchtap="handleTap2"> middle view <view id="inner" bindtap="handleTap3"> inner view </view> </view> </view>
事件对象
如无特殊说明,当组件触发事件时,逻辑层绑定该事件的处理函数会收到一个事件对象。
function(e){
console.log(e.target);
console.log(e.detail);
}
BaseEvent基础事件对象属性列表:
属性 | 类型 | 说明 |
---|---|---|
type | String | 事件类型 |
timeStamp | Integer | 该页面打开到触发事件所经过的毫秒数。 |
target | Object | 触发事件的根源组件的一些属性值集合,包括触发事件组件的id,类型,以及dataset自定义属性的集合 |
currentTarget | Object | 触发事件的当前组件,触发当前事件组件的id,类型,以及dataset自定义属性的集合 |
CustomEvent 自定义事件对象属性列表(继承 BaseEvent):
属性 | 类型 | 说明 |
---|---|---|
detail | Object | 自定义事件所携带的数据,如表单组件的提交事件会携带用户的输入,媒体的错误事件会携带错误信息 |
target
触发事件的源组件。
属性 | 类型 | 说明 |
---|---|---|
id | String | 事件源组件的id |
tagName | String | 当前组件的类型 |
dataset | Object | 事件源组件上由data-开头的自定义属性组成的集合 |
currentTarget
事件绑定的当前组件。
属性 | 类型 | 说明 |
---|---|---|
id | String | 当前组件的id |
tagName | String | 当前组件的类型 |
dataset | Object | 当前组件上由data-开头的自定义属性组成的集合 |
dataset
在组件中可以定义数据,这些数据将会通过事件传递给 SERVICE。书写方式:以data-开头,多个单词由连字符-链接,不能有大写(大写会自动转成小写)如 data-element-type,最终在 event.target.dataset 中会将连字符转成驼峰 elementType。
引用
WXML提供两种文件引用方式import和include。
import
import可以在该文件中使用目标文件定义的template,如在item.wxml中定义了一个叫item的template:
<!-- item.wxml --> <template name="item"> <text>{{text}}</text> </template>
在index.wxml中引用了item.wxml,就可以使用item模板:
<import src="item.wxml"/> <template is="item" data="{{text: 'forbar'}}"/>
import的作用域
import 有作用域的概念,即只会 import 目标文件中定义的 template,而不会 import 目标文件 import 的 template。
include
include可以将目标文件除了<template/>的整个代码引入,相当于是拷贝到include位置,如:
<!-- index.wxml --> <include src="header.wxml"/> <view> body </view> <include src="footer.wxml"/>
视图层(View) - WXS
在微信小程序开发中,是不能直接在wxml中写js代码的,因此就有了wxs。
WXS(WeiXin Script)是小程序的一套脚本语言,结合 WXML,可以构建出页面的结构。
WXS 代码可以编写在 wxml 文件中的 <wxs> 标签内,或以 .wxs 为后缀名的文件内。
注意:
- <template> 标签中,只能使用定义该 <template> 的 WXML 文件中定义的 <wxs> 模块
- 切记wxs里面的变量不要使用let,因为wxs是微信的脚本语言,和js还是有区别的
模块
每一个 .wxs 文件和 <wxs> 标签都是一个单独的模块。
每个模块都有自己独立的作用域。即在一个模块里面定义的变量与函数,默认为私有的,对其他模块不可见。
每个 wxs 模块均有一个内置的 module 对象,通过该属性,可以对外共享本模块的私有变量与函数,这样就可以被别的wxs文件或<wxs>标签引入。
内嵌wxs脚本:
wxs代码可编写在wxml文件中,<wxs></wxs>标签内,就像JavaScript代码可以编写在html文件中的<script></script>标签内一样。
wxml文件中的每个<wxs></wxs>标签,必须提供一个module属性,用来指定当前<wxs></wxs>标签的模块名。在单个wxml文件内,建议其值唯一。
<!-- wxml --> <wxs module="localFilter"> var some_msg = "hello world"; function fnFirstText(text) { if (null != text && text.length > 1) { return text.substring(0, 1); } else { return text; } } module.exports = { msg : some_msg, fnFirstText: fnFirstText } </wxs>
在wxml中引入内联的wxs脚本:
<text>{{localFilter.fnFirstText(businessName)}}</text> <text>{{localFilter.msg}}</text>
外联wxs脚本:
wxs代码可以编写在以.wxs为后缀的文件内,就像js一样可以编写在.js文件中一样。
// /pages/tools.wxs var foo = "'hello world' from tools.wxs"; var bar = function (d) { return d; } module.exports = { FOO: foo, bar: bar, }; module.exports.msg = "some msg";
在wxml中引入外联的wxs脚本:
<!--wxml--> <wxs src="../../filter/filter.wxs" module="tools" /> <view> {{tools.msg}} </view> <view> {{tools.FOO}} </view> <view> {{tools.bar(tools.FOO)}} </view>
require 函数
在.wxs模块中引用其他 wxs 文件模块,可以使用 require 函数。
引用的时候,要注意如下几点:
- 只能引用 .wxs 文件模块,且必须使用相对路径。
- wxs 模块均为单例,wxs 模块在第一次被引用时,会自动初始化为单例对象。多个页面,多个地方,多次引用,使用的都是同一个 wxs 模块对象。
- 如果一个 wxs 模块在定义之后,一直没有被引用,则该模块不会被解析与运行。
示例代码:
// /pages/tools.wxs var foo = "'hello world' from tools.wxs"; var bar = function (d) { return d; } module.exports = { FOO: foo, bar: bar, }; module.exports.msg = "some msg"; // /pages/logic.wxs var tools = require("./tools.wxs"); console.log(tools.FOO); console.log(tools.bar("logic.wxs")); console.log(tools.msg);
WXSS
WXSS(WeiXin Style Sheets)是一套样式语言,用于描述WXML的组件样式。
WXSS用来决定WXML的组件应该怎么显示。
样式导入
使用@import语句可以导入外联样式表,@import跟需要导入的外联样式表的相对路径,用;表示语句结束。
示例代码:
/** common.wxss **/ .small-p{ padding:5px; } /** app.wxss **/ @import "common.wxss"; .middle-p { padding:15px; }
内联样式
框架组件上支持使用style、class属性来控制组件的样式。
- style:静态的样式统一写到class中。style接收动态的样式,在运行时会进行解析,请尽量避免将静态的样式写进style中,以免影响渲染速度。
<view style="color:{{color}};" />
- class:用于指定样式规则,其属性值是样式规则中类选择器名(样式类名)的集合,样式类名不需要带上
.
,样式类名之间用空格分隔。
<view class="normal_view" />
全局样式与局部样式
定义在app.wxss中的样式为全局样式,作用于每一个页面。在page的wxss文件中定义的样式为局部样式,只作用在对应的页面,并会覆盖app.wxss中相同的选择器。