当你使用 megalo
、mpvue
这些框架的时候,看上去,写的是vue
的代码,一切仿佛就像在写一个 vue
项目,然后打包编译之后就可以运行在小程序内,是不是很神奇?
接下来,我会用一个系列文章,讲解这些跨端框架的核心原理,深入到源码底层去分析,揭开他们神秘的面纱。
小程序的跨端方案哪家强?
首先,这不是一个跨端框架的评测文章。
本质上,这几家的框架的核心都一样,甚至是 weex
这类框架,都差不多的。
这些框架做的事情,都是把 vue 框架拿过来改了改,借助了 vue 的能力,比如说,Vue
的DSL
、vue 的编译打包流程(也就是vue-loader), vue 的响应式双向绑定,虚拟dom, diff 算法,在涉及到具体平台的dom操作时,替换成对应平台的api。他们之间不同的点,在于具体问题的处理,优化策略的不同。
从另一个角度来说,无论选择哪家的框架进行源码分析,对于普通前端来说,都能从中学到很多知识。
目前想法是,先从 megalo
的源码开始分析,之后再对比其他框架的不同。
写在读源码之前
不要一头扎进去读源码,否则会找不到北。
要先了解一下整理流程,再去看细节和各种分支逻辑。
所以,在本章节不会先去看源码是怎么实现的,而是先介绍一下megalo
的整体流程,之后会再去读源码分析。
.vue 单文件,怎么跑在小程序里面?
首先,可能我们会有疑问,一个 .vue
的单文件,究竟做了啥,怎么就能跑在小程序里面了?
我们知道,对于微信小程序来说,只认 4份文件:.wxml
、.wxss
、.js
、 .json
。
下面是去小程序官网截的图。
一般来说,.vue
单文件有三个部分组成: <template>
、 <script>
、<style>
。
我们第一步需要做什么呢?就是把 <template>
、 <script>
、<style>
这三个部分对应的代码,拆一拆,处理一下,分到 .wxml
、.wxss
、.js
、 .json
这 4 份文件中。
- 最简单是
<style>
部分的css 样式,哪怕直接放到.wxss
,也是正常运行。 - 稍微复杂一点的是
<template>
到.wxml
,我们需要把 h5 的标签啊、vue的语法啊 替换成小程序的标签、小程序的语法。替换的工作我们称为模板替换
,下文会有一个章节用来介绍。 - 最难的是
<script>
到.js
, 涉及到vue
的运行时 如何和 小程序的实例通讯的问题,这一部分会用比较多的章节去介绍。
接下来,我们先看模板替换
,也就是.vue
到 .wxml
的过程。
<template>
到 .wxml
—— 模板替换
在进行 vue 开发的时候,我们是直接使用 vue 的语法进行开发的,与小程序的模版语法不一样,例如小程序里没有 div 标签,条件语句、循环语句在小程序里面都不是一样的。因此我们需要对模版进行转换。
例如,div 标签需要转换需要转换成 view 标签。
// 替换前
<div></div>
复制代码
// 替换后
<view></view>
复制代码
比如说,条件判断,v-if
则需要改变前缀,将 v- 改成 wx:
// 替换前
<div v-if=“a”>
复制代码
// 替换后
<view wx:if=“a”>
复制代码
再比如说,在 vue 里面绑定事件,常用 @事件名
的语法, 转成小程序模版则需要用 bind,同时用 tap 事件替换 click 事件:
// 替换前
<div @click=“b”>
复制代码
// 替换后
<view bindtap=“b”>
复制代码
Vue 和小程序插值表达式则是一样的,采用了双花括号,可以不需要做任何转换。
// 替换前
<div>{{ title }}</div>
复制代码
// 替换后
<view>{{ title }}</view>
复制代码
上面展示模板替换是最简单的转换,这种转换能实现最简单的插值表达式的数据映射。但后面我们会提到,它有局限性。
接下来,我们看一下, <script>
到 .js
部分,这部分也可以称为 vue 运行时的改造
<script>
到 .js
—— vue 运行时的改造
在 .vue
单文件中的 script
部分中, 我们通常会写下面的代码,写的配置项传入Vue
构造函数中:
new Vue({
data(){},
methods: {},
components: {}
})
复制代码
new Vue()
会实例化出来一个 vm
实例。
但这并不是小程序想要的呀!
正如上图所示,我们在 <script>
中写的是 new Vue()
这样子的代码,而微信想要的是 Page()
。
Page()
方法是小程序官方提供的api。题外话, Page() 方法是必须要有的。微信小程序会在进入一个页面时,扫描该页面中的
.js
文件,如果没有调用Page()
这个方法,页面会报错。
那么,应该怎么做呢?
megalo
的做法是,拓展了 Vue
的框架,如下面伪代码所示:
new Vue() {};
Vue.init = () => {
Page()
}
复制代码
在 vue 实例化的时候,调用 init
时会调用 Page()
函数,生成一个小程序的page
实例。这就涉及到,vue 的数据和 page
实例的数据同步。
那么,如何做到数据同步呢?
为了更好的深入理解,接下里需要介绍一下 vue 的核心流程。
Vue 的核心流程
如下图左侧所示,简单来说, 一个 .vue
的单文件由三部分构成: template
, script
, style
。
我们先看图中的橘黄色路径,也就是 template
部分的处理过程, template
部分, 会在编译的过程中,在 vue-loader
中通过 ast
进行分析,最终生成一段 render
函数,render
函数可以在打包生成的文件中找到。如果你忘记了 render
长什么样子,可以看下面这个例子:
一段简单的 template
模板如下所示:
<div class="ctl-view" @click="handleClick">
{{ a }}
</div>
复制代码
经过编译之后,通过 ast
进行分析,生成的 render
函数如下:
_c("div", { staticClass: "ctl-view", on: { click: _vm.handleClick } },[_vm._t("default")])
复制代码
上面的 render
函数,会被 vue 在运行时执行。那么执行 render
函数会生成什么呢?
是生成虚拟dom树 (对应图中,render
函数的下一个阶段)。
虚拟DOM树是对真实DOM树的抽象,树中的节点被称作 vnode
。 vnode
有一个特点, 它保存了这个DOM节点用到了哪些数据 ,这一点非常重要,因为下文会介绍到megalo利用了该特点。
Vue
拿到 虚拟dom树之后,就可以去和上次老的 虚拟dom树 做 patch
diff
对比。目的是找出,我们应该怎么样改动旧的DOM树,代价才最小。
patch
阶段之后,vue
就会使用真实的操作DOM的方法(比如说 insertBefore
, appendChild
之类的),去操作DOM结点,更新视图。
接下来,我们再来看一下,蓝色的线条的路径。在实例化 Vue
的时候,会对数据 data
做响应式的处理。
这部分的内容已经有很多人写过源码分析了,这里就不再赘述。
简单来说,本质是通过 object.defineproperty
重写了数据的 getter
和 setter
, 在 getter
中收集依赖,在 setter
中通知依赖。当数据发生改变,watcher
就会收到通知,从而触发 watch
实例的 update
方法。
在 watcher.update()
中,最终又会调用上文的 render
函数,生成最新的虚拟DOM树, 接着对比老的虚拟DOM 树进行 patch
, 找出最小修改代价的vnode
节点进行修改。
上面介绍了 vue
的整体流程,下面趁热打铁,介绍megalo
的核心流程。
megalo 的核心流程
megalo 、 mpvue 等小程序框架,本质都是对 vue 的拓展。
对比 vue 的核心流程图,我们发现,小程序跨端框架的流程图多了一个 Page()
方法,同时还新增了setData
,替换掉 vue
原本的 DOM 操作。
megalo
等跨端框架,会在页面初始化的时候,实例化一个Vue
实例,还会偷偷的调用 Page()
用于生成了小程序的page
实例。
因此在实际页面打开之后,会同时创建 Page
实例 和 Vue
实例,Page
实例和 Vue
实例之间是有通讯关系的, Vue
实例向Page
实例的通讯,就是setData
方法。
setData()
是小程序官方提供的api,用来修改小程序 page
实例上维护的数据。
思考一个问题:小程序并不支持原生DOM操作,因此也就没有修改视图节点的能力。那么我们想控制一个小程序页面上的一个节点显示和隐藏,应该怎么做呢?
我们没有办法直接操作小程序视图,只能调用 setData()
更新小程序实例上的数据,从而更新视图。
来看下面的例子:
new Vue({
data(){
return {
showToggle: true
}
}
})
复制代码
// 下面是经过 模板替换 之后的代码
<view wx:if="{{showToggle}}">
</view>
复制代码
在上面的例子中,showToggle
这个数据是写在Vue
实例上的。当该数据发生变化时, megalo
通过某种机制可以知道showToggle
发生了变化,调用小程序的 setData({showToggle: false})
,修改小程序实例上的数据, 从而让小程序Service
层去更新视图,从而控制该节点的显示和隐藏。
通讯流程是: vm 实例
-> 调用 setData()
-> 小程序 page 实例
-> 小程序 page 视图
下面这张图,是 megalo
官方的一张原理图。
上图中,应该不难理解。
小结
在本篇文章中,介绍了megalo
的模板替换、 vue
的核心流程、megalo
的核心流程。
但是这个流程还是很抽象的,一些具体的细节,会在下一篇文章中介绍。
比如说,小程序Page
实例的数据,真的是和 Vue
实例上的数据一一对应的吗?小程序 Page
实例中维护的数据结构是什么样子的?megalo
是如何保证高性能的?
这些问题,统统会在下一篇文章中介绍。