写在前面:
- 本文是单纯的个人学习总结和课堂笔记整理,大部分知识点来自“开课吧”线上直播课程,以及官方资料。
- 由于目前仍处于前端学习、入门阶段,因此对于知识的掌握和理解难免会有偏差,因此该文章仅仅是个人记录,请查阅者不要以此作为资料依据。
简介
宿主指的是微信客户端,也就是官方API里的 wx 对象。宿主环境就是微信客户端给小程序提供的一种环境。
宿主环境的主要作用:
- 宿主环境会把所有文件(wxml、wxss、js、json)统统编译到一个js文件里面,然后通过宿主环境的解析,最终才会在微信APP里面展示出我们日常见到的视图。
- 宿主环境可以喂小程序提供微信客户端的一些能力,比如微信扫码,这是普通网页不具备的。
当我们点开一个小程序的时候,宿主环境其实进行了如下操作:
- 我们点开微信小程序
- 微信客户端检索本地缓存中是否有代码包的缓存
- 如果有,则直接加载
- 如果没有,则通过网络请求下载代码包
- 加载好代码包之后,所有代码会被编译成一个js文件
- 微信客户端会根据代码包根目录下的
app.js
内的APP()
构造器创建APP实例 - 在创建APP实例的时候,APP()构造器内定义的onLaunch方法会被微信客户端调用,然后进行登录相关的逻辑
- 这些登录相关的逻辑执行之后,就进入到了微信小程序界面
- 微信小程序根据我们定义好的索引配置,展示对应的页面
接下来,我们一起看一下宿主环境是如何工作的,以及为我们提供了哪些方便的API和开发方式。
渲染层和逻辑层
首先需要知道上述各类文件的运行环境,其中WXML文件和WXSS文件是运行在渲染层上面,其中的JS文件是运行在逻辑层上面。
微信小程序分别使用两个线程单独管理渲染层和逻辑层,也就是说逻辑层和渲染层是异步的,页面渲染和脚本逻辑二者之间不会阻塞。
渲染层使用webview进行渲染,逻辑层使用jscore进行渲染。这两类线程会经过微信客户端(也就是图中的native)进行通信和中转,此外,逻辑层的网络请求也会通过native转发。
因为个小程序会存在多个页面(page),因此渲染层会存在多个webview线程。
小程序中关于渲染层和逻辑层的详细解释,请移步官方文档。
APP构造器
APP构造器是宿主环境提供的一个用来注册程序的APP。
- App()构造器必须是写在根目录内的
app.js
文件里面。 - App是单实例对象。整个项目中只能有一个全局对象。
- App是一个全局对象,就像网页端的window对象。
- 因此,宿主环境提供了
getApp()
方法让我们在全局获取到app实例,可以通过该方法,在其他js
文件中获取到app
内的数据。
- 因此,宿主环境提供了
前台状态和后台状态
- 后台状态
- 用户点击小程序右上角关闭按钮,或手机的 home 键时,会离开小程序,但小程序并不会被销毁,而是进入后台状态。此时,APP构造器参数里的
onHide
方法会被调用。
- 用户点击小程序右上角关闭按钮,或手机的 home 键时,会离开小程序,但小程序并不会被销毁,而是进入后台状态。此时,APP构造器参数里的
- 前台服务
- •前台状态:用户再次小程序时,微信用户端会唤醒后台状态的微信小程序,微信小程序就进入了前台状态,
onShow
方法会被调用。
- •前台状态:用户再次小程序时,微信用户端会唤醒后台状态的微信小程序,微信小程序就进入了前台状态,
备注:onShow
和 onHide
方法必须是由用户操作手动触发的,不可以在代码内通过方法调用。
如下,是App构造器示例:
App({
onLaunch() {
console.log('小程序初始化完成');
// 展示本地存储能力
const logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
// 登录
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
}
})
// 获取用户信息
wx.getSetting({
success: res => {
if (res.authSetting['scope.userInfo']) {
// 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框
wx.getUserInfo({
success: res => {
// 可以将 res 发送给后台解码出 unionId
this.globalData.userInfo = res.userInfo
// 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
// 所以此处加入 callback 以防止这种情况
if (this.userInfoReadyCallback) {
this.userInfoReadyCallback(res)
}
}
})
}
}
})
},
onHide(){
console.log('进入后台状态');
},
onShow(){
console.log('进入前台状态');
},
})
页面构造器
每一个页面的js逻辑都是写在对应的page()构造器里面的。page构造器接收一个object参数,在object里面可以绑定数据,监听页面事件以及生命周期函数。
Page({
data: { text: "This is page data." },
onLoad: function(options) { },
onReady: function() { },
onShow: function() { },
onHide: function() { },
onUnload: function() { },
onPullDownRefresh: function() { },
onReachBottom: function() { },
onShareAppMessage: function () { },
onPageScroll: function() { }
})
上述每一个参数我们一一来讲。
数据绑定
数据绑定已经在上一篇文章中写过了,这里不再赘述。
但是要注意的一点是:
- 用数据驱动视图渲染,也就是修改data时,要用
this.setData()
,不可以直接使用this.data
修改数据,这样不仅无法实现数据驱动视图更新,还会产生数据不一致的错误。 - 由于
setData
是两个线程间的通信,为了提高性能,每次设置的数据不应超过1024KB
。 - 不要把
data
中的任意一项的value
设为undefined
,否则可能会有引起一些不可预料的bug
。
生命周期
主要有如下生命周期:
- 页面初次加载时:
onLoad
。在页面没被销毁之前只会触发1次。 - 页面显示时:
onShow
。从别的页面返回到当前页面时,都会被调用。 - 页面初次渲染完成时:
onReady
。在页面没被销毁前只会触发1次,在逻辑层可以和视图层进行交互。
此外,当用户操作小程序时,还会触发如下生命周期:
-
页面不可见时:
onHide
。wx.navigateTo切换到其他页面、底部tab切换时触发。 -
返回到其它页时:
onUnload
。wx.redirectTo或wx.navigateBack使当前页面会被微信客户端销毁回收时触发。
页面的用户行为
-
下拉刷新
onPullDownRefresh
:监听用户下拉刷新事件,需要在全局或具体页面的json 文件中配置enablePullDownRefresh
为true
。 -
上拉触底
onReachBottom
:监听用户上拉触底事件。可以在app.json
的window
选项中或页面配置page.json
中设置触发距离onReachBottomDistance
。在触发距离内滑动期间,本事件只会被触发一次。 -
页面滚动
onPageScroll
:监听用户滑动页面事件,参数为Object
,包含scrollTop
字段,表示页面在垂直方向已滚动的距离(单位px
)。 -
用户转发
onShareAppMessage
:只有定义了此事件处理函数,右上角菜单才会显示“转发”按钮,在用户点击转发按钮的时候会调用,此事件需要return
一个Object
,包含title
和path
两个字段,用于自定义转发内容
页面用户行为有很多,这里就简单罗列了几个常见,更详细的请移步官方文档查阅。
页面跳转
页面间跳转主要有三类方式:
-
在 app.json 里面用 tabBar 属性设置跳转方式
-
在 wxml 页使用导航组件
<navigator>
跳转页面 -
在 js 中使用 路由API 跳转
tabBar
tabBar 一般是用来设置页面上的导航栏,最多可以设置五个。
"tabBar": {
"selectedColor":"#00acec",
"list": [
{
"selectedIconPath": "images/homeSel.png",
"iconPath": "images/home.png",
"pagePath": "pages/index/index",
"text": "首页"
},
{
"selectedIconPath": "images/heartSel.png",
"iconPath": "images/heart.png",
"pagePath": "pages/logs/logs",
"text": "日志"
}
]
},
navigator
在 navigator
标签里面,通过 url
标签声明跳转路由的相对路径即可。
<navigator url="../03-secPage/03-secPage">去二级页</navigator>
路由API
在page.js文件里面声明好事件方法:
toSecPage(){
wx.navigateTo({
url:'../03-secPage/03-secPage'
})
}
然后在 wxml 文件里面通过行为事件触发:
<button bindtap="toSecPage">js跳转到二级页</button>
自定义组件
一个页面(page)是由很多个标签组成的,而这些标签其实也可以被称为组件(与React Vue一样),宿主环境自带的组件如 text view,有时候并不能满足我们的项目实际需求,因此需要我们自己定义组件。
自定义组件需要按照如下的步骤创建:
- 在主项目下建立components 文件夹,在其中建立floatball 文件夹,在此文件夹上右击“新建 Component”,这样就可以建立出json、wxml、wxss、js 四个文件。
- 组件的
json
中设置"component": true
- 组件的
wxml
、wxss
可以正常写。 - 组件的
js
中的properties
可以接受父组件属性。 - 父组件在调用子组件时要在其
json
文件中设置usingComponents
,如:
"usingComponents": {
"floatball":"/components/floatball/floatball"
}
组件间通信
小程序的组件间通信没有规定必须是像React 那样单项传递数据,因此,有如下两类数据传递方式:
-
父组件向子组件传递数据:属性
-
子组件可以使用 Mustache 语法预设一些模板,当父组件没有传递属性时显示默认值,如果父组件传来了属性值,就显示父组件传来的。
-
// child.js properties: { text:{type:'string',value:'悬浮球'} },
-
<view class="ball">{{text}}</view>
-
如上述声明,默认会显示 ‘’悬浮球‘’ 三个字
-
如果父组件传递进来属性:
<floatball text="组件间通信"></floatball>
-
那么就会显示 “组件间通信”五个字。
-
-
子组件向父组件传递数据:事件
-
父组件通过bind语法绑定上事件后,该事件名会传递到子组件(备注,下面代码里为了方便解释,把事件名和事件值写的不一样,但是实际项目中最好还是写成一样的,省的记错)
// fater.wxml <floatball text="组件间通信" bind:onClickBall="doClickBall"></floatball> <!-- onClickBall 事件名称,会传递到子组件 doClickBall 该事件的处理函数,可以在对应的 js 文件里接收到子组件传递来的数据 -->
-
当子组件接收到该该事件之后,将数据传递到父组件
// child.wxml <view class="ball" bind:handleClickBall="handleClickBall">{{text}}</view>
// child.js methods: { handleClickBall(){ // 第二个object参数就是传递子组件传递给父组件的数据 this.triggerEvent('onClickBall',{tap:false}) } }
-
父组件通过该事件的回调函数接收数据。
// father.js doClickBall(param){ console.log(param); }
-
事件捕捉和事件冒泡
-
事件冒泡:上面我们写的其实就是事件冒泡。
- 书写方式:
bind:clickBall
- 执行顺序:当多层组件嵌套时,会先执行外层的组件,然后一层一层的执行进去,直到要我们需要的那一层
- 书写方式:
-
事件捕捉:在
bind
前面加上前缀capture
即为事件捕捉。- 书写方式;
capture-bind:clickBall
- 执行顺序:优先执行我们想要的那一层,那一层事件触发之后,再去执行外层组件的事件。
- 书写方式;