因为最近在重新梳理小程序的 runtime 标准,所以单独写一篇文章整理小程序的重要功能
最终目的是构建一棵树,将这所有的信息放到树上,最终得到一个微信的子集
一、json 配置:
- app.json
{
"pages": ["pages/index/index", "pages/logs/index"], // 默认匹配第一个
"window": {
"navigationBarBackgroundColor": "#ffffff", // 顶部导航栏背景色
"navigationBarTextStyle": "black", // 顶部导航栏文字颜色
"navigationBarTitleText": "微信接口功能演示", // 顶部导航栏文字内容
},
"tabBar": {
"list": [
{
"pagePath": "pages/index/index", // 页面路径
"text": "首页", // 下面的小字
"iconPath": "img/news.svg", // 默认图
"selectedIconPath": "img/news-sel.svg" // 选中后的图片
},
{
"pagePath": "pages/logs/logs",
"text": "日志"
}
]
},
"usingComponents": { // 公共组件
"component-tag-name": "path/to/the/custom/component"
},
"useExtendedLib": { // 减少预编译
"kbone": true,
"weui": true
},
"debug": true, // 开启 debug 模式,有用的日志
"version": "v3", // 可以借助这个字段做版本控制
}
以上,是我认为 app.json 中比较重要的内容,我们除了支持这些字段,还要一些小的细节要处理
1. tabbar 的 icon 需要支持 svg 格式,png 位图会失真
2. useExtendedLib 可以做成 module federation,用来减少预编译
3. 增加一个 version 字段,做版本控制
- [page].json
{
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black",
"navigationBarTitleText": "微信接口功能演示",
"enablePullDownRefresh" : "true", // 启动下拉刷新并触发 onPullDownRefresh 回调
"onReachBottomDistance": 50, // 触发 onReachBottom 回调的距离
"usingComponents": { // 页面组件
"component-tag-name": "path/to/the/custom/component"
},
"disableScroll": "true" // 禁用 IOS 自带的回弹和 onPageScroll 方法
}
page 的字段就简单地多了,除了几个 window 的字段会覆盖 app.json,只有 useComponents 字段比较重要
有一些需要注意的:
1. 上面列举的字段,app.json 也支持统一配置
2. 下拉刷新,触底,scroll 三个配置都是针对页面的,需要和 scroll-view 区分开
二、App 接口
- App()
App({
onLaunch (options) {
// app 启动时
},
onShow (options) {
// app 激活时
},
onHide () {
// app 失活时
},
onError (msg) {
// 错误收集
},
globalData: {}, // globaldata
})
- getApp()
const app = getApp()
console.log(app.globalData)
小程序的 App 实例上没有太多东西,但是一些工具组件啥的,还是要挂上来的
三、Page 接口
- Page()
Page({
data: {
count: 0
},
onLoad: function(options) {
// 页面 load
},
onShow: function() {
// 页面 show
},
onReady: function() {
// 页面 ready
},
onHide: function() {
// 页面 hide
},
onUnload: function() {
// 页面 unload
},
onPullDownRefresh: function() {
// 下拉刷新回调
},
onReachBottom: function() {
// 触底回调
},
onShareAppMessage: function () {
// 基本没啥用的分享
},
onPageScroll: function() {
// scrollTo
},
onResize: function() {
// pc 上的 resize
},
onTabItemTap(item) {
// tab 的 tap 回调
console.log(item.index)
console.log(item.pagePath)
console.log(item.text)
},
// 普通事件
viewTap: function() {
this.setData({
text: 'Set some data for updating view.'
})
// 序列化为 json-patch
this.setData({
"a.b.c[0]": 'Set some data for updating view.'
})
}
})
Page 的事件比较多,但主要分为三类:
1. 生命周期 -> 待会总体讲
2. 下拉,触底,scroll 的回调
3. data,setData,普通事件
其中最容易被忽视的是 this.setData 的 json-patch 格式,这个是之后自定义组件的 observer 的基础
- getCurrentPages()
const stack = getCurrentPages()
页面栈
这是一个栈,通过 getCurrentPages 拿到的栈,第一个元素是首页,最后一个元素是当前页
API | 栈行为 |
---|---|
load | 首页入栈 |
wx.navigetTo | 入栈 |
wx.redirectTo | 栈顶的替换 |
wx.navigateBack | 循环出栈 |
wx.switchTab | 清空栈,然后目标页面入栈 |
reload | 清空栈,然后当前页面入栈 |
以上的栈行为和生命周期相关,但总的来说,只要这个页面还在栈中,生命周期走的就是 show 和 hide,如果已经不在栈中了,则走 load 和 unload
这块我们待会梳理
四、Component 接口
- Component()
Component({
behaviors: [],
properties: {
myProperty: { // 属性名
type: String,
value: ''
},
myProperty2: String // 简化的定义方式
},
data: {}, // 私有数据,可用于模板渲染
lifetimes: {
// 生命周期
attached: function () { },
moved: function () { },
detached: function () { },
},
pageLifetimes: {
// 组件所在页面的生命周期
show: function () { },
hide: function () { },
resize: function () { },
},
methods: {
onMyButtonTap: function(){
this.setData({
'A[0].B': 'myPrivateData'
})
},
triggerParentEvent(){
this.triggerEvent('myevent', myEventDetail, myEventOption)
}
}
observers: {
"A[0].B": function(newVal, oldValue) {
console.log(newVal)
}
}
})
Component 比 Page 主要多了 properties 和 observers
props 是单项数据流,它只能从父亲流进来,但是要注意,properties 和 data 共享同一个 template 作用域,原则上两者不能同 key,在微信中 data 会覆盖 props,vue 中会报错
我倾向于 vue 的处理
observers
observers 和以前的 watch 是一样的,但是性能有了很大的提升,我怀疑是使用了 Proxy 做了写时拷贝
值得一提的是,这块是拿 setData 的 json-patch 和 observer 的 json-patch 直接做对比
json-patch 除了提升 observer 的性能,还能提升 setData 的线程传递的性能
- Behavior()
const b = Behavior({
behaviors: [],
properties: {
myBehaviorProperty: {
type: String
}
},
data: {
myBehaviorData: {}
},
attached: function(){},
methods: {
myBehaviorMethod: function(){}
}
})
这玩意的参数和自定义组件是一样的,它一般的合并策略有三种:
1. 队列,如生命周期
-> behavior -> component
-> child -> parent
-> front -> back
2. 合并,比如 data,observer 这种对象
-> component -> behavior
-> parent -> child
-> back -> front
3. 替换,事件,properties
-> 规则同 2
值得一提的是,这里的生命周期的顺序是冒泡的,接下来继续梳理生命周期
三、生命周期
这块需要单独说,因为小程序是双线程的,而且 Page,Component,甚至 Behavior 都和生命周期有关
- Page 的生命周期,这个最简单:
onload:逻辑层加载,入栈
onready:视图层加载完毕,相当于 didmount
onshow:native 端显示 ————————
|
onunload:逻辑层卸载,出栈 | onshow 和 onhide 来回切换
|
onhide:native 端隐藏 ————————
这里有一点比较重要,也就是 onshow/onhide 这俩生命周期,这和 berial 的 mount/unmount 一样
它们会来回切换,在 berial 中我使用一个有状态的队列做到这一点
- Component 的生命周期
微信的 Component 是个纯视图层的实现,它的生命周期本来是 web-component 的生命周期的,所以这事……
attached: 渲染层加载完毕,相当于 didmount
detached: 渲染层卸载完毕,相当于 didunmount
- 父子间生命周期的顺序
在 react 中,父子的生命周期顺序是先潜水再冒泡的
<cmp-a>
<cmp-b>
<cmp-c></cmp-c>
</cmp-b>
</cmp-a>
cmp-a - componentWillMount
cmp-b - componentWillMount
cmp-c - componentWillMount
cmp-c - componentDidMount
cmp-b - componentDidMount
cmp-a - componentDidMount
小程序的顺序是一样的,它的 attached 相当于 componentDidMount,自然也是从子到父
四、wxs
这玩意是在视图层的,我猜应该是模拟了一个 cjs 标准,然后用 new Function 跑的
这个没什么好说的好像……
构建数据结构
基本上就这么多了,把所有东西都整理一遍之后,就可以冥思这个结构长啥样了
首先肯定是一棵树,然后大概长这样:
App
- [ // 这里是个栈
- Page1
- [
- Component1
-[
- Component3
-]
- Component2
- ]
- Page2
- ]
1. 总体结构是个树
2. Pages 是个栈
3. behavior 是个树
4. 生命周期是个队列
有了这个结构,就很容易补 case 了,比如:
export function getCurrentPages(){
reutn app.pageStack.values()
}
一行代码搞定,啊哈哈
经过这一波整理,我对小程序总体架构理解地更好一点了,相当于一个低配版的 vue
具体的实现细节不能透露,但我觉得我可以构建出一个满意的结构了
望天,第一次写这么长的文章,感觉实在凑不出来一万字,饶了我吧,大家都要讨生活鸭……