如何借助 Taro Next 横穿跨端业务线

转发链接:
大家下午好,今天我的分享主题是如何借助 Taro Next 横穿跨端业务线 。
前言、Taro 框架
Taro 是一款开源的多端统一开发框架,它让我们只编写一份代码,就可以让这份程序运行再各个小程序端、H5 端、RN 端,再开源两年多以来收获了业界的很多关注,然后现在在 github 上面的 Star 数有 25,000 多,同时业界也有非常多的团队正在使用 Taro 框架进行开发 。那么在过去的一年里面,我们谋划着一个比较大的重构,也就是我今天将要分享的 Taro Next 。
今天我的分享将会分为 5 个部分,首先是重构特有的背景 , 然后介绍 Taro Next 的一些新特性,接着是 Taro Next 的开发实践,然后会对 Taro Next 的部分原理进行简单分析析 。最后是对今天的分享做一个总结与展望 。
一、Taro Next 背景
首先来介绍一下为什么我们要重构,主要有两个原因 , 第一个是为了解决目前架构上面的一些缺陷,第二个是希望新增对于多个框架的支持 。
1.1、Taro 架构—
首先来看一下目前 Taro 的整体架构 , 它分为两个部分,第一部分是编译时,第二部分是运行时 。编译时会先对用户的 React 代码进行编译 , 转换成各个端上的小程序都可以运行的代码,然后再在各个小程序端上面都配上一个对应的运行时框架进行适配 , 最终让这份代码运行在各个小程序端上面 。那么这个架构有什么问题呢?
1.2、Taro 编译时缺陷—
首先看一下编译的实现过程:编译时是借助 babel 去对源代码进行词法、语法分析、语义分析,得出一棵抽象语法树 AST,然后对这棵抽象语法树进行一些转换操作,最终得出目标代码 。这样的实现过程共有三大缺点:
1.3、Taro 运行时缺陷—
接下来再看一下运行时的缺陷 。现在对于每个小程序平台,我们都会提供对应的一份运行时框架进行适配 。当我们修改一些 Bug 或者新增一些特性的时候 , 需要我们同时去修改多份运行时框架,这显然是不合理的 。
1.4、希望支持更多框架—
另一方面我们也希望去支持更多的框架 。目前 Taro 只支持 React,但是公司内部也有很多使用 Vue 技术栈的团队,她们也希望享受到 Taro 编译到多端的能力,因此我们就想能不能去支持 Vue,甚至 jQuery、Angular 等框架 。
二、Taro Next 新特性
说完重构 Taro 的原因之后,我们再来说说 Taro 重构之后 Taro Next 的新特性 。
2.1、Taro Next 架构—
这是 Taro Next 的整体架构图 , 即使各个 Framework 的框架拥有不同的组件库、生命周期、API,但我们仍然可以选定微信小程序作为一个基准,然后使用各种框架,去对接微信小程序的组件库、生命周期、API,然后在 Taro 运行时,内部对各个平台的缺失部分进行补全 。例如在小程序里面 , 它没有 DOM 和 BOM , 那我们就去实现对应的 DOM 和 BOM,然后在 H5 端并没有微信小程序规范的组件库、路由规范、API 等等,我们也要去实现,最后用 Webpack 去对运行时的代码进行打包,就可以让整份代码运行在不同的平台上面 。
2.2、Taro Next 优点一—
整体来说 Taro Next 有三个优点 。第一点是 Taro Next 更强大了 , 我们现在不限制语言和语法,可以运行各种真实的框架,还提供了渲染 HTML 和跨框架组件这些比较特色的功能 。
2.3、Taro Next 优点二—
第二点是 Taro Next 更迅速了 。我们剥除了 AST 操作,让构建速度更快 。其次,我们也提升了项目初始化、更新操作的性能 。除了 Taro 自身的优化外,我们还提供了一系列的优化选项给用户,让用户可以根据自己的实际情况对项目进行调用 。
2.4、Taro Next 优点三—
第三点是更灵活了 , 我们现在提供了一个插件化的系统,用户可以根据自己的场景去拓展一些自己的能力 。然后我们还把一些 Taro 的内部配置项、Webpack 的配置项给暴露出来,用户可以配置的项目就更加之多 。其次对 Taro 的依赖做了瘦身,现在 Taro 用户可以根据自己的实际情况去安装对应的依赖,而不是像以前一样把所有的依赖都打包到 Taro 的 cli 里面 。
三、开发实践
看完 Taro Next 新特性之后 , 再了解一下怎么样去使用 Taro 进行开发 。
这一部分会分为 5 个小节,首先是开发前的一些准备工作 , 然后是开始编码,项目进阶,多端开发,还有性能优化 。
3.1、准备工作—
先来看一下我们开发之前要做一些什么样的准备工作 。
3.1.1、环境准备
首先我们需要安装 Taro 的 CLI 工具 。这里分两种情况,如果是要新建一个项目的话,可以使用 init 命令去新建一个全新的项目;如果用户自己本来就有一个微信小程序项目的话,我们也提供了 convert 命令 , 让用户可以把自己的微信小程序项目给转换到 Taro 项目中来,得到 Taro 项目之后,可以运行 build 命令,然后把 Taro 项目运行到各个端上面 。
3.1.2、项目配置准备
在开始编码之前 , 我们首先需要去对 Taro 进行一些配置 。Taro 有一个配置文件 , 按区域划分,可分为公共配置、小程序配置、H5 配置 。按种类划分,它就分为 Taro 内部配置项和 Webpack 相关的配置项 。
3.1.3、babel 配置准备
接下来看一下对于 babel 的配置 , 目前 Taro 框架的 babel 配置是放在刚刚我们说的 Taro 配置里面的,但在 Taro Next 我们升级了 babel7,因此用户可以直接使用 babel 的配置文件 babel.config.js 进行配置,配置文件里面默认会带有 Taro 的 preset , 他有个比赛里面会有一些常用的 presets 和 plugins,比如 @babel/preset-env、@babel/plugin-transform-runtime 等等 。然后如果用户在使用 react 或者 typescript 时,也会去加入对应的 presets 。
3.1.4、App、Page 配置准备
接下来看一下 App 和 Page 配置 。和小程序相对应 ,  Taro 里面也有一个入口配置文件 app.config.js,每个页面也会有自己对应的配置,但和小程序、目前的 Taro 只能写 json 形式的配置不一样的是,Tarol Next 使用了@babel/register 去加载这些配置文件 , 因此用户可以使用 ES Next 的语法去编写这些配置,使用起来就会更加灵活 。
3.2、开始编码—
做完项目开始之前要做的配置之后,再看一下我们怎么样开始编码 。
3.2.1、入口组件
和小程序一样,我们首先编写一个入口组件 app.js 。从代码组织上面看,就是一个普通的 React 的 Component,还有 Vue 的实例 。首先我们需要去引入对应的框架,然后如果我们引入了样式的话,这份样式就会成为全局的样式 。那么在组件内部也可以使用到 Taro 提供的一系列生命周期方法 。入口组件会有一个 render 函数,render 函数里面是我们需要渲染的页面 。
3.2.2、页面组件
我们写完一个入口组件之后 , 就要开始编写一系列的页面了 。一个普通的页面组件 , 它同样也是一个 React 的 Component 或者 Vue 的 Component 。如果在页面上我们引用了样式文件,这份样式文件就会成为页面级别的样式 。同样的在页面内部也可以使用 Taro 提供的一系列生命周期方法 。需要注意的是,React 如果要使用 Taro 的一些基础组件,需要从 @tarojs/component 先 import 再使用 。而 Vue 则可以直接使用,因为我们借助 vue-loader 去分析用户使用了哪些基础组件 。
3.2.3、组件化
我们在编写页面的时候会写很多的组件 , 所有的框架,他们的组件化语法我们都是完美支持的 。但是这里需要注意的是,这些组件并不对应小程序的自定义组件,也就是说这些组件之间是没有样式隔离的 。假设这些组件都各自拥有一份样式,它们最后会被编译为页面样式的一部分 。
3.2.4、生命周期
接下来看一下我们可以使用哪些生命周期 。以页面的生命周期为例,Taro 的生命周期分为两部分:第一部分是框架原有的生命周期方法,第二部分就是会以小程序一些比较特色的生命周期方法进行拓展 。
3.2.5、组件库
下面介绍组件库 , 刚刚也有提及,组件在 React 里面需要先引用在使用,原因是什么呢?无论是 Vue 或 React,都需要去引用 Taro 的组件库,Taro 组件库在小程序端会直接使用小程序对应的组件,但是在 H5 端,因为只有 HTML 标签,并没有小程序规范的组件,因此我们就使用了 Web Components 去实现了对应微信小程序规范的组件库 。
3.2.6、API
接下来看一下 API,在小程序里面,所有的小程序规范的 API 在 Taro 里面都是完全支持的 。使用上我们首先要从 @tarojs/taro 包里面引入 Taro 对象,然后就可以去调用 Taro 对象里的小程序 API 。Taro API 它具体做了什么东西呢?在小程序端其实会直接调用小程序底层的 API,只是额外对这些 API 做了 Promise 化的操作 。在 H5 端,因为没有这些 API,所以我们对大部分常用的小程序规范的 API 进行了一层 Mock 。
3.2.7、Ref
最后来介绍一下 Ref,Taro Next 的 Ref 和目前 Taro 的实现有点不一样 。如果我们使用框架的 Ref 语法,获取到的会是 Taro 的 HTMLElement 元素 , 我们可以去获取 HTMLElement 元素的一些节点属性或方法,甚至可以设置 style 以此来去更新视图等 。但是如果我们要获取一些节点相关的信息,只能通过小程序提供的获取真实渲染节点的方法,去获取到视图层对应的渲染节点,然后再去获取它的尺寸的信息 。
3.3、项目进阶—
接下来是介绍项目进阶 。

如何借助 Taro Next 横穿跨端业务线

文章插图
3.3.1、状态管理
在我们的项目越来越庞大的时候 , 我们可能会引入一些状态管理工具 , 在目前的 Taro 架构中,如果我们要使用 redux 或 mobx 的话,需要官方先对这些状态管理工具进行兼容,对接 Taro 的渲染机制,然后用户才可以安装使用兼容之后的包 。
但是在 Taro Next 里面这些都是不需要的 , 用户可以直接使用 React-Redux、Mobx、Vuex 等状态管理工具 。
3.3.2、样式工具
接下来看一下样式工具,刚才也提到了组件之间其实是没有样式隔离的 。如果我们要使用样式作用域 , 我们可以使用 CSS Modules 。CSS Modules 的配置可以直接在 Taro 的配置文件里面开启 。如果用户忠爱于 CSS in JS 这种写法的话,可以使用 linaria 去实现,具体的用法在文档里面有介绍,这里则不作展开了 。
3.3.3、渲染 HTML
然后就是渲染 HTML,我们有时候会需要直接去渲染 HTML 字符串, React 的 dangerouslySetInnerHTML 或 Vue 的 v-html 指令都是可以直接使用的 。在我们实现这个功能的时候,我们也去看了业界很多优秀的 HTMLParser 方案 , 他们都非常棒 , 但是有两个问题 , 第一个是体积太大了 , 这样放在小程序里面就不太合适 。第二个是他们都会先解析成为 DOM 层 , 然后我们还需要去对 DOM 层进行一次便利,这样才能得到可以 setData 的数据 , 这里就会多了一层便利 。考虑到这两个问题 , 我们就自己实现了一个 HTMLParser,它能解析用户的 HTML 字符串,直接得到可以 setData 的数据结构,大大加快渲染 HTML 的速度 。
3.4、多端开发—
我们开发完一个项目之后,可能会有一些适配到多端的工作 , 接下来就介绍一下多端开发相关的知识 。
3.4.1、原生小程序页面/组件
首先是使用原生的小程序页面/组件,使用一个原生的小程序页面很简单 , 只要在入口配置文件里面去配置对应的路由,然后指向那些原生小程序页面就可以了 。如果需要使用原生小程序的组件,只要在页面配置里配置 usingComponents 字段,就可以在页面里面去使用这些原生小程序组件 。
3.4.2、跨平台开发
接下来是跨平台开发,这里我们提供了两种方式 , 第一种是环境变量,在 Taro 里面有一个环境变量叫 TARO_ENV,开发者可以使用它去做一些条件判断,根据不同平台使用不同的逻辑 。但是如果我们的代码里面有很多这些 if else 的话,其实是很难维护的 。
因此我们提供了第二种方式,多端文件,开发者可以像上图左侧所示来组织代码 。命名规则是文件名 + 平台名+ 后缀,然后在 import 的时候只需要去 import 对应的文件名就可以了 。在 Webpack 打包的时候会根据目前的平台再去引用对应的文件 。
3.4.3、跨框架开发
最后会介绍一下跨框架的开发 , 我们考虑到可能有些用户需要开发一些在 React 和 Vue 上可以同时使用的组件,我们也提供了一个类似 jQuery 的写法,让用户去编写一个跨框架的组件,然后我们就可以在 React 和 Vue 中同时使用这些组件了 。
3.5、性能优化—
在开发完一个项目之后 , 可能我们会去做一些性能优化的工作,接下来就介绍一下性能优化相关的知识 。
3.5.1、Taro 对 setData 的优化
首先介绍一下 Taro 对 setData 的优化 。第一点是 Taro Next 的 setData 是最小颗粒度的 setData 。考虑有以下场景,如果我们要 setData 一个很大的列表,我们在开发原生小程序的时候 , 常常会担心会不会把一些冗余的数据 setData 到视图层 。但是在 Taro Next 里面,我们首先会对这个数据做一个 Hydrate 操作,去识别出渲染时候真正使用到的数据 , 最终只会 setData 这些渲染用到的数据,这样就可以大大减少 setData 的数据量 。
第二点是按路径更新 。我们在更新的时候会对新旧的 Taro DOM Tree 进行对比,然后将 diff 的结果使用小程序的按路径更新语法进行 setData,同样也可以大大减少 setData 的数据量 。
3.5.2、预渲染
说完了 Taro 本身对于性能的一些优化之外,接下来介绍一下 Taro 提供给用户的一些优化选项 。第一个是预渲染 Prerender ,  Taro 的整体渲染流程大概如上图左侧 。React 或 Vue 首先会去渲染出 Taro 的 DOM 树,Taro 内部会对 Taro DOM 树进行一层 Hydrate 操作得出可以 setData 的数据,最后再把数据 setData 到视图层 。如果我们初始化的时候 , DOM 树非常复杂,那我们 setData 的数据量就会非常大,这样页面就会出现短暂的白屏 。因此我们提供了一个预渲染功能,用户可以使用预渲染的功能去预渲染出一个占位页面,然后等到 Taro DOM 树真正的 setData 完成之后,才展示可以真正交互的页面 。
3.5.3、React
如果用户在使用 React 的话,那么 shouldComponentUpdate、PureComponent、React.Memo页面渲染完成后执行js,这些方法都是可以使用的 。
3.5.4、长列表优化
接下来介绍的是长列表优化 。长列表在我们开发 web 应用时是一个常见的性能瓶颈,因此我们提供了一个 VirtualList 虚拟列表组件 , 虚拟列表组件只会渲染在可视窗口中可见的 item,超出视口之外的这些 item 会被占位的元素所代替,如此就可以限制我们渲染的 item 数量,使得列表的滚动更加顺滑 。
3.5.5、体积优化
最后看一下体积相关的优化 。Taro 的代码会使用 Webpack 进行打包 , 因此 Webpack 里面的优化工具我们都是可以使用的 。例如代码压缩、tree-shaking、side-effects 等 。如果用户还是觉得自己的包比较大,那么就可以使用 webpack-bundle-analyzer 插件去分析自己的包依赖,做出更加细致的优化 。
接下来说一下分包,如果用户的主包比较大 , 可以拆分出一些分包,分包功能在 Taro Next 里面是完全支持的 。但这里有一个问题,分包的 node_modules 依赖项默认会被打包到主包的 vendor.js 里面,这时候我们可以使用 splitChunks 做出更细致的优化 , 把分包对应的 node_modules 依赖单独进行拆分,把拆分后的文件放到对应的分包里面,进一步减少主包的大小 。
四、原理浅析
说完怎么样去使用 Taro Next 之后,相信大家已经对 Taro Next 产生了一定的兴趣,这里会对 Taro Next 的部分原理进行一个讲解 , 包括小程序及 H5 的实现原理 。
4.1、设计思路—
首先介绍一下我们在小程序端的设计思路 。可以看到无论是 React 或者 Vue,他们都会使用到浏览器的 DOM 和 BOM API,然后再渲染到浏览器上 。那么我们也同样可以在小程序里面实现一层 DOM 和 BOM , 从而让这些框架运行到小程序上 。
4.2、DOM 渲染方案—
但是这里有一个问题页面渲染完成后执行js,即使我们去得到一棵 Taro DOM 树,又要怎么样去 setData 到视图层?因为小程序并没有提供动态创建节点的能力,我们需要考虑如何使用相对静态的 wxml 来渲染相对动态的 Taro DOM 树 。我们使用了模板拼接的方式,根据运行时提供的 DOM 树数据结构,各 templates 递归地相互引用,最终可以渲染出对应的动态 DOM 树 。
这里先看一个比较简单的 view 模板实例 。上方是一个 view 组件模板 , 首先我们需要在 template 里面写一个 view,然后把它所有的属性全部列出来(把所有的属性都列出来是因为小程序里面不能去动态地添加属性) 。接下来是遍历渲染所有子节点,这些子节点首先会去引用中间层模板 , 然后中间层模板会根据对应的 nodeName,再去找到对应的组件模板 。通过这样的方式把模板一步一步拼接起来,就可以渲染出我们动态的 DOM 树 。
4.3、适配 React—
接下来看一下怎么样去适配 React 。React 架构主要分为 React Core、Reconcliers、Renderers 。但是 React DOM 渲染器的体积比较大,里面有很多兼容性代码,放到小程序里面的话就太大了 。因此我们就想自己去实现一个渲染器,我们可以提供一个 hostConfig 以对接 Taro DOM 的各 API,从而去实现一个小程序的渲染器 。
接下来看一下 React 的整体的渲染流程 。首先 React 会使用 taro-react 这个包里面提供的小程序的渲染器,然后再配合 taro-runtime 里面的 createReactPage 函数,去把页面渲染出对应的 Taro DOM 树,然后我们会对 Taro DOM 树做一个 Hydrate 操作,得到需要 setData 的数据 , 然后进行 setData,视图层会根据这些 data 数据对所有的模板进行拼接,从而渲染出对应的页面 。
4.4、Vue 渲染流程—
接下来看一下 Vue 的渲染流程 。因为 Vue 这边并没有很多冗余代码,因此我们可以直接使用 。Vue 同样需要配合
taro-runtime 包里面的 createVuePage 方法,把页面渲染出一个 Taro DOM 树,然后进行 setData,在视图层对这些模板拼接,最终渲染出对应页面 。
4.5、Taro Next 小程序端架构—
接下来看一下 Taro Next 小程序端的整体架构 。首先是用户的 React 或 Vue 的代码会通过 CLI 进行 Webpack 打包,其次在运行时我们会提供 React 和 Vue 对应的适配器进行适配,然后调用我们提供的 DOM 和 BOM API,最后把整个程序渲染到所有的小程序端上面 。
4.6、H5 端架构—
接下来看一下 H5 端的架构,同样的也是需要把用户的 React 或者 Vue 代码通过 Webpack 进行打包 。然后在运行时我们需要去做三件事情:第一件事情是我们需要去实现一个组件库,组件库需要同时给到 React 、Vue 甚至更加多的一些框架去使用,因此我们就使用了 Stencil 去实现了一个基于 WebComponents 且遵循微信小程序规范的组件库,第二、三件事是我们需要去实现一个小程序规范的 API 和路由机制,最终我们就可以把整个程序给运行在浏览器上面 。
五、总结与展望
最后说一下对于今天分享的一个总结与展望 。
—5.1、总结—
总结分为四点:
5.2、可定制化拓展—
下一步我们会添加可定制化拓展框架的能力,让 Taro 成为开放式的多端框架 。背景是我们现在最近正在适配 Vue3,但我们发现要适配 Vue3 我们需要去改动很多处源码相关的地方 , 而且源码里充斥着各种对于框架的条件判断代码 。因此我们就想把 Taro 设计得更加开放 。目标是可以直接以 Taro 插件的形式去新增对于框架的支持,而并不需要去改动 Taro 的源码 。
5.3、Taro3 新版本—
我们 Taro3 的正式版将会在 7 月初进行发布 , 相信本文发出时正式版已经发布了 。(正式版已经于 7 月 1 日发布,详情点击:Taro 3 正式版发布:开放式跨端跨框架解决方案)
【如何借助 Taro Next 横穿跨端业务线】本文到此结束,希望对大家有所帮助!

猜你喜欢