前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入分析小程序主流跨端框架原理(一)

深入分析小程序主流跨端框架原理(一)

作者头像
极乐君
发布2020-11-04 14:40:23
3.1K3
发布2020-11-04 14:40:23
举报
文章被收录于专栏:极乐技术社区极乐技术社区

我们将从框架的语法这两个维度进行讲解。第一部主讲Vue跨端框架,第二部主讲类 React 跨端框架。

目前,小程序在用户规模及商业化方面都取得了极大的成功。微信、支付宝、百度、字节跳动等平台的小程序日活都超过了3亿。

我们在开发小程序时仍然存在诸多痛点:小程序孱弱简陋的原生开发体验,注定会出现小程序增强型框架,来提升开发者开发体验;各家厂商小程序API碎片化的现状,注定会有多端框架会成为标配,由跨端框架肩负跨平台移植挑战。

正是因为开发者对于提升小程序开发效率有着强烈需求,小跨端框架发展到如今已经百花齐放、百家争鸣: 除了美团的 mpvue 、网易的 megalo 、滴滴的 chameloen 已经趋于稳定,京东的 Taro开始探索 taro next, Hbuilder 的uni-app 产品和生态持续完善,微信新推出了支持H5和微信小程序的 kbone 框架,蚂蚁金服的 remax。

上述的这么多跨端框架纷繁复杂,我们可以从下面两个维度进行分类:

1

小程序跨端框架的分类

1

按语法分类

从框架的语法来说,可以分为下面两类:

  • Vue语法
  • React语法/类React语法

主流的跨端框架基本遵循 React、Vue 语法,这也比较好理解,可以复用现有的技术栈,降低学习成本。

remax

Taro next

Taro 1/2

megalo

mpvue

uni-app

chameloen

语法

react

react

类 react

vue

vue

vue

类 vue

厂家

蚂蚁金服

京东

京东

网易考拉

美团

Hbuilder

滴滴

2

按实现原理分类

从实现原理上,开源社区的跨端框架大致分为下面两类:

  • compile time编译时
  • runtime运行时

compiletime编译时的跨端框架,主要的工作量在编译阶段。他们框架约定了一套自己的DSL ,在编译打包的过程中,利用 babel 工具通过 AST 进行转译,生成符合小程序规则的代码。

这种方式容易出现 BUG ,而且开发限制过多。早期的Taro 1.0 和2.0的版本就是采用的这种方案,下文会有更具体的介绍。

而另外一种runtime 运行时模式, 跨端框架真正的在小程序的逻辑层中运行起来 React 或者是 Vue 的运行时,然后通过适配层,实现自定义渲染器。这种方式比静态编译有天然的优势,所以 Taro 的最新 Next 版本和 Remax 采用的是这种方案。

2

写在小程序跨端原理之前

通过上文我们知道小程序跨端框架目前有很多嘛,各个大厂都会有自己的一套,百花齐放。文章篇幅有限,如果要分别拆开讲清楚他们各家实现的细节,是一件很困难同时很费时间的事情。

所以,下文会尝试梳理一下主流小程序一些共用性的通用实现原理, 尽量会屏蔽忽略掉各家实现一些细枝末节的细节差异,也不会在文章中贴大段的源码分析,而是通过伪代码来代替。

下面,我们会从 Vue 跨端框架React 跨端框架两个大方向,进入到小程序跨端原理的世界,讲解这些跨端框架的核心原理,深入到源码底层去分析,揭开他们神秘的面纱。

3

Vue 跨端框架

当你使用 megalompvue 这些 Vue 跨端框架时,看上去,我们写的是vue 的代码,然后打包编译之后就可以运行在小程序内,是不是很神奇?这些框架背后做了哪些事情呢?

实际上,这些 Vue的跨端框架 核心原理都差不多,都是把 Vue 框架拿过来强行的改了一波,借助了 vue 的能力。比如说,vue 的编译打包流程(也就是vue-loader的能力), vue 的响应式双向绑定、虚拟dom、diff 算法。上面这些东西跨端框架都没有修改,直接哪来用的。

那么哪些部分是这些跨端框架自己新加的东西呢?涉及到 Vue 框架中操作DOM节点的代码

这些跨端框架,把原本Vue框架中原生 javascript 操作 DOM 的方法,替换成小程序平台的 setData()。为什么要这样呢?不着急,下文会有比较详细的讲解。

不着急,慢慢来,我们先从一个最简单的问题开始。

1

从vue到小程序

首先我们来看,一个 vue 的单文件,究竟做了啥,怎么就能跑在小程序里面了?

我们知道,对于微信小程序来说,需要:.wxml.wxss.js.json

(上面是去微信小程序官网截的图)

而对于一个Vue组件来说,一个vue文件有三个部分组成:template, script, style

那么,这些跨端框架把Vue 单文件中的 <template><script><style>这三个部分对应的代码,拆一拆,分别处理编译一下,分到 .wxml.wxss.js.json 这 4 份文件中,如下图所示:

我们分别从<template><script><style>这三个部分来讨论:

  • <style> 部分是最简单的。一般来说,在 h5 环境中的 css 样式,大部分都可以直接挪到 .wxss,需要处理的部分比较少,除了少部分不支持的属性和 小程序的单位转换
  • <template> 转换到 .wxml稍微复杂一点。我们需要把 h5 的标签啊、vue特殊的语法替换成小程序的标签、小程序特殊的语法。替换的工作我们称为 模板替换 ,下文会有一个章节用来介绍。
  • 最难的是<script>.js, 涉及到 vue 的运行时 如何和 小程序的实例通讯的问题,这一部分会用比较多的章节去介绍。

接下来,我们先看模板替换 ,也就是template生成 .wxml文件的过程。

<template>.wxml

Vue 是采用 template 语法的,各大厂商的小程序也是采用了 template语法。从Vue的 template 转变成小程序的 template 相对比较简单,React的jsx 转变为小程序的 template 就相对比较棘手啦。

Vue 的 template 与小程序的 template 大体是上的语法很类似,但是还是有不一样的地方。例如小程序里没有 <div> 标签,而是小程序厂商提供的 <view> 标签等等

因此我们需要把 Vue 模版转换为微信小程序的 .wxml

例如上图所示,<div> 标签需要转换成 <view> 标签,一些 vue 中的语法也需要进行转化成对应小程序平台的语法。

再比如说,在 Vue 里面绑定事件常用 @methodName 的语法, 转成小程序模版则需要用 bind,同时用 tap 事件替换 click 事件。

除了这个,还有一些vue的模板语法,也需要转成小程序的模板语法

Vue 和小程序插值表达式则是一样的,采用了双花括号,可以不需要做任何转化

上面展示的这些模板替换,都只是替换为微信小程序的语法。转化为其他小程序平台的语法也是类似的思路,如下图所示:

那么,模板的转化具体是如何实现的呢?我们的第一想法是通过正则来匹配,但是要写出匹配出所有情况的正则是非常困难的。

实际上,mpvuemegalouni-app 的框架是采用了 ast 来解析转化模板的。

模板替换过程其实就是两侧对齐语法的过程,把语法不一致的地方改成一样的,是一个 case by case 的过程,只要匹配到不同情况下语法即可,比较费功夫但是难度系数不是很高。

接下来我们看如何把 <script> 中的内容,挪到小程序 .js 中呢?

<script> .js

我们在 .vue 单文件中的 script 部分中, 通常会写下面的代码,我们会写一个 Vue 的配置项对象传入到 Vue 构造函数中,然后 new Vue() 会实例化出来一个 vue 实例。

代码语言:javascript
复制

上面的代码是完全可以跑在小程序的逻辑层里面的,只要引入vue 即可,毕竟 Vue 大部分就是纯粹的 javascript。也就是说,小程序的渲染层里面是完全可以直接运行起来 Vue 的运行时和 React 的运行时的。

但是这样还不够,小程序平台还规定,要在小程序页面中调用 Page() 方法生成一个 page 实例, Page() 方法是小程序官方提供的 API。

在一个小程序的页面中,是必须有 Page() 方法的。微信小程序会在进入一个页面时,扫描该页面中的 .js 文件,如果没有调用 Page() 这个方法,页面会报错。

如下图所示,我们在 <script> 中写的是 new Vue() 这样子的代码,而微信想要的是 Page()

那么,应该怎么解决呢?

Vue 跨端框架他们拓展了 Vue 的框架,把 Vue2.0 的源码直接拷贝过来,改了里面的初始化方法,在初始化方法中调用了 Page() 方法, 如下面伪代码所示:

代码语言:javascript
复制

在 vue 实例化的时候,会调用 init 方法,在 init 方法里面会调用 Page() 函数,生成一个小程序的 page 实例。

这样,我们在一个小程序页面中,就会同时存在一个 vue 的实例,和一个小程序的 page 实例,他们是如何融合起来一起工作的呢?他们之间是如何做到数据管理的?如何进行通讯的呢?

接下来就涉及到 Vue 框架的核心流程了,为了方便一些不了解 Vue 同学,同时也为了更好的深入理解下面讲的内容,接下来会稍微讲一丢丢 vue 的核心流程。(真的只有一丢丢)

Vue 的核心流程

如下图左侧所示,简单来说, 一个 .vue 的单文件由三部分构成: template, script, style

我们先看上图中的橙黄色的路径,也就是 template 部分的处理过程。

如下图所示,template 模板部分会在编译打包的过程中,被 vue-loader 调用 compile 方法通过词法分析生成一个 ast 对象,然后调用代码生成器,经过遍历 AST 树递归的拼接字符串操作,最终生成一段 render 函数, render函数最后会存在打包生成的dist 文件中。

可以看下面这个例子,一段简单的 template 模板如下所示:

经过编译之后,通过 ast 进行分析,生成的 render 函数如下:

render 函数会在第一次 mount时,或者Vue 维护的 data 有更新产生的时候会被执行。

那么执行下面这段 render 函数会拿到什么呢?

上面图中蓝色圆圈中的 _c 方法是创建元素类型的vnode, 而 _v 方法是创建 文本类型的vnode。

Render 函数中会调用这些方法创建不同类型的vnode,最终的产物是生成好的虚拟DOM树 vnode tree,对应上面图中 render 函数的下一个阶段 vnode。

虚拟DOM树是对真实DOM树的抽象,树中的节点被称作 vnodevnode 有一个特点, 它保存了这个DOM节点用到了哪些数据 ,这一点非常重要。

Vue拿到 虚拟dom树之后,就可以去和上次老的虚拟dom树patch diff 对比。

这一步的目的是找出,我们应该怎么样改动现存的老的DOM树,代价才最小。

patch 阶段之后,如果是运行在浏览器环境中, vue 实例就会使用真实的原生 javascript 操作DOM的方法(比如说 insertBefore , appendChild 之类的),去操作DOM节点,更新浏览器页面的视图。

接下来,我们再来看一下上面图中,蓝色的线条的路径。

在new Vue 的时候,Vue 在初始化的时候会对数据 data 做响应式的处理,当有数据发生更新时,最后会调用上文的 render 函数,生成最新的虚拟DOM树。

接着对比老的虚拟DOM 树进行 patch, 找出最小修改代价的vnode 节点进行修改。

上面介绍的流程就是 vue 的整体流程啦。

我们要关心的是,下面的类 vue 小程序跨端框架的核心流程。接下来一起来看吧。

类 vue 小程序跨端框架的核心流程

在进一步讲解之前,我们先思考一个问题。上图中,Vue 在 diff 之后就是操作了原生的 DOM 元素,但是各家厂商的小程序不支持原生DOM操作,因此也就没有修改视图节点的能力。那么我们怎么样才能更新小程序的视图呢?

下面这张图代表了类 vue小程序跨端框架的核心流程图。

咋一看这张图,会发现和上面Vue的图是很像的。毕竟 megalompvue 等小程序框架,本质都是对 vue 的拓展(copy过来改了改)

仔细和上面的 vue 的核心流程图一对比,我们发现,小程序跨端框架的流程图替换掉 vue 原本的 DOM 操作,替换为新增的绿色的setData 操作, 同时还多了一个绿色框框中的的 Page() 方法。

Page() 方法上文有介绍过原因

setData() 是小程序官方提供的 API,用来修改小程序 page 实例上的数据,从而会更新小程序的视图。

『替换掉 vue 原本的 DOM 操作』这一个点比较容易理解,因为小程序容器并没有提供操作小程序节点的 API 方法,这是因为小程序隔离了渲染进程 (渲染层)和逻辑进程 (逻辑层),如下图所示:

在小程序容器中,逻辑层到渲染层的更新,只能通过 setData() 来实现。

不管是 mpvuemegalo ,还是uniapp,这些类 vue 跨端框架,都是通过这种方法来更新视图的。而且,在未来可预见的几年里,只要小程序厂商不提供修改小程序节点的 API 方法,小程序跨端框架更新 DOM 节点仍然会通过 setData 这种 API

好了,到了这一步,我们已经知道了,跨端框架替换了 Vue 框架中 JS 操作DOM 原生节点的 API setData() 来更新小程序的页面。

但是我们还是不知道具体背后做了什么,接下来,看一个具体的例子:

代码语言:javascript
复制

在上面的例子中,showToggle 这个变量代表的数据是维护在Vue 实例上的。

在页面初始化的时候,我们的小程序跨端框架就开始执行了,它会先实例化一个Vue 实例,然后调用小程序官方的 Page() API 生成了小程序的page 实例,并在在 Vue 的 mounted 中会把数据同步到小程序的 page 实例上。

因此在实际页面打开之后,会同时存在小程序原生的Page 实例和 Vue 实例。vue 实例上有数据(我们的 data 本来就是定义在 vue 里面的),小程序Page 实例上也有数据(小程序实例上没数据没法渲染页面对吧)。

当 Vue 中的数据发生变化时,会触发 Vue 响应式的逻辑,走 上图中Vue 更新的那一套逻辑:重新执行 render 函数得出一份最新的 Vnode 树。

接下来 Vue去 diff 新旧两个 Vnode 的树,找出修改 DOM 节点最高效的操作。注意!接下来不是调用操作 DOM 的 API, 而是调用小程序的 setData() API 方法, 修改小程序实例上的对应的数据, 从而让小程序渲染层层去更新视图。

这一套流程下来我们发现,通俗来讲,数据是归 Vue 管。Vue 是一个双向数据绑定的框架,小程序也是一个双向数据绑定的东西,这两个东西放一块,通过跨端框架的运行时来做中间桥接,把数据同步到小程序中。

事件归到小程序容器管,小程序触发各种事件,比如说滚动,事件点击,小程序容器捕获到事件后,会去调用在 Vue 注册的对应的事件处理函数。

上面介绍的模型,是一个通用的Vue 小程序跨端框架的实现。Vue 的小程序跨端框架基本上思路是一致的。有了这些理解和认识,我们再来看一下各家小程序框架是如何实现的:

Mpvue 模型

下面是 mpvue 官方网站上的一张原理图:

从右到左来看,当 Vue 上数据变化时,会通过 mpVue 运行时来通知小程序的实例,从而更新小程序 page 视图。从左到右,当小程序的渲染层容器触发了事件后,会通过跨端框架运行时来找到注册的 vue 的事件回调函数

Uni-app 模型

我们接着来看,下面是 uni-app 的官网的原理图,和上面的图像素级别的相似啊

从右到左来看,当 Vue 上数据变化时,会通过uni-app运行时来通知小程序的实例,从而更新小程序 page 视图。从左到右,当小程序的渲染层容器触发了事件后,会通过跨端框架运行时来找到注册的 vue 的事件回调函数

Megalo 模型

下面是 megao 官方的一张原理图,这两张图和上面看似长的不一样,但表达的的意思是一样的。

小结

在这个小节中,重点部分有跨端框架模板替换、 vue 的核心流程、跨端框架替换了 Vue 的 javascript 操作真实 DOM 的 API 等。

至此,一个 vue 跨端框架的核心流程就已经走完了。这个流程中,一些跨端框架会进行优化,不同的跨端框架会采用不同的优化策略。第二部分我们将探讨【类React跨端框架】,将于下周推送,敬请关注。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-10-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 极乐技术社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 小程序跨端框架的分类
  • 写在小程序跨端原理之前
    • <template> 到 .wxml
      • <script> 到 .js
        • Vue 的核心流程
          • 类 vue 小程序跨端框架的核心流程
            • Mpvue 模型
            • Uni-app 模型
            • Megalo 模型
            • 小结
        相关产品与服务
        云开发 CloudBase
        云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档