一、全局框架
小程序的主体由三个文件组成:app.js
、app.json
、app.wxss
小程序主体.png
一个小程序的页面由四个文件组成:.js
、.json
、.wxss
、.wxml
小程序的某个页面.png
-
app.json 是当前小程序的全局配置,定义了小程序的所有页面路径、界面表现、网络超时时间、底部 tab 等,JSON 文件中无法使用注释,加注释会报错。
-
app.js中会产生一个
App
实例,用于监听小程序的生命周期、错误监听、页面不存在监听,整个小程序只有这一个App
实例,全部页面共享,可以通过getApp()
方法获取此全局唯一的App实例。(可以在此实例上保存共用的数据,以便各个界面共用)
// xxx.js
const appInstance = getApp()
console.log(appInstance.globalData)
-
app.wxss,用来描述WXML的组件样式,具有CSS的大部分特性。
-
project.config.json是项目配置文件,可以定义小程序和云开发的目录位置,可以配置个性化选项等等。
二、小程序的渲染层和逻辑层
小程序的运行环境分成渲染层
和逻辑层
,其中 WXML 模板
和 WXSS 样式
工作在渲染层
,JS 脚本
工作在逻辑层
。
小程序的渲染层和逻辑层分别由2个线程管理:
-
渲染层的界面使用了WebView 进行渲染,一个小程序存在多个界面,所以渲染层存在多个WebView线程。
-
逻辑层采用JsCore线程运行JS脚本。
-
这两个线程的通信会经由微信客户端做中转,逻辑层发送网络请求也经由微信客户端转发,小程序的通信模型下图所示。
小程序通信模型
三、 程序与页面
微信打开小程序流程:
-
首先微信在打开小程序之前,会把小程序的代码包下载到本地。
-
然后通过 app.json 的 pages 字段就可以知道你当前小程序的所有页面路径,pages字段的第一个就是小程序的首页。
-
微信吧首页的代码装载进来,通过底层机制,渲染出来首页。
小程序启动流程:
-
小程序启动之后,在
app.js
定义的App
实例的onLaunch
回调会被执行。(整个小程序只有这一个实例,全部页面共享。) -
微信会先根据首页的
.json
文件生成一个界面。(.json
文件定义了导航栏的颜色和文字等信息) -
然后装载首页的
.wxss
和.wxml
文件。 -
最后装载首页的
.js
文件,.js
文件有一个Page实例,小程序会根据Page实例中的data数据和首页的.wxml
文件,一起渲染出最终的结构,这就是你看到的小程序首页的样子。(渲染完成后,Page实例会受到一个onLoad回调)
四、小程序运行机制
五、小程序更新机制
六、事件系统
七、自定义组件
自定义组件
-
已自定义一个上拉加载组件为例,下图是一个自定义的上拉加载组件:
上拉加载组件.png -
组件由四个文件组成:
json、wxml、wxss、js
,要写自定义组件,需要在json
文件中进行自定义组件声明
{
"component": true
}
- 在自定义组件的
js
文件中,需要使用Component
来注册组件,并提供组件的属性定义、内部数据和自定义方法。
// components/load-more/index.js
Component({
/**
* 组件的属性列表
*/
properties: {
hasMore: {
type: Boolean,
value: false
},
// 加载中的显示文本
loadingText: {
type: String,
value: '加载中...'
},
// 加载失败的显示文本
failText: {
type: String,
value: '加载失败, 请点击重试!'
},
// 没有更多后的显示文本, 默认没有则隐藏加载更多控件
finishText: {
type: String,
value: ''
},
// 列表渲染延时, 默认为 500 ms, 我在开发工具中测试列表渲染速度时快时慢, 可根据实际使用中界面复杂度自行调整
// ps 如果能监听setData() 渲染结束的话则可以不需要延时
listRenderingDelay: {
type: Number,
value: 500
}
},
/**
* 组件的初始数据
*/
data: {
showThis: false,
text: '',
showIcon: false,
isLoading: false
},
/**
* 组件的方法列表
*/
methods: {
//加载更多的入口方法, 直接在page中使用时请在onReachBottom() 方法中调用这个方法, 并实现loadMoreListener方法去获取数据
loadMore: function() {
if(!this.properties.hasMore){
console.log('load more finish')
return
}
if(this.data.isLoading) {
console.log('loading ...')
return
}
this.setData({
isLoading: true
})
this.triggerEvent('loadMoreListener')
},
//加载完成, 传入hasMore
loadMoreComplete: function(data) {
var hasMore = data.curPage < data.pageCount && data.pageCount != 1
var text = '', showThis = false, showIcon = false
if (hasMore) {
showIcon = true
showThis = true
text = this.properties.loadingText
} else if (this.properties.finishText.length>0) {
text = this.properties.finishText
showThis = true
}
this.setData({
hasMore: hasMore,
text: text,
showIcon: showIcon,
showThis: showThis
})
//界面渲染延迟, 避免列表还未渲染完成就再次触发 loadMore 方法
setTimeout(function(){
this.setData({
isLoading: false
})
}.bind(this), this.properties.listRenderingDelay)
},
// 加载失败
loadMoreFail: function() {
this.setData({
showIcon: false,
text: this.properties.failText
})
//界面渲染延迟, 避免列表还未渲染完成就再次触发 loadMore 方法
setTimeout(function(){
this.setData({
isLoading: false
})
}.bind(this), this.properties.listRenderingDelay)
},
//点击 loadmore 控件时触发, 只有加载失败时才会进入页面回调方法
clickLoadMore: function() {
if(this.data.text != this.properties.failText) return
this.setData({
showIcon: true,
text: this.properties.loadingText,
isLoading: true
})
this.triggerEvent('clickLoadMore')
}
}
})
- 剩下的就跟下普通的页面一样了,在wxml中编写组件模板,在wxss中编写样式。
注意:在组件wxss中不能使用ID选择器、属性选择器和标签名选择器。
使用自定义组件
-
将组件文件夹复制到自己的小程序中
-
在需要用到此组件的页面A的
json
文件中进行声明,提供此组件的标签名和文件路径:
{
"usingComponents": {
"load-more":"../../components/load-more/index"
}
}
- 把此组件放到页面A的wxml文件的合适的位置
<load-more id="loadMoreView" bindloadMoreListener='loadMoreListener' bindclickLoadMore='clickLoadMore'></load-more>
- 在页面A的js文件中,初始化此组件
/**
* 生命周期函数--监听页面加载
*/
onLoad: function (options) {
//loadMoreView是在Page之外的定义的变量(var loadMoreView)
//拿到loadMoreView后就可以调用此组件里的方法
loadMoreView = this.selectComponent("#loadMoreView")
},
- 在页面A的js文件中,在触底事件中调用组件的loadMore方法
/**
* 页面上拉触底事件的处理函数
*/
onReachBottom: function () {
if (this.data.isLoading == false) {
this.setData({
currentPage: this.data.currentPage + 1
})
loadMoreView.loadMore()
}
},
- 在页面A的js文件中,实现两个回调:
//触底事件时,调用了loadMore函数,loadMore函数会调用loadMoreListener函数
loadMoreListener: function (e) {
this.requestListData(this.data.currentPage)
},
//当加载失败时,会出现加载更多视图,
//点击加载更多视图,会调用此函数
clickLoadMore: function (e) {
this.requestListData(this.data.currentPage)
},
- 每个自定义组件的使用方法都不相同,可以先看作者提供的使用方法和源码,根据自己的实际情况使用或者微调。
八、插件
九、 需要注意的地方
1. wx:key,wx:key值是否唯一,会控制组件是否重新创建
-
当wx:key=""是唯一的值时,数据改变触发渲染层重新渲染时,会校正key唯一的组件,保持他们的状态,提高渲染效率,而不会重新创建。
-
当wx:key没有设置或者不唯一时,数据改变触发渲染时,会重新创建组件,会丢失原先的状态。(如不提供
wx:key
,会报一个warning
, 如果明确知道该列表是静态,或者不必关注其顺序,可以选择忽略。)
设置wx:key唯一,有两种方式:
-
wx:key可以是数组的item的某个属性,需要确保此属性是唯一的字符串或者数字,并且不能动态改变。
-
wx:key可以是保留关键字
*this
,代表在for循环中的item本身,这种表示需要item是一个唯一的字符串或者数字。
2. 下拉刷新
-
通过
.json
中的enablePullDownRefresh
为true来开启用户手动刷新 -
通过API的
wx.startPullDownRefresh()
可以自动触发下拉刷新 -
通过API的
wx.stopPullDownRefresh()
来停止下拉刷新 -
通过
app.json
的window
的backgroundTextStyle
来控制样式,dark和light两种可以选择。 -
通过
.js
中的onPullDownRefresh()
方法,来监控下拉刷新操作
**3. **
网友评论