⼩程序
1 登录
unionid和openid
了解⼩程序登陆之前,我们写了解下⼩程序/公众号登录
涉及到两个最关键的⽤ 户标识:
OpenId 是⼀个⽤户对于⼀个⼩程序/公众号的标识,开发者可以通过这个标识识别出⽤ 户。
UnionId 是⼀个⽤户对于同主体微信⼩程序/公众号/ APP 的标识,开发者需要在微信 开放平台下绑定相同账号的主体。开发者可通过 UnionId ,实现多个⼩程序、公众号、甚 ⾄APP 之间的数据互通了。
关键Api
wx.login 官⽅提供的登录能⼒
wx.checkSession 校验⽤户当前的 session_key 是否有效
wx.authorize 提前向⽤户发起授权请求
wx.getUserInfo 获取⽤户基本信息
登录流程设计
利⽤现有登录体系
直接复⽤现有系统的登录体系,只需要在⼩程序端设计⽤户名,
密码/验证码输 ⼊⻚⾯,便可以简便的实现登录,
只需要保持良好的⽤户体验即可
利⽤ OpenId 创建⽤户体系
OpenId 是⼀个⼩程序对于⼀个⽤户的标识,利⽤这⼀点我们可以轻松的实 现⼀套基于⼩程序的⽤户体系,值得⼀提的是这种⽤户体系对⽤户的打扰最 低,可以实现静默登录。具体步骤如下
⼩程序客户端通过 wx.login 获取 code传递 code 向服务端,服务端拿到 code 调⽤微信登录凭证校验接⼝,微信服务器返回openid 和会话密钥session_key ,此时开发者服务端便可以利⽤ openid ⽣成⽤户 ⼊库,再向⼩程序客户端返回⾃定义登录态。
⼩程序客户端缓存 (通过 storage )⾃定义登录态( token ),后续调⽤接⼝时携带该 登录态作为⽤户身份标识即可
利⽤ Unionid 创建⽤户体系
如果想实现多个⼩程序,公众号,已有登录系统的数据互通,可以通过获取到 ⽤户 unionid 的⽅式建⽴⽤户体系。因为 unionid 在同⼀开放平台下的所所有应⽤都是相同的,通过 unionid 建⽴的⽤户体系即可实现全平台数据的互通,更⽅便的接⼊原有的功能,那如何获取 unionid 呢,有以下两种⽅式
如果户关注了某个相同主体公众号,或曾经在某个相同主体 App 、公众号上进⾏过微信登 录授权,通过 wx.login 可以直接获取 到 unionid
结合 wx.getUserInfo 和 这两种⽅式引导⽤户主动授权,主动授权后通过返回的信息和服务端交互 (这⾥有⼀步需要服务端解 密数据的过程,很简单,微信提供了示例代码) 即可拿到 unionid 建⽴⽤户体系, 然后 由服务端返回登录态,本地记录即可实现登录,附上微信提供的最佳实践
调⽤ wx.login 获取 code ,然后从微信后端换取session_key ,⽤于解密getUserInfo 返回的敏感数据
使⽤ wx.getSetting 获取⽤户的授权情况
如果⽤户已经授权,直接调⽤ API wx.getUserInfo 获取⽤户最新的信息;
⽤户未授权,在界⾯中显示⼀个按钮提示⽤户登⼊,当⽤户点击并授权后就获取到⽤ 户的最新信息
获取到⽤户数据后可以进⾏展示或者发送给⾃⼰的后端
注意事项
需要获取 unionid 形式的登录体系,在以前(18年4⽉之前)是通过以下这种⽅ 式来实现,但后续微信做了调整(因为⼀进⼊⼩程序,主动弹起各种授权弹窗的这 种形式,⽐较容易导致⽤户流失),调整为必须使⽤按钮引导⽤户主动授权的⽅ 式,这次调整对开发者影响较⼤,开发者需要注意遵守微信的规则,并及时和业务 ⽅沟通业务形式,不要存在侥幸⼼理,以防造成⼩程序不过审等情况.
wx.login(获取code) ===> wx.getUserInfo(⽤户授权) ===> 获取 unionid
因为⼩程序不存在 cookie 的概念, 登录态必须缓存在本地,因此强烈建议为登录态设置过期时间 值得⼀提的是如果需要⽀持⻛控安全校验,多平台登录等功能,可能需要加⼊⼀些公共参数,例如 platform , channel , deviceParam 等参数。在和服务端确 定⽅案时,作为前端同学应该及时提出这些合理的建议,设计合理的系统。
openid , unionid 不要在接⼝中明⽂传输,这是⼀种危险的⾏为,同时也很
不专业.
2 图⽚导出
这是⼀种常⻅的引流⽅式,⼀般同时会在图⽚中附加⼀个⼩程序⼆维码。
基本原理
借助 canvas 元素,将需要导出的样式⾸先在 canvas 画布上绘制出来 ( api 基本和h5 保持⼀致,但有轻微差异,使⽤时注意即可。
借助微信提供的 canvasToTempFilePath 导出图⽚,最后再使⽤saveImageToPhotosAlbum (需要授权)保存图⽚到本地。
如何优雅实现
绘制出需要的样式这⼀步是省略不掉的。但是我们可以封装⼀个绘制库,包含常⻅图形的绘制,例如矩形,圆⻆矩形,圆, 扇形,三⻆形, ⽂字,图⽚减少绘制代码,只需要提炼 出样式信息,便可以轻松的绘制,最后导出图⽚存⼊相册。笔者觉得以下这种⽅式绘制更为优雅清晰⼀些,其实也可以使⽤加⼊⼀个type参数来指定绘制类型,传⼊的⼀个是样式 数组,实现绘制。
结合上⼀步的实现,如果对于同⼀类型的卡⽚有多次导出需求的场景,也可以使⽤⾃定义 组件的⽅式,封装同⼀类型的卡⽚为⼀个通⽤组件,在需要导出图⽚功能的地⽅,引⼊该 组件即可。
class CanvasKit {
constructor() {
}
drawImg(option = {}) {
...
return this
}
drawRect(option = {}) {
return this
}
drawText(option = {}) {
...
return this
}
static exportImg(option = {}) {
...
} }
let drawer = new CanvasKit('canvasId').drawImg(styleObj1).drawText(styleOb
drawer.exportImg()
注意事项
⼩程序中⽆法绘制⽹络图⽚到 canvas 上,需要通过 downLoadFile 先下载图⽚ 到本地临时⽂件才可以绘制
通常需要绘制⼆维码到导出的图⽚上,有⼀种⽅式导出⼆维码时,需要携带的参数 必须做编码,⽽且有具体的⻓度( 32 可⻅字符)限制,可以借助服务端⽣成短链接 的⽅式来解决。
3 数据统计
数据统计作为⽬前⼀种常⽤的分析⽤户⾏为的⽅式,⼩程序端也是必不可少 的。⼩程序采取的曝光,点击数据埋点其实和h5原理是⼀样的。但是埋点作为⼀个和业务逻辑不相关的需求,我们如果在每⼀个点击事件,每⼀个⽣命周期加⼊各种埋点代码,则会⼲扰正常的业务逻辑,和使代码变的臃肿,笔者提供 以下⼏种思路来解决数据埋点。
⼩程序的代码结构是,每⼀个 Page 中都有⼀个 Page ⽅法,接受⼀个包含 ⽣命周期函数,数据的 业务逻辑对象 包装这层数据,借助⼩程序的底层逻辑实现⻚⾯的业务逻辑。通过这个我们可以想到思路,对 Page 进⾏⼀次包装, 篡改它的⽣命周期和点击事件,混⼊埋点代码,不⼲扰业务逻辑,只要做⼀些 简单的配置即可埋点,简单的代码实现如下:
// 代码仅供理解思路
page = function(params) {
let keys = params.keys()
keys.forEach(v => {
if (v === 'onLoad') {
params[v] = function(options) {
stat() //曝光埋点代码
params[v].call(this, options)
}
}
else if (v.includes('click')) {
params[v] = funciton(event) {
let data = event.dataset.config
stat(data) // 点击埋点
param[v].call(this)
}
}
}) }
这种思路不光适⽤于埋点,也可以⽤来作全局异常处理,请求的统⼀处理等场景。
分析接⼝
对于特殊的⼀些业务,我们可以采取 接⼝埋点,什么叫接⼝埋点呢?很多情况下,我们有的 api 并不是多处调⽤的,只会在某⼀个特定的⻚⾯调⽤,通过这个思路我们可以分析出,该接⼝被请求,则这个⾏为被触发了,则完全可以通过服务端⽇志得出埋点数据,但是这种⽅式局限性较⼤,⽽且属于分析结果 得出过程,可能存在误差,但可以作为⼀种思路了解⼀下。
微信本身提供的数据分析能⼒,微信本身提供了常规分析和⾃定义分析
两种数据分析⽅式,在⼩程序后台配置即可。
借助⼩程序数据助⼿这款⼩程序可以很 ⽅便的查看。
4 ⼯程化
⼯程化做什么
⽬前的前端开发过程,⼯程化是必不可少的⼀环,
那⼩程序⼯程化都需要做些什么呢,
先看下⽬前⼩程序开发当中存在哪些问题需要解决:
不⽀持 css 预编译器,作为⼀种主流的 css 解决⽅案,不论是 less , sass , stylus都可以提升 css 效率 不⽀持引⼊npm包 (这⼀条,从微信公开课中听闻,微信准备⽀持) 不⽀持 ES7 等后续的 js 特性,好⽤的 async await 等特性都⽆法使⽤ 不⽀持引⼊外部字体⽂件,只⽀持 base64
没有 eslint 等代码检查⼯具。
⽅案选型
对于⽬前常⽤的⼯程化⽅案, webpack , rollup , parcel 等来看,
都常 ⽤与单⻚应⽤的打包和处理,⽽⼩程序天⽣是 “多⻚应⽤”
并且存在⼀些特定的配置。根据要解决的问题来看,
⽆⾮是⽂件的编译,修改,拷⻉这些处理, 对于这些需求,
我们想到基于流的 gulp ⾮常的适合处理,
并且相对webpack 配置多⻚
应⽤更加简单。所以⼩程序⼯程化⽅案推荐使⽤ gulp。
具体开发思路
通过 gulp 的 task 实现:
实时编译 less ⽂件⾄相应⽬录 。
引⼊⽀持 async , await 的运⾏时⽂件 。
编译字体⽂件为 base64 并⽣成相应 css ⽂件,⽅便使⽤。
依赖分析哪些地⽅引⽤了 npm 包,将 npm 包打成⼀个⽂件,拷⻉⾄相应⽬录。
检查代码规范。
5 ⼩程序架构
微信⼩程序的框架包含两部分 View 视图层、 App Service 逻辑层。
View 层⽤来渲染⻚⾯结构, AppService 层⽤来逻辑处理、数据请求、接⼝调⽤。
它们在两个线程⾥运⾏。
视图层和逻辑层通过系统层的 JSBridage 进⾏通信,
逻辑层把数据变化通知 到视图层,触发视图层⻚⾯更新,
视图层把触发的事件通知到逻辑层进⾏业务处理
视图层使⽤ WebView 渲染, iOS 中使⽤⾃带 WKWebView ,在 Android 使⽤腾讯的x5 内核(基于 Blink )运⾏。
逻辑层使⽤在 iOS 中使⽤⾃带的 JSCore 运⾏,在 Android 中使⽤腾讯的 x5 内核 (基于 Blink )运⾏。 开发⼯具使⽤ nw.js 同时提供了视图层和逻辑层的运⾏环境。
6 WXML && WXSS
WXML
⽀持数据绑定 。
⽀持逻辑算术、运算。
⽀持模板、引⽤ ⽀持添加事件( bindtap )。
Wxml 编译器: Wcc 把 Wxml ⽂件 转为 JS。
执⾏⽅式: Wcc index.wxml
使⽤ Virtual DOM ,进⾏局部更新。
WXSS
wxss编译器: wcsc 把 wxss ⽂件转化为 js
执⾏⽅式: wcsc index.wxss
尺⼨单位 rpx
rpx(responsive pixel ): 可以根据屏幕宽度进⾏⾃适应。规定屏幕宽为750rpx 。公式:
const dsWidth = 750
export const screenHeightOfRpx = function () {
return 750 / env.screenWidth * env.screenHeight
}
export const rpxToPx = function (rpx) {
return env.screenWidth / 750 * rpx
}
export const pxToRpx = function (px) {
return 750 / env.screenWidth * px
}
样式导⼊
使⽤ @import 语句可以导⼊外联样式表, @import 后跟需要导⼊的外联样 式表的相对路径,⽤ ; 表示语句结束。
内联样式静态的样式统⼀写到 class 中。 style 接收动态的样式,在运⾏时会进⾏解析,请尽量避免将静态的样式写进 style中,以免影响渲染速度
全局样式与局部样式
定义在 app.wxss 中的样式为全局样式,作⽤于每⼀个⻚⾯。在 page 的wxss ⽂件中定义的样式为局部样式,只作⽤在对应的⻚⾯,并会覆盖app.wxss 中相同的选择器 。
7 ⼩程序的问题
⼩程序仍然使⽤ WebView 渲染,并⾮原⽣渲染。(部分原⽣) 服务端接⼝返回的头⽆法执⾏,⽐如: Set-Cookie 。
依赖浏览器环境的 JS 库不能使⽤。
不能使⽤ npm ,但是可以⾃搭构建⼯具或者使⽤ mpvue 。(未来官⽅有计划⽀持)。
不能使⽤ ES7 ,可以⾃⼰⽤ babel+webpack ⾃搭或者使⽤ mpvue 。 不⽀持使⽤⾃⼰的字体(未来官⽅计划⽀持)。
可以⽤ base64 的⽅式来使⽤ iconfont 。
⼩程序不能发朋友圈(可以通过保存图⽚到本地,发图⽚到朋友前。⼆维码可以使⽤B接⼝)。
获取⼆维码/⼩程序接⼝的限制
程序推送只能使⽤“服务通知” ⽽且需要⽤户主动触发提交 formId , formId 只有7天有 效期。(现在的做法是在每个⻚⾯都放⼊ form 并且隐藏以此获取更多的 formId 。后端 使⽤原则为:优先使⽤有效期最短的)。
⼩程序⼤⼩限制 2M,分包总计不超过 8M 。
转发(分享)⼩程序不能拿到成功结果,原来可以。链接(⼩游戏造的孽)。
拿到相同的 unionId 必须绑在同⼀个开放平台下。开放平台绑定限制:
50 个移动应⽤
10 个⽹站
50 个同主体公众号
5 个不同主体公众号
50 个同主体⼩程序
5 个不同主体⼩程序
公众号关联⼩程序
所有公众号都可以关联⼩程序。
⼀个公众号可关联10个同主体的⼩程序,3个不同主体的⼩程序。
⼀个⼩程序可关联500个公众号。
公众号⼀个⽉可新增关联⼩程序13次,⼩程序⼀个⽉可新增关联500次。
⼀个公众号关联的10个同主体⼩程序和3个⾮同主体⼩程序可以互相跳转 品牌搜索不⽀持⾦融、医疗 。
⼩程序授权需要⽤户主动点击
⼩程序不提供测试 access_token
安卓系统下,⼩程序授权获取⽤户信息之后,删除⼩程序再重新获取,并重新授权,得到 旧签名,导致第⼀次授权失败
开发者⼯具上,授权获取⽤户信息之后,如果清缓存选择全部清除,则即使使⽤了wx.checkSession ,并且在 session_key 有效期内,授权获取⽤户信息也会得到新的
session_key
8 授权获取⽤户信息流程
session_key 有有效期,有效期并没有被告知开发者,只知道⽤户越频繁使⽤⼩程序,session_key 有效期越⻓。
在调⽤ wx.login 时会直接更新 session_key ,导致旧session_key 失效 ⼩程序内先调⽤ wx.checkSession 检查登录态,并保证没有过期的 session_key 不会 被更新,再调⽤ wx.login 获取 code 。接着⽤户授权⼩程序获取⽤户信息,⼩程序拿到加密后的⽤户数据,把加密数据和 code 传给后端服务。后端通过 code 拿到session_key 并解密数据,将解密后的⽤户信息返回给⼩程序。
⾯试题:先授权获取⽤户信息再 login 会发⽣什么?
⽤户授权时,开放平台使⽤旧的 session_key 对⽤户信息进⾏加密。调⽤ wx.login重新登录,会刷新 session_key ,这时后端服务从开放平台获取到新 session_key , 但是⽆法对⽼ session_key 加密过的数据解密,⽤户信息获取失败 在⽤户信息授权之前先调⽤ wx.checkSession 呢? wx.checkSession 检查登录态,并 且保证 wx.login 不会刷新session_key ,从⽽让后端服务正确解密数据。但是这⾥存在⼀个问题,如果⼩程序较⻓时间不⽤导致 session_key 过期,则 wx.login 必定会重 新⽣成 session_key ,从⽽再⼀次导致⽤户信息解密失败。
9 性能优化
我们知道 view 部分是运⾏在 webview 上的,
所以前端领域的⼤多数优化⽅式都有⽤。
加载优化
代码包的⼤⼩是最直接影响⼩程序加载启动速度的因素。
代码包越⼤不仅下载 速度时间⻓,业务代码注⼊时间也会变⻓。
所以最好的优化⽅式就是减少代码 包的⼤⼩。
⼩程序加载的三个阶段的表示
优化⽅式
代码压缩。
及时清理⽆⽤代码和资源⽂件。
减少代码包中的图⽚等资源⽂件的⼤⼩和数量。
分包加载。
⾸屏加载的体验优化建议
提前请求: 异步数据请求不需要等待⻚⾯渲染完成。
利⽤缓存: 利⽤ storage API 对异步请求数据进⾏缓存,⼆次启动时先利⽤缓存数据渲染⻚⾯,在进⾏后台更新。
避免⽩屏:先展示⻚⾯⻣架⻚和基础内容。
及时反馈:即时地对需要⽤户等待的交互操作给出反馈,避免⽤户以为⼩程序⽆响应。
使⽤分包加载优化
在构建⼩程序分包项⽬时,构建会输出⼀个或多个功能的分包,其中每个分包⼩程序必定 含有⼀个主包,所谓的主包,即放置默认启动⻚⾯/ TabBar ⻚⾯,以及⼀些所有分包都需 ⽤到公共资源/ JS 脚本,⽽分包则是根据开发者的配置进⾏划分。
在⼩程序启动时,默认会下载主包并启动主包内⻚⾯,如果⽤户需要打开分包内某个⻚ ⾯,客户端会把对应分包下载下来,下载完成后再进⾏展示。
优点:
对开发者⽽⾔,能使⼩程序有更⼤的代码体积,承载更多的功能与服务。
对⽤户⽽⾔,可以更快地打开⼩程序,同时在不影响启动速度前提下使⽤更多功能。
整个⼩程序所有分包⼤⼩不超过 8M
单个分包/主包⼤⼩不能超过 2M
原⽣分包加载的配置 假设⽀持分包的⼩程序⽬录结构如下
├── app.js
├── app.json
├── app.wxss
├── packageA
│ └── pages
│ ├── cat
│ └── dog
├── packageB
│ └── pages
│ ├── apple
│ └── banana
├── pages
│ ├── index
│ └── logs
└── utils
开发者通过在 app.json subPackages 字段声明项⽬分包结构。
{
"pages":[
"pages/index",
"pages/logs"
],
"subPackages": [
{
"root": "packageA",
"pages": [
"pages/cat",
"pages/dog"
]
}, {
"root": "packageB",
"pages": [
"pages/apple",
"pages/banana"
]
}
]
分包原则
声明 subPackages 后,将按 subPackages 配置路径进⾏打包, subPackages 配置路 径外的⽬录将被打包到 app (主包) 中。
app (主包)也可以有⾃⼰的 pages (即最外层的 pages 字段。
subPackage 的根⽬录不能是另外⼀个 subPackage 内的⼦⽬录 。
⾸⻚的 TAB ⻚⾯必须在 app (主包)内。
引⽤原则
packageA ⽆法 require packageB JS ⽂件,但可以 require app 、⾃⼰ package 内的 JS` ⽂件。
packageA ⽆法 import packageB 的 template ,但可以 require app 、⾃⼰ package 内的 template`
packageA ⽆法使⽤ packageB 的资源,但可以使⽤ app 、⾃⼰ package` 内的资源。
官⽅即将推出 分包预加载(截止目前还没推出)
独⽴分包
渲染性能优化
每次 setData 的调⽤都是⼀次进程间通信过程,通信开销与 setData 的数据量正相关。
setData 会引发视图层⻚⾯内容的更新,这⼀耗时操作⼀定时间中会阻塞⽤户交互。
setData 是⼩程序开发使⽤最频繁,也是最容易引发性能问题的。
避免不当使⽤ setData
使⽤ data 在⽅法间共享数据,可能增加 setData 传输的数据量。。 data 应仅包括 与⻚⾯渲染相关的数据。
使⽤ setData 传输⼤量数据,通讯耗时与数据正相关,⻚⾯更新延迟可能造成⻚⾯更新 开销增加。仅传输⻚⾯中发⽣变化的数据,使⽤ setData 的特殊 key 实现局部更新。 短时间内频繁调⽤ setData ,操作卡顿,交互延迟,阻塞通信,⻚⾯渲染延迟。避免不必要的 setData ,对连续的 setData 调⽤进⾏合并。
在后台⻚⾯进⾏ setData ,抢占前台⻚⾯的渲染资源。⻚⾯切⼊后台后的 setData 调 ⽤,延迟到⻚⾯重新展示时执⾏。
避免不当使⽤onPageScroll
只在有必要的时候监听 pageScroll 事件。不监听,则不会派发。
避免在 onPageScroll 中执⾏复杂逻辑。
避免在 onPageScroll 中频繁调⽤ setData
避免滑动时频繁查询节点信息( SelectQuery )⽤以判断是否显示,部分场景建议使⽤节 点布局橡胶状态监听( inersectionObserver )替代。
使⽤⾃定义组件
在需要频繁更新的场景下,⾃定义组件的更新只在组件内部进⾏,
不受⻚⾯其 他部分内容复杂性影响。
10 wepy vs mpvue
相⽐传统的⼩程序框架,这个⼀直是我们作为资深开发者⽐较期望去解决的, 在 Web 开发中,随着 Flux 、 Redu x、 Vuex 等多个数据流⼯具出现, 我们也期望在业务复杂的⼩程序中使⽤。
WePY 默认⽀持 Redux ,在脚⼿架⽣成项⽬的时候可以内置
Mpvue 作为 Vue 的移植版本,当然⽀持 Vuex ,同样在脚⼿架⽣成项⽬的时候可以内置。
组件化
WePY 类似 Vue 实现了单⽂件组件,最⼤的差别是⽂件后缀 .wpy ,只是写法上会有差异。
export default class Index extends wepy.page {}
Mpvue 作为 Vue 的移植版本,⽀持单⽂件组件, template 、 script 和 style都在⼀个 .vue ⽂件中,和 vue 的写法类似,所以对 Vue 开发熟悉的同学会⽐较适应。现在主流前端框架:Anglar,react,Vue,都可以了解一下。
⼯程化
所有的⼩程序开发依赖官⽅提供的开发者⼯具。开发者⼯具简单直观,对调试⼩程序很有帮助,现在也⽀持腾讯云(⽬前我们还没有使⽤,但是对新的⼀些 开发者还是有帮助的),可以申请测试报告查看⼩程序在真实的移动设备上运 ⾏性能和运⾏效果,但是它本身没有类似前端⼯程化中的概念和⼯具。
wepy 内置了构建,通过 wepy init 命令初始化项⽬,⼤致流程如下:
wepy-cli 会判断模版是在远程仓库还是在本地,如果在本地则会⽴即跳到第 3 步, 反之继续进⾏。
会从远程仓库下载模版,并保存到本地。
询问开发者 Project name 等问题,依据开发者的回答,创建项⽬。
mpvue 沿⽤了 vue 中推崇的 webpack 作为构建⼯具,但同时提供了⼀些⾃⼰的插件 以及配置⽂件的⼀些修改,⽐如 :
不再需要 html-webpack-plugin
基于 webpack-dev-middleware 修改成 webpack-dev-middleware-hard-disk
最⼤的变化是基于 webpack-loader 修改成 mpvue-loader
但是配置⽅式还是类似,分环境配置⽂件,最终都会编译成⼩程序⽀持的⽬录结构和⽂件后缀。
11 mpvue
mpvue
Vue.js ⼩程序版, fork ⾃ vuejs/vue@2.4.1 ,保留了 vue runtime 能⼒, 添加了⼩程序平台的⽀持。 mpvue 是⼀个使⽤ Vue.js 开发⼩程序的前端框架。
框架基于 Vue.js 核⼼, mpvue 修改了 Vue.js 的 runtime 和 compiler 实 现,使其可以运⾏在⼩程序环境中,从⽽为⼩程序开发引⼊了整套 Vue.js 开发体验。
框架原理
两个⼤⽅向:
通过 mpvue 提供 mp 的 runtime 适配⼩程序
通过 mpvue-loader 产出微信⼩程序所需要的⽂件结构和模块内容。
七个具体问题:
要了解 mpvue 原理必然要了解 Vue 原理,这是⼤前提:
现在假设您对 Vue 原理有个⼤概的了解。那么接下来会有几个问题请带入思考。(Vue不熟练的同学可以先收藏起来,以后再看哈)
由于 Vue 使⽤了 Virtual DOM ,所以 Virtual DOM 可以在任何⽀持 JavaScript语⾔的平台上操作,譬如说⽬前 Vue ⽀持浏览器平台或 weex ,也可以是 mp (⼩程 序)。那么最后 Virtual DOM 如何映射到真实的 DOM 节点上呢? vue 为平台做了⼀层适配层,浏览器平台⻅ runtime/node-ops.js 、 weex 平台⻅ runtime/nodeops.js ,⼩程序⻅ runtime/node-ops.js 。不同平台之间通过适配层对外提供相同的接 ⼝, Virtual DOM 进⾏操作 Real DOM 节点的时候,只需要调⽤这些适配层的接⼝即可,⽽内部实现则不需要关⼼,它会根据平台的改变⽽改变 所以思路肯定是往增加⼀个 mp 平台的 runtime ⽅向⾛。但问题是⼩程序不能操作DOM ,所以 mp 下的 node-ops.js ⾥⾯的实现都是直接 return obj
新 Virtual DOM 和旧 Virtual DOM 之间需要做⼀个 patch ,找出 diff 。 patch完了之后的 diff 怎么更新视图,也就是如何给这些 DOM 加⼊ attr 、 class 、style 等 DOM 属性呢? Vue 中有 nextTick 的概念⽤以更新视图, mpvue 这块对于 ⼩程序的 setData 应该怎么处理呢? 另外个问题在于⼩程序的 Virtual DOM 怎么⽣成?也就是怎么将 template 编译成 render function 。这当中还涉及到运⾏时-编译器-vs-只包含运⾏时,显然如果要提⾼ 性能、减少包⼤⼩、输出 wxml 、 mpvue 也要提供预编译的能⼒。因为要预输出 wxml 且没法动态改变 DOM ,所以动态组件,⾃定义 render ,和 <script type="text/xtemplate"> 字符串模版等都不⽀持
另外还有⼀些其他问题,最后总结⼀下
1.如何预编译⽣成 render function 2.如何预编译⽣成 wxml , wxss , wxs 3.如何 p atch 出 diff 4.如何更新视图 5.如何建⽴⼩程序事件代理机制,在事件代理函数中触发与之对应的 vue 组件事件响应。 6.如何建⽴ vue 实例与⼩程序 Page 实例关联。 7.如何建⽴⼩程序和 vue ⽣命周期映射关系,能在⼩程序⽣命周期中触发 vue ⽣命周期。
platform/mp 的⽬录结构
├── compiler //解决问题1,mpvue-template-compiler源码部分
├── runtime //解决问题3 4 5 6 7
├── util //⼯具⽅法
├── entry-compiler.js //mpvue-template-compiler的⼊⼝。package.json相关命令会⾃
├── entry-runtime.js //对外提供Vue对象,当然是mpvue
└── join-code-in-build.js //编译出SDK时的修复
mpvue-loader
mpvue-loader 是 vue-loader 的⼀个扩展延伸版,类似于超集的关系, 除了 vue-loader 本身所具备的能⼒之外,它还会利⽤ mpvue-templatecompiler ⽣成 render function
它会从 webpack 的配置中的 entry 开始,分析依赖模块,并分别打包。在 entry 中
app 属性及其内容会被打包为微信⼩程序所需要的 app.js/app.json/app.wxss ,其 余的会⽣成对应的⻚⾯ page.js / page.json / page.wxml / page.wxss ,如示例的 entry 将会⽣成如 下这些⽂件,⽂件内容下⽂慢慢讲来:
// webpack.config.js
{
// ...
entry: {
app: resolve('./src/main.js'), // app 字段被识别为 app
index: resolve('./src/pages/index/main.js'), // 其余字段被识别为 pag
'news/home': resolve('./src/pages/news/home/index.js')
} }
// 产出⽂件的结构 .
├── app.js
├── app.json
├──· app.wxss
├── components
│ ├── card$74bfae61.wxml
│ ├── index$023eef02.wxml
│ └── news$0699930b.wxml
├── news
│ ├── home.js
│ ├── home.wxml
│ └── home.wxss
├── pages
│ └── index
│ ├── index.js
│ ├── index.wxml
│ └── index.wxss
└── static
├── css
│ ├── app.wxss
│ ├── index.wxss
│ └── news
│ └── home.wxss
└── js
├── app.js
├── index.js
├── manifest.js
├── news
│ └── home.js
└── vendor.js
wxml 每⼀个 .vue 的组件都会被⽣成为⼀个 wxml 规范的 template , 然后通过 wxml 规范的 import 语法来达到⼀个复⽤,同时组件如果涉及到props 的 data 数据,我们也会做相应的处理,举个实际的例⼦:
<template>
<div class="my-component" @click="test">
<h1>{{msg}}</h1>
<other-component :msg="msg"></other-component>
</div>
</template> <script>
import otherComponent from './otherComponent.vue'
export default {
components: { otherComponent },
data () {
return { msg: 'Hello Vue.js!' }
},
methods: {
test() {}
} }
</script>
这样⼀个 Vue 的组件的模版部分会⽣成相应的 wxml
<import src="components/other-component$hash.wxml" />
<template name="component$hash">
<view class="my-component" bindtap="handleProxy">
<view class="_h1">{{msg}}</view>
<template is="other-component$hash" wx:if="{{ $c[0] }}" data="{{ ..
</view>
</template>
可能已经注意到了 other-component(:msg=“msg”) 被转化成了 。 mpvue在运⾏时会从根组件开始把所有的组件实例数据合并成⼀个树形的数据,然后通过 setData 到 appData , $c 是 $children 的缩写。⾄于那个 0 则 是我们的 compiler 处理过后的⼀个标记,会为每⼀个⼦组件打⼀个特定的 不重复的标记。 树形数据结构如下。
// 这⼉数据结构是⼀个数组,index 是动态的 {
$child: {
'0'{
// ... root data
$child: {
'0': {
// ... data
msg: 'Hello Vue.js!',
$child: {
// ...data
}
}
}
}
} }
wxss
这个部分的处理同 web 的处理差异不⼤,唯⼀不同在于通过配置⽣成.css 为 .wxss ,其中的对于 css 的若⼲处理,在 postcss-mpvuewxss 和 px2rpx-loader 这两部分的⽂档中⼜详细的介绍。
推荐和⼩程序⼀样,将 app.json/page.json 放到⻚⾯⼊⼝处,使⽤ copy-webpackplugin copy 到对应的⽣成位置。
这部分内容来源于 app 和 page 的 entry ⽂件,通常习惯是 main.js , 你需要在你的⼊⼝⽂件中 export default { config: {} } ,这才能被我 们的 loader 识别为这是⼀个配置,需要写成 json ⽂件。
import Vue from 'vue';
import App from './app';
const vueApp = new Vue(App);
vueApp.$mount();
// 这个是我们约定的额外的配置
export default {
// 这个字段下的数据会被填充到 app.json / page.json
config: {
pages: ['static/calendar/calendar', '^pages/list/list'], // Will be
window: {
backgroundTextStyle: 'light',
navigationBarBackgroundColor: '##455A73',
navigationBarTitleText: '美团汽⻋票',
navigationBarTextStyle: '##fff'
}
} };
每天一句中文式外语
泰语
1、你好!/Sa-wa-di-ka萨瓦迪卡(男生要说:萨瓦迪卡扑)/Hello!/Hi!
2、你好吗?/sa-bai-di-mai萨拜迪麦/How are you?
3、我还好!/sa-bai-di萨拜迪/I am fine!
4、您叫什么名字?/kun-ci-a-lai坤赐阿莱/what is your name?
5、你去哪里?/kun-bai-nai 坤拜奈/where are you going? 6、再见!/la-gong 拉拱(联想记忆:拉弓射箭:那就是再见了)/ Good Bye!/Bye Bye
7、祝好运!/cuo-di措迪(联想记忆:用脚搓地表示好运)/ Good Luck!
8、谢谢你!/kuo-kun扩坤/Thank you!
9、对不起!/kuo-tuo扩拓 /sorry!/Excuse me!
10、不要紧!没关系!/mai-bian-lai卖鞭来(穷的要命,但是心爱的马儿舍不得卖,那就卖鞭来生活。卖鞭来)/never mind!
(日常会常用的我用红字标识出来了)对了,英语是国际通用语言,一定要重视起来哟!