前言
在介绍 Taro 之前,先来简单讲讲小程序。
说到小程序,大家最先想到的应该是微信小程序。三年前,微信利用其高流量、高用户停留时长等优势,在微信公众号的基础上,开放更多能力,促成了小程序的诞生,微信也借此从应用级 App 迈向平台化超级 App,这种即用即走的方式更好地满足的用户轻量 App 的需求。
其他厂商也看到了小程序业务的各种好处,纷纷争相效仿,也试图能够做成平台型 App。例如头条小程序、支付宝小程序、京东小程序等。而这些小程序都不约而同地采取了与微信小程序类似的架构和语法,从技术的角度上来说也许是不够明智,但从业务和标准化的角度来看,这是 ROI 比较高的选择,既有一套确实可行的方案,又能蹭一波微信小程序的热度,也可以让开发者更快地投放到自己的平台,一举多得。
但这样的方式势必带来一些问题。微信小程序在诞生之初就定义了自己的一套标准,这一套标准与前端已有的生态格格不入,最开始的微信小程序甚至没有 npm、没有工程化,是真正的刀耕火种时代。由于其特殊的双线程模型(渲染层和逻辑层)以及四不像语法,开发者的开发成本高、开发体验差。而各大厂商的小程序在采用与微信小程序相同架构和语法的同时,又基于自己平台的特性进行了各种扩展,导致各种小程序之间的差异化,小程序生态就此割裂。
在这样的背景下,「接入现有前端生态、良好的开发体验以及一码多端」 就成了小程序开发者的核心诉求。在这样的诉求下,诞生了许多小程序框架,例如 DCloud 的 Uniapp、京东的 Taro、阿里的 rax 等,其中 Taro 则是这些框架中的佼佼者,也是本次分享的主题。
本文不涉及这些小程序框架之间对比的讨论,如有兴趣可以自行阅读:
Taro 简介
Taro 是一款开源的多端统一的开发框架,让我们只需要编写一套代码,就可以运行在多个端上,很大程度上做到了 write once, run everywhere。这里我们没有把它称之为小程序框架,因为它并不止支持小程序跨端,而且能支持 H5 甚至是 RN。
而在开发体验上,Taro 支持 Nerv(一个类 React 框架)、React、Vue 开发,并且能较为完善的对接前端生态(例如 webpack、npm、Typescript 等),极大地提升了开发者的开发体验和开发效率。而Taro在这开源的两年多里也获得了业界较多的关注,也有非常多的团队在使用Taro进行开发,并且社区也十分活跃,是目前来说最为可靠的跨端框架。
最近,Taro 团队谋划了一年的重构 Taro Next 也上线了,这也是本文的主要内容,主要面向 Taro Next 中的小程序部分。
Taro 前世
探索
在介绍 Taro Next 之前,先给大家讲讲 Taro 的故事 —— Taro 诞生记。
Taro 团队在 2017 年抛弃了历史包袱,开始全面拥抱 React 的开发方式,并且也研发了自己的类 React 框架 Nerv,让 Taro 团队与 React 的开发思想结合得更深。在他们开发微信小程序时,就在思考着能否使用 React 来写微信小程序(这里我们可以看出 Taro 团队的出发点是想用 React 写微信小程序而不是跨端)。
于是,他们开始类比 React 和小程序,发现了许多相似的地方,例如生命周期、数据更新、事件绑定等,但也发现了巨大的差异,例如 React 的 JSX 语法和小程序的模板语法。而 JSX 的表现力和操作性要比模板语法强太多了,那么如何使用 JSX 来写小程序模板就成为了一个亟待解决的问题。
有一句话我非常喜欢,当一个语言的能力不足,而当前环境下又没有其他选择时,那么它最终会变成编译的目标语言。微信自然不可能直接去支持 JSX 的,于是他们就在思考,如何将 JSX 编译成小程序模板,这时,他们想到了编译原理,想到了 Babel,而 Babel 的核心编译器 babylon 是支持 JSX 语法解析的,这样一来,就能利用 Babel 生成的 AST 来转换成小程序模板了。
实现
以微信小程序为例,它由四部分组成:
- 配置(JSON)
- 视图层模板(WXML)
- 视图层样式(WXSS)
- 逻辑层逻辑(JS)
配置和样式转换起来比较简单,而难点在于模板的转换和逻辑的转换
模板的转换
模板的转换实际上就是把 JSX 代码中的 render 函数转换成小程序运行的字符串模板。
举个栗子:
以上是一个简单的 Taro 代码,经过 Taro 编写的一个 babel 插件(@tarojs/transformer-wx),会输出如下的微信小程序模板:
这些 View、Text 等都是 Taro 的内置组件,Taro 以微信小程序组件库为标准,定制了一套自己的组件库规范。而对于其他平台的小程序特有的一些组件,Taro 也把他们内置到了自己的组件库中,并在不同小程序中均实现了这些组件。
但在上面我们看到,我们使用了自定义组件 my-test-component,这个组件在小程序中是不存在的,所以并不能直接运行,需要一种跨端组件定义,来将自定义组件编译成各个端上支持的形式。为此,Taro 提供了两个东西:
- 跨端组件库 (
@taro/components):也就是我们上面说到的 Taro 内置组件(View、Text等),磨平了不同端上组件的差异 - 自定义组件多端打包方法:根据编译目标平台的不同(小程序、Web等),将自定义组件编程成各个端支持的形式。
实际上,第一点跨端组件库的实现就是利用了第二点的能力,只不过第一点是 Taro 帮我们完成了,用于应对一般的应用场景。而第二点则是开放一种自定义的能力,来满足定制化的需求。
逻辑的转换
类似组件库需要做多端的配置,各端能力的差异也需要适配。
于是 Taro 整了一套运行时框架,负责适配各端的能力。这个框架主要有三个作用:
- 适配组件化方案、配置选项等基础 API
- 适配平台能力相关的 API,例如调用网络请求、拍照等
- 提供一些应用能力,比如事件总线(
Taro.Events)、运行环境相关 API(Taro.getEnv)等
Taro.request 之类的平台 API 是存在于 @tarojs/taro 这个包中,实际上是提供了一个统一入口,在编译时会分平台进行替换。例如需要打包成头条小程序代码,那么在编译时实际上在 @tarojs/taro 中会引入 @tarojs/taro-tt 来调用头条小程序的相关 API。
我们再来看一下之前的 Demo 代码:
实际上,除了使用 render 函数生成视图之外,在使用组件时我们会使用一些状态、生命周期等,Taro 在编译时会对这部分逻辑进行编译,并通过运行时框架提供的能力,让其在小程序中可以正常执行,以下是 Taro 对这部分逻辑编译前后的对比:
可以看到,组件从继承 Component 变为继承 BaseComponent,render 方法被 _createData 方法代替了,并且整个组件用 createComponent 包起来了。可以看出,Taro 2.x 只是在开发的时候遵循了 React 语法,而在代码编译之后的实际运行中,和 React 基本上没什么关系了。
为什么 Taro 在这部分逻辑的转换时,没有像模板转换一样将其在编译时编译成小程序的 Page 实例的样子,而是采用了运行时的方案呢?
举例来说,小程序新建一个页面是使用 Page 方法传入一个字面量对象,并不支持使用类。如果全部依赖编译时的话,那么我们要做的事情大概就是把类转化成对象,把 state 变为 data,把生命周期例如 componentDidMount 转化成 onReady,把事件由类函数和类属性函数转化成字面量对象方法等等。比如下面这样:
这样会让我们的编译时工作变得非常繁重,在一个类异常复杂时出错的概率也会变高。一个更好的办法是:实现一个 createPage 方法,接受一个类作为参数,返回一个小程序 Page 方法所需要的字面量对象。这样不仅简化了编译时的工作,我们还可以在 createPage 对编译时产出的类做各种操作和优化。通过运行时把工作分离了之后,再编译时我们只需要在文件底部加上一行代码 Page(createPage(ComponentName)) 即可。这种方式在运行时实现起来非常简单,并且减轻了编译时的一些工作。
关于其他的 Taro 2.x 的一些实现原理,有兴趣可以查看 Taro 原理,里面描述的非常详细,在这里就不再赘述了。
经过这样一顿操作,Taro 团队已经把 React 语法代码转换成可以跑的小程序代码了。Taro2 的一个整体架构如下:
架构
它分为两个部分,第一部分是编译时,第二部分是运行时。编译时会先对用户的 React 代码进行编译,转换成各个端上的小程序都可以运行的代码,然后再在各个小程序端上面都配上一个对应的运行时框架(因为各个小程序提供的能力、接口有所不同)进行适配,最终让这份代码运行在各个小程序端上面。
就这样,Taro 诞生了(这里不涉及到 Taro 对 H5 和 RN 以及其他平台的适配)。
那这样的架构存在哪些问题呢?
存在的问题
编译时
- DSL强绑定,只能用 React
- JSX支持有限(花了大量代码来支持各种 JSX 语法)
- 不支持source-map
- 维护迭代非常困难
运行时
- 每一种小程序都要维护对应的运行时框架,当存在bug时或新增新特性时,需要同步修改
Taro Next 今生
发现了以上问题,Taro 团队在分析了一些其他小程序框架(例如 mpvue、Remax)的实现原理以及架构特点后,给他们带来了一些思考:
- 编译时 OR 运行时:当初 Taro 选择重编译时的主要原因是处于性能考虑,毕竟同等条件下,编译时做的工作越多,也就意味着运行时做的工作越少,性能会更好。但是从长远来看,计算机硬件的性能过剩,如果在牺牲一点可以容忍的性能的情况下换来整个框架更大的灵活性和更好的适配性是值得的。
- 模版静态编译 OR 动态构建:尽管 Taro 和 mpvue 的模版都是通过静态编译生成的,但是社区也不乏动态构建的例子,比如:Remax。
- DSL 限制:能否实现一个小程序开发框架,摆脱 DSL 的限制?
基于以上思考,Taro 团队将重心从编译时转向运行时,在运行时做更多的事来扩充框架的灵活性,但具体要怎么做呢?
Taro 团队从 mpvue 和 Remax 框架上受到了启发。这里我来以图例的形式简单的介绍一下 mpvue 和 Remax 的工作方式。
mpvue
mpvue 是与 Vue 强绑定的,在编译时会将 Vue 模板编译成小程序模板(这一点和 Taro 类似,只不过 Taro 是将 JSX 编译成小程序模板),而在运行时则是将 Vue 运行在小程序上。mpvue 运行的 Vue 是经过魔改过的,增加了一些对小程序的处理。
我们知道,Vue 的工作方式也分为编译时和运行时。在编译阶段会通过 vue-loader 对 template 进行 ast 分析,从而生成一段 render 函数。render 函数执行则会生成虚拟 DOM,虚拟 DOM 中的节点被称为 vnode。拿到虚拟 DOM 之后,就会去和之前的虚拟 DOM 做 diff patch 操作,得到最小需要更新的 vnode 节点。之后就会调用操作真实 DOM 的方法(例如 appendChild)来修改真实 DOM 节点,从而更新视图。
在下面这条绿色的路径,在实例化 Vue 时,会对 data 做响应式处理,当检测到 data 变化时,则再次调用 render 函数,并再走一遍之后的流程。
而在小程序中,是无法直接操作 DOM 的,也就是 diff patch 之后,没法操作 DOM 节点的修改。唯一能够更新视图的方式就是使用小程序提供的 setData 方法来修改数据,让小程序根据变更的数据来更新视图。那么 mpvue 是怎么做的呢?
前面我们说了,mpvue 中使用的 Vue 实际上是经过魔改的,它在 Vue 实例化的同时,在根实例上挂在了一个实例化的小程序 Page。Page 里维护着视图的状态数据,该数据是与 Vue 实例中的响应式数据保持同步的,是在 Vue 触发数据更新时同步更新 Page 中的数据,从而更新视图。
在上面那张图我们看到,mpvue 在 patch 阶段没有去调用(也没法调用)DOM 方法,他把相关的 DOM 方法全部置空,实际上只调用了 updateDataToMp 函数,去把当前 Vue 实例中的数据(data, computed,props 等)拿出来并同步修改到 Page 里(调用 Page 的 setData),这样就同步了数据并更新视图(还用了一些优化手段,例如节流等 为什么用节流)。
同时, Page 实例还会在小程序特定的生命周期内去触发 Vue 的生命周期钩子(这些钩子在初始化时就注入到了 Vue 实例中)
就这样,mpvue 将 Vue 较为完整地跑在了小程序上(但实际上,mpvue 还是存在很多问题,这点留给读者自己思考~)。
Remax
相比于 mpvue,Remax 则是强绑定 React 的,而且 Remax 的数据同步和视图更新与 mpvue 是不太一样的。从之前我们对 mpvue 的介绍可以看出,mpvue 可以算是半编译( Vue 模板编译成小程序模板)半运行时(魔改 Vue),而 Remax 则是一个基本全运行时的小程序框架。接下来我简单介绍一下 Remax 在小程序开发中的工作方式。
渲染器
以上是 React 的一个架构示意图,最下层是 React 本身(react-core),包含一些 React 的全局 API。而 react-reconciler(协调器) 主要是做虚拟 DOM 的生成以及 patch diff,并通知下一层(渲染器 renderer)去将虚拟 DOM 渲染到对应的宿主视图上。
我们看到,在 react-reconciler 和目标宿主平台(浏览器、客户端、Shell等)之间有一层渲染器 renderer,实际上它就是连接目标宿主平台和虚拟 DOM 之间的一道桥梁,实现了渲染的具体细节,它会在收到 react-reconciler 的一些指令后通过调用目标宿主平台的渲染接口来完成渲染。
这里补充一点关于 React 渲染器的知识。React 提供了统一的方式用于实现渲染器,开发者只需要提供两个东西就能自定义一个渲染器:
-
宿主配置(
hostConfig):react-reconciler要求宿主提供一份宿主配置,主要包含一些适配方法和配置项,这些适配方法和配置项定义了如何创建节点实例、构建节点数、更新等操作。在实例化reconciler时将hostConfig传入。HostConfig 结构 -
渲染函数(
render):渲染函数用于将组件树挂载到指定的目标节点上(一般挂载到根元素),例如我们在 Web 上使用的 ReactDOM 渲染器,它提供了一个渲染方法ReactDOM.render,会把我们的 React 根节点挂载到指定的 DOM 标签上,比如ReactDOM.render(<App />, document.querySelector('#app')。这个根元素是整个组件树的入口,他会被reconciler用来保存信息,以及管理下面所有节点的更新和渲染。
在调用渲染函数挂载根节点或子节点渲染更新的时候,reconciler 就会调用宿主配置中定义的一些适配方法来让宿主环境完成实际的渲染工作。
而 Remax 就是实现了一个渲染器,将虚拟 DOM 渲染到小程序视图上(以小程序为目标宿主平台):
那么打破砂锅问到底,Remax 是怎么将虚拟 DOM 渲染到小程序视图上的呢?
我们之前说过,小程序的架构是渲染层和逻辑层分离的双线程架构,小程序屏蔽了 DOM,我们的代码运行在一个 worker 线程中,无法直接去操作视图层的 DOM。
而之前也说了, Remax 是一个基本全运行时的小程序框架,是不会去将 JSX 静态编译成小程序模板的。而且小程序也不支持 DOM 操作,也就不能动态生成 DOM,那怎么在运行时将小程序视图模板生成出来了呢?
VNode
Remax 引入了一个叫做 VNode 的树结构(注意与虚拟 DOM 做区分),让 React 在 reconciliation (虚拟DOM 的 diff patch) 之后去通知渲染器(Remax)去修改 VNode。VNode 的基本结构如下:
我们不去关注具体每个属性是干啥的,从整体上看,VNode 实现了类似于 DOM 中的属性和节点操作方法(这里操作的对象是 VNode)。在执行数据更新之类的操作时,会调用相关方法去同步更新 VNode。在执行更新动作时,会调用 要更新的节点的 toJSON 方法生成一个 JSON 对象,这个对象会更新到小程序 Page 中的 data 里。
以下第一张图是 React 的 JSX 代码,第二张图是对应生成的 VNode 树结构:
之后要做的事情就是在小程序模板中如何把这个 data 给显示出来了。
我们之前说了,小程序不能直接操作 DOM,更别说动态生成 DOM 了,那么如何通过相对静态的小程序模板去生成这样相对动态的 VNode 树对应的视图呢?
模板递归调用
Remax 想了一个非常鸡贼的办法,小程序你不是不让我动态生成 DOM 嘛,那我提前把要生成的内容写好,作为一个个模板。当我要这些模板的时候动态引用不就行了?
可以看到,Remax 模板会先去遍历根节点的子元素,再根据每个子元素的类型选择对应的模板来渲染子元素,然后在每个模板中我们又会去遍历当前元素的子元素,以此把整个节点树递归遍历出来。
以上这张图仅仅是简化的示意图,而源码里有一些更复杂的处理。实际上 Remax 是通过 ejs 来生成这些模板的,并且增加了对元素属性、事件、模板递归问题的处理。下图是真正生成的模板的样子(有点长但并不复杂):
而在 React 状态更新时,会同步更新 VNode,并通过 diff 操作对比新旧 VNode,找出最小变化数据再更新到小程序 Page 的 data 中,从而更新小程序视图。
生命周期(扩展阅读)
在上面的一些介绍中,我们没有涉及到小程序生命周期方法在 Remax 中是如何正确地通知 React 组件的,也没有涉及到 VNode 是如何更新到小程序 Page 的 data 中去的。因为后者逻辑实际上包含在前者实现中的,而前者逻辑实现较为复杂,若要描述清楚则需要结合源码讲述,不适合快速吸收。
所以在这里就用简单的文字浅显地描述 Remax 对生命周期做的一些事情: Remax 写了一个 babel-loader 的插件,在编译时用小程序的 Page 函数包裹住了具体的逻辑(就是在实例化一个 Page 对象),从而让小程序可以正确地触发逻辑中的生命周期;在运行时动态地创建这些逻辑(通过调用 createPageConfig),在生命周期触发时去调用 React 组件的生命周期(与之前介绍的 Taro 2.x 中逻辑的转换很类似)。
关于生命周期这部分内容为扩展阅读,不太容易懂,注重理解整体设计,可暂时不关注细节。
实际上,Remax 是在编译时和运行时两个时期做了一些操作。以 Page 为例,在运行时,Remax 是通过一个
createPageConfig函数来创建Page的参数对象,该参数对象中编写了onLoad、onShow等小程序生命周期方法,在这些方法中会在onLoad时获取该Page对应的 React 组件,并通知react-reconciler调用appendInitialChild,Remax 在该函数内将子VNode挂载到指定的父VNode上(在挂载时会把一个更新请求逻辑推送到这个父VNode的container属性对应容器的更新队列里),在挂载后会触发react-reconciler的一个resetAfterCommit钩子,Remax 在这个钩子里根据container的更新队列中的更新请求信息来执行一些更新App和Page的逻辑(调用小程序的setData,$spliceData等),从而将更新后的VNode Tree更新到Page的data中,从而更新视图。之后通过Ref获取该 React 组件实例,并在其他生命周期方法触发时去调用该组件的生命周期方法。而在编译时,Remax 实现了一个babel-loader的插件,从而在编译时使用Page函数包裹住了createPageConfig返回的参数对象,让小程序可以正常地触发Page的生命周期方法,从而通过以上的步骤去调用Page对应的 React 组件的生命周期方法。
小结
通过以上操作,Remax 终是将 React 完全运行在小程序上了,并拥有完整的开发体验。而对于跨端功能,除了各端通用的一些运行时代码之外,Remax 对每个端都单独维护了一套对应的运行时代码,做的主要事情就是组件、API 以及模板的兼容,这部分就不再仔细展开了(值得一提的是 remax-toutiao 是字节的同学在支持 PR#166)。
Taro Next
终于轮到我们的 Taro Next 同学上场了~。接下来我们简单聊一聊 Taro Next 的设计思路。
设计思路与架构
Taro 团队在参考了 mpvue、Remax(主要是 Remax)等小程序框架的实现和设计思路后,突然顿悟了。他们开始站在浏览器的角度来思考前端的本质:无论使用哪个框架,React 也好,Vue 也罢,本质上都是 JavaScript 代码,最终代码运行都是去调用浏览器那几个 DOM/BOM 的 API,比如 appendChild、removeChild 等,这几个 API 实际上就是调用了浏览器提供的能力来做一些操作。
于是,Taro Next 参照这个思想,自己在运行时实现了一套精简高效版的 DOM 和 BOM (在 @tarojs/runtime 这个包里),并且通过 Webpack 的 ProvidePlugin 在运行时把 DOM、BOM 等 API 注入到小程序逻辑层,来给框架调用。这样一来,不管是什么框架,都能在 Taro Next 上运行。
话虽然是这么说,但由于小程序的固有的一些特殊逻辑,比如生命周期、App、Page、组件等,不同的框架之间存在一定的差异,也需要一些处理,并以不同的方式注入生命周期,所以在运行时还需要额外对每个框架维护一套逻辑。但这一套逻辑就相对简单了,Taro 团队未来也打算把这部分逻辑以插件的形式对外开放,从而让社区来维护以支持各种框架。
所以整体而言,Taro Next 在小程序上的一个架构如下图所示:
好了,Taro Next 的一个小程序框架的实现思路就讲完了。
什么?你说 Taro Next 的视图渲染更新过程我还没说?其实我在 Remax 的时候就说了~
视图渲染更新过程
以下是 Remax 和 Taro Next 的渲染流程在适配 React 上的对比:
虽然很不愿意承认,但通过源码分析之后发现,二者的相似度竟然高达90%。与 Remax 一样,Taro Next 在适配 React 时也实现了一个渲染器,叫 taro-react。react-reconciler 会通知他们去调用 appendChild 之类的 API ,去更新视图,Remax 是通过更新 VNode 后同步到小程序 Page 的 data 中的,而 Taro Next 是通过更新 TaroElement 后同步到小程序 Page 的 data 中的,只不过在同步到小程序之前,TaroElement 多做了一层 hydrate 处理,只会同步会影响视图的数据,从而减少了同步的数据量,优化了一定的性能。
同步了数据之后,再使用一些写好的模板(和 Remax 一样)通过递归调用的形式渲染出对应的视图。这个步骤是与 Remax 完全一致的。
那他俩到底有啥区别呢?
与 Remax 的区别
从二者对 React 上的渲染过程来看,确实看着没什么区别。
而实际上,二者的抽象程度是不同的,Remax 相当于是自己创造了一种数据结构,只是用于对接 react-reconciler ,用于小程序数据同步,是与 React 强绑定的,倘若更换为其他框架,会需要修改很多核心逻辑。而 Taro Next 则是参照浏览器中 Element、Node 等类型自己实现了一套简洁高效的 DOM/BOM 以及事件机制,虽然在 React 的渲染过程中很相似,但其可以使用很小的成本去添加其他框架的支持,这一点是 Remax 无法做到的。
除此之外,Taro 本身很多优秀的特性也是 Remax 所没有的。
现在,我们不在讨论 Taro Next 的实现以及与其他框架的对比,单纯从使用方的角度来看看 Taro Next 相比于 Taro 2.x 到底可以为我们带来些什么。
Taro Next 新特性
总结来说,Taro Next 相比于之前的 Taro 2.x 更强大、更迅速、更灵活。
更强大
无 DSL 限制:想要用 React 写小程序?可以!想要用 Vue 写小程序?也可以!想要用最新的 Vue3 来写小程序?都可以!无 DSL 限制的 Taro Next 满足我们各种框架的需要。
支持 HTML 渲染:想要直接支持标签字符串的直接渲染?可以!新的 Taro Next 实现机制让我们可以动态渲染我们想渲染的标签元素。
支持跨框架组件:Taro 利用 web components 提供了一套可以在多端以及多框架中使用的组件,并且当用户想要编写一些跨框架的组件时,也可以通过 Taro Next 提供的 API 进行编写。
更迅速
构建打包速度更快:根据 Taro Next 近乎全运行时架构,不再需要在编译时进行复杂 AST 操作,构建速度更快了。
运行时性能优化:Taro Next 的新架构变成近乎全运行之后,花了很多精力在性能优化上面:
- 模板大小恒定:由于 Taro Next 使用模板动态渲染,因此随着项目规模的增大,模板大小总是保持固定不变;
- 精简高效 DOM/BOM:在 Taro DOM Tree 的构建和更新阶段,实现了一套仅实现了高效的、精简版 DOM/BOM API,而且仅仅实现了必要的。相比于 js-dom,Taro DOM/BOM API 代码总共不到 1000 行,极大地保证了 Taro DOM Tree 的构建更新性能。
- 更细粒度的更新:Taro Next 的更新是 DOM 级的,相比于 Data 级的更新更加细粒度,因为某些 Data 并不会引起 DOM 的更新,Taro Next 在更新时做了 hydrate 操作来识别出会影响视图更新的 Data,从而减小了数据量,提高了性能。
更灵活
插件系统设计:Taro Next 提供了一套插件化的系统(灵感来源 Remax),用户可以编写 Taro 插件来根据业务场景拓展更多的能力。
编译配置露出:Taro Next 把一些内部编译时的一些配置以及 webpack 相关的配置暴露出来让用户自定义,比如说小程序递归调用时的那些模板的层数,全局常量注入等等。
自定义依赖加载:Taro Next 将自身依赖瘦身,不再维护类似 taro-redux,taro-mobx 之类的库,而是让用户在需要这些库时自行通过 npm 安装使用。
总结
本文就 Taro Next 的设计思想与架构展开了讨论,并没有对 Taro Next 的使用进行描述,因为个人认为 Taro Next 相比于 Taro 2.x 在使用上实际上是差别不大的(除了在 Taro 中使用 Vue),而此类文章已经非常多了,读者可以自行搜索查阅。
很多人认为,Taro Next 就是抄了一份 Remax,但我认为,设计思想的共通性在编程世界其实非常普遍,而编程中的一些架构设计也会参考现实世界的做法,因此争论这些个人认为是没什么意义的。而 Taro Next 在设计思想上相比于 Remax 也加深入,并且拥有非常多优秀的特性,也提供了较为完善的工具链和生态。社区活跃、团队可靠,迭代和 issue 相应速度也是几个小程序框架中最快的。
当然,Taro Next 目前也存在很多问题,从 Github 中 issue 数量中就可得知。但 Taro 拥有可靠的团队和活跃的社区,我觉得这些问题也会随着时间的推移逐步完善并解决。
参考资料
本文参考了较多资料,若侵犯了您的权益,请及时与我联系,我会及时修正或删除,谢谢!
[3] Taro
[4] Taro 原理总结
[5] Taro 官网
[7] Remax 原理浅析
[8] 用Vue.js开发微信小程序:开源框架mpvue解析
[9] React 源码概况