第三方小程序框架原理

·  阅读 508

前言

本章内容分解:

  • 小程序框架都有哪些
  • 框架之间的原理有什么不同
  • 具体实现方式是怎样的

第三方框架

为什么会出现第三方框架呢,换一种方式来说的话就是小程序在基于当前的情况下还有什么弊端?

这一章我们就来聊一聊这个方面。

首先webpack工程化小程序就不支持。还有预编译也是不可以的,原来我们开发的时候可以使用lesssass写起来很方便,规避一些问题,做一些嵌套、类的选择,集中处理一些问题。

如果直接使用小程序规则开发,对于开发效率方面会有一些影响,也不方便定制工程化的流程。会有一些回归到最原始的html+css+js的开发模式年代的感觉。

为了解决上面的问题,出现了很多的小程序第三方框架。这些第三方框架基本上都是围绕着两种语言来的,vue & react。或者说类vue类react

那么我们就来聊一下框架。

三种框架

预编译

什么是预编译的框架呢?还记得我们讲解WXSS的时候,WXSS的文件会编译成js再执行。像这种执行前就进行编译的手段就叫做预编译。这种框架就是预编译框架。wepytaro就是这样的框架。

我们先用taro的代码来举一个例子。

image.png

代码上我添加了一些备注,预编译框架正是分开编译了这三个部分,在执行之前就将小程序需要的文件编译出来,比如return内容编译为WXMLless编译为WXSS,生命周期及方法等编译为js

预编译框架自身定义了一套DSL(语法规则),这里taro的语法规则就是类似react的语法规则,也一直是taro主打的优势对吧。然后DSL解析编译为抽象语法树AST进行词法分析和语法分析。最后还原为小程序的代码。

可以看出预编译框架的核心思想就是DSL+ 语法解析。刚看了一下taro最新的文档,已经支持react hooks这样的写法了。

image.png

如果taro支持react hooks类似的写法的话,那么taro团队一定需要写一套关于hooks的语法解析,把hooks的逻辑转换为小程序的js逻辑。换句话说,taro已经把react的语法解析写的差不多了。现在还支持了vueNerv版本。

可以看的出来的是上面的截图中有一句话:

本篇文档只会介绍在 Taro 中可用的 Hooks API 和部分与 React 不一致的行为,其它内容大体的内容和 Hooks Reference 相同。

与React有不一致是很正常的事情,自己写语法解析终究会有一些环境掣肘,毕竟是两个平台之间的兼容,我认为主要是小程序平台的规则较为简陋导致解析不过来。

不过taro自身也推出了一些类似hooks的api可以使用。这样的话就可以弥补一些与react hooks不一致的情况,业务场景覆盖还是很全的。

image.png

我们再换成wepy来看一下。wepy也是预编译框架,看一下wepy的模版是怎么样的。

image.png

可以看到模版中的语法与vue模版语法相似,只不过有些许地方不一样,比如page的声明,template中的结构,小程序独有的config模块。

与taro一致,只不过wepy定制的是类似vue语法结构的DSL,模版中的四个模块分别会编译为小程序的四个文件。style模块编译为WXSS,template模块编译为WXML,script模块编译为js,config模块编译为小程序配置json。

小程序预编译框架的原理就如上述讲解,我们可以想象一下预编译框架的坏处有什么呢?

  • react或者vue后期再出一些新特性的话,预编译框架都需要在进行语法解析扩展编写。
  • 兼容问题,比如小程序不支持的一些属性,如果不支持,预编译框架要进行兼容。

因为如上因素,半编译半运行框架随之推出。

半编译半运行框架

半编译半运行框架有什么呢,美团开发的mpvue

虽然还没讲到运行时框架,但是要了解到是,基本上运行时的框架都是基于vue的框架才可以达到运行时的目的。可以看一下mpvueGithub简介

image.png

这个项目就是直接从vue项目fork过来修改的。那么它改了什么东西呢,我们接着往后看。

首先看一下vue的渲染框架:

mpvue.png

最后的node就是web端渲染真实node节点了,达到页面更新渲染的目的。

我们可以想象一下一个问题,如果想让vue运行在小程序中,需要做哪些工作?

通过前面章节的讲解我们可知微信小程序的视图渲染与逻辑是分开的,逻辑层通过setData更新视图渲染。

那么就可以修改patch流程不直接生成真实node,而是触发setData来更新视图层。

可以想象一下,如果把vuetemplate编译成WXML就变为了小程序的视图层。vue本身预编译的代码为js,这个js是可以在逻辑层中运行由于js-core,然后当数据变动的时候走vue的渲染流程,patch流程改为setData来触发视图层更新。这样的话是完全没问题的。

所以为什么这个框架的名称叫半编译半运行框架,半编译讲的是vuetemplate需要单独编译为wxml,半运行讲的是vue整体的特性都会在逻辑层中运行。为了符合小程序的渲染框架,修改了vue的框架,最终达到了这个目的。

看一下mpvue的官网介绍。

image.png

可以看到介绍中提到的compiler实现就是讲vue中的template抽离并且编译为wxml的模块。mpvue的源码中可以看到具体实现。

image.png

备注都是中文还是很友好的。

另一处修改就是vue的runtime下的patch模块。

image.png

可以看到触发了一个this.$updateDataToMP()方法。这个方法在runtime/render.js中。

image.png

this.$updateDataToMP()方法中就进行了setData的一个调用。还可以看到有另一个方法为initDataToMP初始化方法。

在初始化中setData中data是全部的,然而在updateDataToMP阶段可以看到做了一个diffData数据的比对,有修改的data才会触发setData。

并且随后的是throttle函数,减少setData的次数。优化双线程通讯的性能。在函数上方备注的也很清楚。

这两点也是印证了上面我们所阐述的,核心修改就是这两处,当然还需要一些额外的工作才可以,比如说生命周期的对称、监听等等,都是修改runtime下的文件。

image.png

大家可以看一下mpvue的源码,并且备注十分齐全。

运行时框架

可以借鉴一下半编译半运行时框架的原理思考,如果要把半运行时框架变成为运行时框架需要做什么?

首先为什么mpvue需要compiler模块将vue中的template编译为WXML?为什么不直接生成真实node

我们需要缕一下思路。

首先vue或者react如果生成真实node后,需要插入到HTML中去,怎么插入呢,一般是通过操作DOM的api比如innerHTML类似。

在小程序双线程架构中,渲染层是没有开放任何操作DOM的api给逻辑层的。逻辑层是没有办法通过操作DOM来改变视图的。所以我们看到了半编译半运行时框架通过半编译,把vue的template模版提前编译为wxml,然后通过setData把data数据传输过去。

然而纯运行时框架就是要解决这个半编译的问题。

届时有几个问题需要解决:

  • 通讯方式只能通过setData到渲染层
  • vue、react最终需要操作DOM

看似无解的两个问题。如果vue运行时在逻辑层,那么逻辑层终究没有操作DOM的api。

首先需要解决的问题就是动态地渲染DOM问题。

针对这块的话有一个契机,小程序的template模版机制。我们先看一下什么是小程序的template模版。

小程序模版template

WXML提供模板(template),可以在模板中定义代码片段,然后在不同的地方调用。

定义模板:

使用 name 属性,作为模板的名字。然后在<template/>内定义代码片段,如:

<!--
  index: int
  msg: string
  time: string
-->
<template name="msgItem">
  <view>
    <text> {{index}}: {{msg}} </text>
    <text> Time: {{time}} </text>
  </view>
</template>
复制代码

使用模板:

<template is="msgItem" data="{{...item}}"/>
复制代码
Page({
  data: {
    item: {
      index: 0,
      msg: 'this is a template',
      time: '2016-09-15'
    }
  }
})
复制代码

is 属性可以使用Mustache语法(一个logic-less(轻逻辑)模板解析引擎,下方例子中的{{}}就是Mustache语法),来动态决定具体需要渲染哪个模板:

<template name="odd">
  <view> odd </view>
</template>
<template name="even">
  <view> even </view>
</template>

<block wx:for="{{[1, 2, 3, 4, 5]}}">
  <template is="{{item % 2 == 0 ? 'even' : 'odd'}}"/>
</block>
复制代码

上面就是template的一些基本使用方式,下面我们一步一步来实现一个动态的template模板渲染结构。

动态template

首先我们新建一个空白的页面,在WXML中添加一个基础的template,其中渲染一个image

image.png

然后在js中添加响应data:

image.png

root是一个对象类型,有属性src。

这个时候,如果我们想在图片的下方添加一段文字。按照正常的数据结构是这样实现的:

image.png

js相应的响应数据中添加text字段。

image.png

此时的页面中:

image.png

渲染方面是没有问题的,但是这种数据结构的扩展性不是很好,我们希望每一种标记都可以自行搭配。

像如上的数据结构怎么动态搭配呢?首先肯定是要把text组件拆分出去,先拆分出去看一下template的数据结构。

image.png

这里把text标记单独拆分为template,并且命名tpl-text。然后替换引用位置。

这个时候有个问题,如果我们希望template是可复用的,那么插入的位置最好不是固定的。比如这个时候有两个业务场景,第一个文字在图片的上方,第二个文字在图片的下方,这个时候就需要一个容器,用来控制template结构的容器。

image.png

对应容器的分发,data数据结构也需要修改一下。

image.png

首先我们分析一下容器结构,首先tpl-container中有一个block标记用来循环children结构。并且内部动态渲染template,内部渲染的template的类型是通过data中的type决定的。并且相应的可以看到image标记与text标记都已经独立拆分出去。

这里大家消化一下,接下来看一下data中的root结构是不是有点眼熟。有一点像树的结构,如果容器支持children自遍历的话那么root的数据结构就是一颗树,那么我们在此数据结构的基础上再套一层,使其变成两层children结构,外层套一个view。先看一下数据结构。

image.png

外层添加了一层view。相应的template这边也要做一下更改。首先声明template-view。

image.png

可以看到template-view中又再次包含了tpl-container,children再次可以进行解析分发。这样就可以进行数据结构的递归遍历。

回到主题,上述演变过程解决了小程序通过数据结构控制XML的问题。还有第二个问题需要解决,就是vuereact最终需要操作DOM的问题,小程序中并没有操作DOM的api给逻辑层中的react用,那么就自己写一套即可。

举个例子,比如appendChild这样的api,进行重写,最终效果为修改上述data数据结构。

image.png

最终思路就是复刻一套操作DOM的api,然后操作的并不是DOM,而是我们自己的数据结构,而后,我们自己的数据结构可以通过sandData发送到渲染层进行动态的模版渲染。这样的话就可以完成一套运行时的框架。

在这里看一下remax完整的VNode结构:

image.png

每个节点都是一个VNode,声明了一些属性、方法,可以看到appendChild的节点方法。

image.png

appendChild源码中本质就是操作data树结构。

remax的源码传送门remax

分类:
前端
收藏成功!
已添加到「」, 点击更改