主题
小程序
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和<button open-type="getUserInfo"><button/>这两种⽅式引导用户主动授权, 主动授权后通过返回的信息和服务端交互 (这里有⼀步需要服务端解密数据的过程,很简单,微信提供了示例代码) 即可拿到unionid建立用户体系, 然后由服务端返回登录态,本地记录即可实现登录, 附上微信提供的最佳实践调用
wx.login获取code,然后从微信后端换取到session_key,用于解密getUserInfo返回的敏感数据使用
wx.getSetting获取用户的授权情况- 如果用户已经授权, 直接调用 API
wx.getUserInfo获取用户最新的信息; - 用户未授权,在界面中显示⼀个按钮提示用户登⼊, 当用户点击并授权后就获取到用户的最新信息
- 如果用户已经授权, 直接调用 API
获取到用户数据后可以进行展示或者发送给自⼰的后端。
WARNING
注意事项
需要获取
unionid形式的登录体系,在以前 ( 18 年 4 ⽉之前) 是通过以下这种⽅式来实现,但后续微信做了调整 ( 因为⼀进⼊⼩程序, 主动弹起各种授权弹窗的这种形式, 比较容易导致用户流失), 调整为必须使用按钮引导用户主动授权的⽅式, 这次调整对开发者影响较大, 开发者需要注意遵守微信的规则, 并及时和业务⽅沟通业务形式,不要存在侥幸⼼理, 以防造成⼩程序不过审等情况jswx.login(获取code) ===> wx.getUserInfo(用户授权) ===> 获取 unionid因为⼩程序不存在 cookie 的概念, 登录态必须缓存在本地, 因此强烈建议为登录态设置过期时间
值得⼀提的是如果需要⽀持风控安全校验, 多平台登录等功能, 可能需要加⼊⼀些公共参数,例如 platform , channel , deviceParam 等参数 。在和服务端确定⽅案时,作为前端同学应该及时提出这些合理的建议,设计合理的系统。
openid,unionid不要在接⼝中明⽂传输, 这是⼀种危险的⾏为, 同时也很不专业
2. 图片导出
这是⼀种常⻅的引流⽅式,⼀般同时会在图片中附加⼀个⼩程序⼆维码。
基本原理
- 借助
canvas元素,将需要导出的样式首先在canvas画布上绘制出来api基本和h5保持⼀致,但有轻微差异,使用时注意即可 - 借助微信提供的
canvasToTempFilePath导出图片, 最后再使用saveImageToPhotosAlbum( 需要授权) 保存图片到本地
- 借助
如何优雅实现
- 绘制出需要的样式这⼀步是省略不掉的 。但是我们可以封装⼀个绘制库, 包含常⻅图形的绘制,例如矩形, 圆⻆矩形, 圆, 扇形, 三⻆形, ⽂字, 图片减少绘制代码, 只需要提炼出样式信息,便可以轻松的绘制, 最后导出图片存⼊相册 。笔者觉得以下这种⽅式绘制更为优雅清晰⼀些, 其实也可以使用加⼊⼀个 type 参数来指定绘制类型,传⼊的⼀个是样式数组, 实现绘制。
- 结合上⼀步的实现, 如果对于同⼀类型的卡片有多次导出需求的场景,也可以使用自定义组件的⽅式, 封装同⼀类型的卡片为⼀个通用组件,在需要导出图片功能的地⽅, 引⼊该组件即可。
jsclass 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()
WARNING
注意事项
- 小程序中无法绘制网络图片到
canvas上, 需要通过downLoadFile先下载图片到本地临时文件才可以绘制 - 通常需要绘制⼆维码到导出的图片上, 有⼀种方式导出⼆维码时, 需要携带的参数必须做编码, 而且有具体的长度 ( 32 可见字符) 限制, 可以借助服务端生成短链接 的方式来解决
3. 数据统计
数据统计作为目前⼀种常用的分析用户行为的方式,小程序端也是必不可少的 。小程序采取的曝光,点击数据埋点其实和 h5 原理是⼀样的 。但是埋点作为⼀个和业务逻辑不相关的需求, 我们如果在每⼀个点击事件,每⼀个生命周期加⼊各种埋点代码,则会干扰正常的业务逻辑,和使代码变的臃肿, 笔者提供以下⼏种思路来解决数据埋点
- 设计⼀个埋点 sdk
⼩程序的代码结构是,每⼀个 Page 中都有⼀个 Page 方法,接受⼀个包含生命周期函数,数据的 业务逻辑对象 包装这层数据,借助⼩程序的底层逻辑实现页面的业务逻辑 。通过这个我们可以想到思路,对 Page 进⾏⼀次包装,篡改它的生命周期和点击事件, 混⼊埋点代码,不⼲扰业务逻辑, 只要做⼀些简单的配置即可埋点, 简单的代码实现如下:
js
// 代码仅供理解思路
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特性, 好用的asyncawait等特性都⽆法使用 - 不⽀持引⼊外部字体⽂件, 只⽀持
base64 - 没有
eslint等代码检查工具
- 方案选型
对于目前常用的工程化⽅案, webpack , rollup , parcel 等来看,都常用与单页应用的打包和处理, 而小程序天生是“多页应用”并且存在⼀些特定的配置 。根据要解决的问题来看, ⽆非是⽂件的编译,修改,拷贝这些处理,对于这些需求, 我们想到基于流的 gulp 非常的适合处理, 并且相对于 webpack 配置多页应用更加简单 。所以小程序工程化⽅案推荐使用 gulp
- 具体开发思路
通过 gulp 的 task 实现:
- 实时编译
less⽂件至相应目录 - 引⼊⽀持
async,await的运行时⽂件 - 编译字体⽂件为
base64并生成相应css⽂件, ⽅便使用 - 依赖分析哪些地⽅引用了
npm包,将npm包打成⼀个⽂件,拷贝至相应目录 - 检查代码规范
5. 小程序架构

微信小程序的框架包含两部分 View 视图层 、 AppService 逻辑层。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 。公式:
js
const dsWidth = 75 0
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 - 原生分包加载的配置 假设支持分包的小程序目录结构如下:
md
├── app . js
├── app . json
├── app .wxss
├── packageA
│ └── pages
│ ├── cat
│ └── dog
│
├── packageB
│ └── pages
│ ├── apple
│ └── banana
│
├── pages
│ ├── index
│ └── logs
└── utils开发者通过在 app.json subPackages 字段声明项目分包结构
json
{
"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 packageBJS 文件,但可以require app、自己package内的JS、文件packageA无法import packageB的template,但可以require app、自己package内的templatepackageA无法使用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 、 Redux 、 Vuex 等多个数据流⼯具出现,我们也期望在业务复杂的⼩程序中使用
WePY默认⽀持Redux,在脚手架生成项目的时候可以内置Mpvue作为Vue的移植版本, 当然⽀持Vuex, 同样在脚手架生成项目的时候可以内置
组件化
WePY类似Vue实现了单⽂件组件, 最大的差别是⽂件后缀.wpy, 只是写法上会有差异
js
export default class Index extends wepy.page {}Mpvue作为Vue的移植版本, ⽀持单⽂件组件,template、script和style都在⼀个.vue⽂件中,和vue的写法类似,所以对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
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使用了Virtual DOM,所以Virtual DOM可以在任何支持JavaScript语⾔的平台上操作, 譬如说目前Vue支持浏览器平台或weex,也可以是mp(小程序)。那么最后Virtual DOM如何映射到真实的DOM节点上呢?vue为平台做了⼀层适配层, 浏览器平台⻅runtime/node-ops.js、weex平台⻅runtime/node-ops.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/x-template">字符串模版等都不支持
另外还有⼀些其他问题,最后总结⼀下:
- 如何预编译生成
render function - 如何预编译生成
wxml,wxss,wxs - 如何
patch出diff - 如何更新视图
- 如何建立⼩程序事件代理机制,在事件代理函数中触发与之对应的
vue组件事件响应 - 如何建立
vue实例与⼩程序Page实例关联 - 如何建立⼩程序和
vue生命周期映射关系, 能在⼩程序生命周期中触发vue生命周期
platform/mp 的目录结构
js
├── 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-template-compiler 生 成 render function
entry
它会从 webpack 的配置中的 entry 开始,分析依赖模块, 并分别打包 。在 entry 中 app 属性及其内容会被打包为微信小程序所需要的 app.js/app .json/app.wxss , 其余的会生成对应的页面 page.js / page.json / page.wxml / page.wxss , 如示例的 entry 将会生成如下这些文件,文件内容下文慢慢讲来
js
// 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')
} }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.jswxml 每⼀个 .vue 的组件都会被生成为⼀个 wxml 规范的 template ,然后通过 wxml 规范的 import 语法来达到⼀个复用, 同时组件如果涉及到 props 的 data 数据,我们也会做相应的处理,举个实际的例子:
vue
<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
vue
<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="{{ ..}}"
></template>
</view>
</template>可能已经注意到了 other-component(:msg=“msg”) 被转化成了 。 mpvue 在运⾏时会从根组件开始把所有的组件实例数据合并成⼀个树形的数据,然后通过 setData 到 appData , $c 是 $children 的缩写。⾄于那个 0 则 是我们的 compiler 处理过后的⼀个标记,会为每⼀个⼦组件打⼀个特定的 不重复的标记。 树形数据结构如下:
js
// 这⼉数据结构是⼀个数组,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 ⽂件。
js
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",
},
},
};