从 PC 时代、移动时代到万物互联的 IoT 时代,伴随终端设备的日趋多样化,跨端复用的种子自此落地,开始生根发芽。从依靠容器能力、各类离线化预装包的 Hybrid 方案,到通过 JSC 连接 JavaScript 生态与原生控件,结合视图框架(React、Vue等)寻找效率、动态性和性能更均衡的 Native 容器方案(React Native、Weex 等),接着由微信牵头的以多进程 WebView、容器标准化的小程序方案出世,各平台小程序随之春笋萌发,随后带来了国内Taro、uni-app、Rax、Remax等多端框架的百家争鸣。
从业务角度出发,跨端技术演进更多是在不同阶段、不同时间段内业务效率上的选择,美团民宿业务就是在大前端融合的浪潮中逐浪前行,不断探索和迭代抉择,为解决业务痛点而孵化出跨端框架技术。本文主要分享美团民宿在跨端复用技术探索层面以及业务实践过程中积累的经验,希望能给大家带来一些帮助或者启发。
1. 背景
1.1 美团民宿业务介绍
美团民宿专注为消费者提供“住得不一样”的旅居体验,提供的服务包括民宿、酒店、公寓、客栈、短租、宾馆、旅行住宿等,同时包括树屋、房车、INS 风等新奇的网红民宿。美团民宿自上线之后,业务发展迅猛,在供给侧,房源类型不断丰富,各类分销、直销、直连、境外陆续推出,房源信息维度不断扩展,筛选、推荐、信息呈现也不断变得复杂。同时伴随着营销方式的丰富、房东管理、经营、服务的不断扩充,民宿的业务也越来越复杂。美团民宿大前端伴随业务的发展不断自我迭代,移动端整体架构也随之不断调整、升级,以寻求匹配业务多样化、复杂化的发展诉求。
1.2 美团民宿移动端现状
业务的发展和跨端复用技术的不断演化,让美团民宿客户端从业务刚起步的单端 Native App,到跨 App(民宿 App、美团 App、点评 App )的 Native 复用和以 SSR 弥补性能差距的 Hybrid 的结合方案,在这场性能和效率的博弈中,客户端最终落脚以 React Native(以下简称 RN)为核心的复用框架。在此同时,民宿小程序端也随着微信小程序的诞生、生态壮大、多平台化的趋势不断成长,逐渐形成多平台复用的小程序架构。
上图是美团民宿移动端原始架构图,左侧是客户端的技术架构,iOS 和 Android 系统层之上是独立的 Native 基建层,再往上通过了 RN 打开双端的复用之门,接着以 RN 容器标准化屏蔽了宿主应用间差异,保障了容器化的一致性,进而实现了业务层的复用和跨 App 的复用。右侧是民宿小程序当前简化的架构图,我们在基建层做了多端适配,通过多平台复用构建工具实现了各平台小程序的复用。当前客户端和小程端相关独立,开发维护也相互独立,团队各司其职。
尽管美团民宿 App 已经通过 RN 实现 iOS 和 Android 的跨端复用,但是由于 App 和小程序仍然需要投入双倍的人力成本进行业务迭代,所以我们思考一个问题:是否可以更进一步,使用一套代码解决多端,把 iOS App、Android App、小程序进行大一统。
2. 美团民宿跨端复用框架设计
2.1 行业现状
近几年,在微信小程序产品牵头下,业界也随之诞生出各种小程序应用,各端技术差异使得开发和维护成本都成倍增加。为了抹平原生开发、小程序开发、Web 开发等技术差异,一些优秀多端框架也就此诞生了。比如 Taro、uni-app、Rax、Remax 等,这些框架都是以自身定义 DSL (一般是 React DSL、Vue DSL)转换成各端应用(微信小程序、RN、H5等),从而实现一套代码,多端运行。
在美团民宿业务中,App 的交易占比较大,从业务角度出发需优先保障 App 的性能体验和需求开发效率,而当前的民宿 App 已迁移至 RN 技术栈。基于这两点,我们希望跨端复用方案的是: RN 转到小程序平台方案,所以上述的多端框架并不能满足我们的 RN-小程序跨端复用的诉求,为此美团民宿参考了业界多端设计方案,实现了基于 RN 转小程序复用的方案。
RN 采用的是 React 语法,因此如何将 RN 转换为小程序,首先要思考如何将 React 代码转换成小程序可运行的代码(简称小程序代码),其次是 RN 基础组件库的适配。随着这几年的发展, React 代码转换成小程序代码在业界实践也是层出不穷,业界方案分为编译时与运行时两类,以下是这两类方案的简单对比:
框架分类 | 重运行 | 重运行 |
---|---|---|
典型代表 | Taro2.0 / Rax 编译时 | Taro Next / Remax |
原理 | 编译时将 React 代码直接转换成小程序代码 | 运行是通过 React 自定义渲染器完成页面绘制 |
优势 | 性能损耗低 | 无语法限制 |
劣势 | 语法限制大 | 性能损耗大 |
对比来看,重编译方案有一个严重的问题:语法限制。因为大部分前端开发者们已经对灵活的语法有一定的依赖性,比如会使用高阶组件、在条件判断的时候写很多 return 等等,这种写法很难在编译过程被准确命中。因此,编译时方案就会制定一些语法规则来限制开发者的写法。重运行方案则没有语法限制问题,可以随意使用各种 React 特性。它的实现原理是通过 react-reconciler 实现小程序平台对应的 React 渲染器(以下简称 MP-Renderer),从而来渲染虚拟 DOM 树。不过小程序没有 DOM API 可以更新界面,所以生成的虚拟 DOM 树数据是通过小程序的 setData 触发渲染层的更新,在渲染层里有一个通用模板可以用来渲染这些数据。
因重编译语法限制的问题,我们决定采用重运行时方案来实现 RN 转小程序。但重运行方案存在性能问题,难以满足业务的要求,我们经不断探索后设计了对应的方案极大提升了性能,下文会详细描述如何解决这个问题的。
2.2 整体方案设计
2.2.1 RN 与小程序复用的技术方案
整体架构分为两个部分:编译过程、运行过程。它的渲染方式与上文描述重运行时方案类似,都是通过 MP-Renderer 来处理 React 代码。下面我们来简要分析这两个过程:
(1) 编译过程:该阶段对 RN 源码进行一定的转换处理,用于运行过程,编译后主要产生有以下产物:
- 编译后的 RN :经过编译后产生 RN 代码,本质上还是 React 代码。
- 适配组件库:RN 基础组件的适配库,是使用小程序自定义组件实现的。
- 通用模板:由于小程序没有像 Web 有 DOM API 操作节点操作方法,所以这里通过一个通用模板来渲染 React 渲染出来的 TreeData (页面虚拟 DOM 树序列化后的 UI 数据)。
- 合并模板:主要用于性能优化的,下文会详细分析这个模板的作用。
- WXSS:将 RN 代码的 Style 转换为 WXSS,这样可以减少页面的 TreeData 数据量,从而优化性能。
(2) 运行过程:运行过程分为逻辑层和视图层两部分。
- 逻辑层:编译后的 RN 源码包含 RN 业务组件和适配组件库,适配组件库是通过小程序自定义组件来进行适配。这样的方式既可以灵活使用小程序原生代码对齐 RN 组件功能,也可以提升转换后小程序的性能,因为小程序原生代码不会产生 TreeData 数据,从而使性能上得到提升。逻辑层有一个 MP-Renderer ,实现方式和上文讲述的是一样的,RN 代码经过渲染后,便产生对应的虚拟 DOM 树,虚拟 DOM 树数据再经过序列化便产生对应的 TreeData(描述页面的 UI 数据)。
- 渲染层:当页面需要更新的时候,逻辑层通过 setData 将 TreeData 传输到渲染层里,TreeData 与通用模板、合并模板和对应样式结合在一起,便可以渲染出对应的 UI。
综上所述,上述整体设计与业界多端框架有点类似,但是也有不同点,主要体现在适配组件库和合并模板。适配组件库上文有解释比较好理解,而合并模板这里可能大家还是比较有疑惑的。其实这个合并模板内容是由编译过程的 “静态编译” 转换生成的,这样的处理方式是为提升转换后的小程序性能,接下来,我们会着重来讲述这个性能解决方案。
2.2.2 性能解决方案
重运行时方案性能损耗原因是什么?正如上文所说,重运行时方案会将所有 React 代码对应的 TreeData,再通过小程序 setData 传输到渲染层,当页面初始化或者大数据更新的话,setData 就需要传递比较大的一个数据,因此也就会造成对应的性能问题。所以要解决这种方案的性能问题,核心就是要减少 TreeData 数据量。
在上述 RN 转小程序方案,有提到适配组件库、样式转换等是可以起到对应性能优化作用的,它的优化原理正是通过减少 TreeData 数据的方式。尽管这些方式可以优化性能,但是在页面比较复杂的时候,TreeData 数据量仍然会保留比较大,因此优化效果并不明显。为此,我们思考一种新的方式来进一步压缩 TreeData 的数据量,也就是前文所提到的结合静态合并树节点方案,在讲述该方案前我们先来看下一个 RN 代码转换为 TreeData 的例子:
如上图所示,RN 代码转换后的 TreeData 是一个描述 UI 树的 JSON 数据,等同于右侧的 UI 树,将这颗树的节点进行分类,可以分为静态数据和动态数据,比如 View、Text 节点就是静态数据,而 “Hello”、“World” 则是动态数据。所谓静态数据,就是编译过程可预知的,因此这些数据是不是可以转换另一种形式来描述 UI 呢,从而减少 TreeData 的数据量。答案是肯定的,静态编译合并树节点正是通过这样的原理来实现的,如下流程所示:
这个方案有两个动作,分别是静态编译和合并树节点,静态编译就将 RN 代码的转换成合并模板,如上图序号 2 代码所示,合并模板的名称为 “b1”,内容就是一段与 RN JSX 代码对应的 WXML 结构片段。而合并节点是将已经静态编译的节点进行合并,如上图序号 2 至序号 3 流程所示,原本五个节点被合并到顶层的 View 节点,这个 View 节点称为合并节点,合并节点需要记录合并模板的名称和相关的动态数据,目的是为了渲染时让合并节点可以找到对应的合并模板进行渲染,经过这样合并节点后,最终生成的 TreeData,如上图序号 4 所示。可以看到 TreeData 相比之前的数据量就减少了 60% 左右!
看到这里,是不是有同学就有疑问了,上文不是提到静态编译会有语法限制,那这里是否会有语法限制?确实,如果是完全静态编译,是会有语法限制,而这里所说的结合静态编译是有选择性的编译,即在编译过程,首先会通过 AST 分析节点是否静态数据,如果是的话,再转换成对应的合并模板。如果遇到不可预测的动态节点,则按照运行时方案去处理。因此,最终生成的 UI 树节点即会包含合并节点、也会包含原本的组件节点,如下图所示:
通过这样的方式,既可以保证语法无限制,又能通过编译结合的手段最大化优化性能。当然了这种方案也是有缺点,因为这种方案其实是用空间换性能的方式,生成的合并模板会影响会影响包大小,不过对于一些需要追求性能的页面,这点包大小的增加是值得付出的。
为了更好地衡量解决方案对性能的提升程度,我们参考 Taro 官网的实验(实验内容),对优化前后以及原生和 Taro 3.0 运行后的性能指标进行采集与比较。经过实验,统计出各框架在初始化、加载数据、加载大量数据的操作耗时,如下表所示:
操作耗时 \ 框架 | 优化前 | 优化后 | 原生 | Taro 3.0.17 |
---|---|---|---|---|
初始化(首屏渲染时间) | 897ms | 423ms | 210ms | 675ms |
加载普通数据(20条) | 1124ms | 198ms | 110ms | 640ms |
加载大量数据(400条) | 5330ms | 1041ms | 470ms | 3919ms |
从上表中可以看出:性能优化后,得益于更少的渲染数据与更精简的节点树,加载数据的操作耗时比优化前减少 80% ,初始化耗时减少了 52%。与同类型的框架 Taro 3.0 相比,也有更好的性能表现。
与原生相比,优化后性能差距明显减少,但是由于运行时方案相对于原生需要更多的 setData 数据开销和更复杂渲染流程,所以从原理上运行时方案和原生性能差距客观存在。尽管如此,业务实践上两者差距并不会那么明显,因为在测评实验中测试数据比较纯粹,setData 数据使用率较高,但在业务实践中原生开发 setData 数据难免冗余且难以优化,而运行时方案会默认优化冗余数据使得两者性能差距更接近,从我们历史业务实践数据上看,性能与原生差距在 10% 左右。
3. 美团民宿跨端复用实践
在跨端复用探索中,我们用创新的方案解决了性能和特性限制的难题,设计了 RN-小程序跨端复用框架。虽然跨端复用属于“利器在手”,但是这是一把“双刃剑”,用得其所则事半功倍,处理不当则隐患丛生。那么,如何在业务实践中驾驭好这把利刃呢?我们先介绍在业务实践中遇到的问题,然后介绍解决这些问题的方案。
3.1 跨端复用场景下的问题
- 复用场景下的问题:小程序产品形态以轻、快、便为旨,用户可快速使用,用完即走,客户端产品相对全、精、稳,可以满足更多的用户需求,以用户留存、用户认知、用户体验为主,两者在产品功能上存在较大的差异,如何恰当地处理产品差异化问题是跨端复用的场景下的一个重要挑战。
- 跨端复用质量隐患:实现了复用便要考虑两端的各种兼容性问题,这就会产生各种质量上的隐患。如何在复用组件不断迭代中,保障组件接口、输入、输出的兼容性问题?如何保障各个复用组件底层依赖的统一、适配层接口的统一?双端复用场景下,如何更好的做测试和监控?双端同学存在各自技术认知的边界,如何在出现问题时快速排查、及时止损?
- 跨端复用流程规范问题:新的技术革命,必然打破旧的秩序,在当前跨端复用场景下,各种包括工程管理、代码规范、分支管理、需求同步的问题也会孕育而生,同需解决。
3.2 跨端复用应用架构
为了解决跨端复用在业务实践中遇到的各种问题,我们重新设计了跨端复用应用架构,从架构分层管理、复用方式设计、流程规范、质量保障方面入手,重点解决跨端差异化、质量隐患、流程规范各种问题,并寻求复用的最大化和性能上的均衡。
3.2.1 跨端复用应用架构演进
在这里,先贴出动态的架构演进过程,让大家有一个宏观的认识。我们先简单地描述下演进过程,后续会基于最终的架构图再做详细的介绍。大致演进过程如下:
- 起初,客户端分 Android App 和 iOS App 单独开发,引入 RN 技术实现了 Android 和 iOS 跨端复用,但是小程序端依然需要单独维护迭代。
- 为了跟进一步实现 RN-小程序跨端复用,我们接入了自研的 RN-小程序跨端复用框架,并基于框架的适配规范,以 RN 的基建为基准,打造出一个和 RN 基建统一接口的小程序适配层。
- 完成小程序渲染器接入(MP-Render)和小程序适配层后,React-Reconciler 这一层就可以打通到小程序侧,实现了 React 代码复用到小程序的能力。
- 实现 RN 与小程序间的复用后,就可以对存量的 RN 代码进行抽象、适配、整理,进而抽取出一个组件复用层,这个复用层可直接供上层业务层直接使用。
- 最后,为了解决跨端复用场景下各种流程、协作和质量隐患,我们配套了相应的流程规范和质量保障措施。
3.2.2 跨端复用应用架构整体介绍
整个民宿的 RN-小程序跨端复用架构图如上,我们按照从下到上,从左到右的视角进行解读:
- 系统层:最底层是系统服务,除了 iOS 系统和 Android 系统外,我们把小程序视为一个单独的系统模块。
- 基础服务层:系统服务之上是基础服务层,这一层主要是集团基于 Native 和小程序建设的基建,全公司通用,覆盖了研发工程中方方面面的基础服务。在此基础上,我们在小程序基建中引入了基于 react-reconciler 实现的小程序运行时渲染器(MP-Render),这个渲染器能在运行时动态更新 vnode 以匹配编译转化的小程序 UI 模板,调用小程序原生 API,最终渲染出小程序组件,有了这个基于 React 的小程序渲染器便使得跨端复用成为可能。
- 基建层:基础服务层之上是基建层,这块主要包括 MRN 基建和小程序适配层,我们以 MRN 的基建为标准,适配出一个统一标准和统一接口的小程序适配库,通过这一层适配,上层可以无感知、无差异地以同一标准实现复用组件。其中适配层分为 2 块,下半部分主要适配 RN 基础服务,上层是民宿业务独立封装的基础库和第三方库,这块我们单独引入一个名为 Mapping 的适配库。一个独立的适配库可以让 RN 和小程序在业务迭代和技术变革过程中相互独立,互不干扰,如此就能保障技术的推进完全不会影响业务的迭代。基建层的最上方是 react-reconciler,React 框架本身就是把协调过程和渲染过程分开的,react-reconciler 是实现跨端复用的核心,所以我们把它单独展示出来,它真正打通了客户端和小程序的隔阂,只要有了一个独立的小程序渲染器,就可以全面、无限制的把 React 代码复用到小程序。
- 复用层:基建层再往上是复用层,复用层主要以组件维度做复用,复用组件是基于存量 RN 组件做抽象和适配,然后抽取独立出来,复用层的组件以统一的标准和接口供上层业务使用。复用层是很重要的一块,好的复用机制能帮助我们解决前面提到的产品差异化问题和复用最大化问题。这块我们单独放到
3.3 跨端复用方式设计
来详细讲解。 - 业务层:复用层之上就是业务层,业务层的各模块主要以页面容器来承接复用组件,基于不同的端和产品差异,可以灵活、动态配置页面的组件来满意业务的差异化需求。
3.3 跨端复用方式设计
差异化问题,一直是跨端复用场景中的一个痛点,双端的产品上、平台上、代码上的差异如何妥善的处理、适配,也是我们一直思考的问题。而好的差异化处理方案可以提升代码的可维护性、降低质量隐患、提升开发效率。我们从复用设计层面出发,探索出页面复用模式、组件复用模式、“组件+逻辑复用”模式等三种复用设计方式,并且根据不同的场景下采用不同的复用模式,可以较好地处理跨端差异化问题,同时能兼顾效率提升、性能体验和可维护性。
3.3.1 差异化下的复用方式
我们自研的复用框架提供两种复用模式,如下图所示:
页面复用模式:页面模式基于页面维度的,可以直接把页面的网络层、逻辑层、数据层以及页面内的组件集全部转换复用,这样可以达到复用的最大化,代码复用率能达到 90% 以上,人效提升明显。 组件复用模式:组件模式是基于组件维度的,复用以页面中的业务组件为目标,把页面的所有组件抽象、解耦、规范化之后抽取为复用组件。组件模式只能复用组件内代码,对于页面容器的逻辑交互、网络层都需要小程序自己实现,代码复用率相对较低,但是组件复用更灵活、可控,可随意插拔、拼接、定制。
以下是两种复用模式的优劣分析。
页面复用模式
优势
- 提效明显:整个页面包括所有组件、页面逻辑层网络层一并打包转换复用,代码复用率极高,开发效率提升幅度更大。 2) 接入成本低:整个页面直接转化同步复用,无需小程序同学协助接入,减少双端协助、接口沟通带来的出错风险。
劣势
- 灵活性低:业务差异和小程序特性不易处理,双端差异适配只能在 RN 上做,代码易出错,维护成本高。 2) 性能劣势:整体页面由 RN 转换复用而来,页面一次性渲染,性能上会略差一些,而且做页面级的性能优化困难。 3) 包大小风险大:整页复用情况下包大小较大,且不能动态调配(比如页面内某一模块需求迭代较少,不想复用,但是页面模式做不到动态移除)。
组件复用模式
优势
- 轻便灵活:组件如插件般可随意插拔、拼接、定制,可较好解决 App 和小程序双端的差异性问题,针对差异点双端可以独立实现,提高项目的可维护性。 2) 性能较好:页面容器依然是小程序原生组件,如滚动、滑动组件采用原生可减少性能损耗,另外组件分布式 setData 渲染有更好的性能,不会像整页一次性渲染导致 setData 数据量较大影响首屏加载性能。 3) 性能优化空间大:不会影响做页面维度的性能优化(如首屏优先、请求前置)。 4) 包大小可控:组件是否复用可以动态调配,比如把页面中迭代较少的组件不复用以减少包大小。
劣势
- 提效有限:组件模式只能复用组件内的代码,代码复用率较低,页面容器、逻辑层、网络层小程序依然要自己维护一份代码。
- 复用组件维护成本高:组件的接口要考虑组件升级迭代的兼容性、可维护性问题,管理不当,容易产生质量隐患。 3) 接入成本较高:小程序需要实现 RN 的页面逻辑,然后按照组件接口进行接入,有更高的接入成本。
两组复用模式各有利弊,页面模式复用率高,但是灵活性低、性能欠佳;组件模式轻便灵活,性能可控,能较好的处理平台差异化问,但是复用率低、维护成本高。我们在想有没有一种方案能保留组件模式的灵活性,又能降低组件维护成本、提高复用程度。在业务实践中,我们探索出一套“组件+逻辑复用”的模式,可以较好地解决上面提到的问题。
3.3.2 差异化下的逻辑复用
“组件+逻辑复用”模式依然保留组件复用的方式,但是在组件复用基础上增加了逻辑层(包括页面逻辑、网络、数据层)的复用,这样保留了组件灵活性,也增加了复用性。具体设计如下图:
整个组件+逻辑复用模式设计图如上,我们按照图片标注的序号进行一一解读:
- 逻辑复用接口实例:在小程序的页面容器中,通过注入的方式获取逻辑层复用的接口实例,通过这个实例便可以调用接口实现获取、更改、监听 Redux 的状态,实质上就达到了逻辑复用的效果。 2) 页面复用组件集:页面可以自由使用复用组件,复用组件可大可小,可以虽然拼装布局,保留了组件模式良好的灵活性。 3) 小程序原生组件:页面既可以使用复用组件,也可以用小程序原生组件来实现小程序差异化的功能和特性,这样能较好的处理双端差异性。小程序原生组件可以通过 逻辑复用接口实例 来调用逻辑层功能,进而达到逻辑复用的效果。 4) 弹窗复用组件:弹窗复用组件和页面复用组件同理,这边主要说明可以按照各类维度把复用组件分类,进而更好的做复用组件管理。 5) 复用组件库:复用组件库的复用组件可多可少,可大可小,如果页面双端差异性小,一个大组件即可满足。每个复用组件集外层包一层 Reudx-Provider 并设置相同的 Store,便可以和逻辑层自动绑定上。因为 RN 组件本身就是基于 Redux 的,所以复用过程相对容易。 6) 业务逻辑层:最右侧的业务逻辑层可以简单理解成3块,一块是基于 Redux Store 的数据层,这里存放整个页面模块所有的数据和操作、监听数据的接口,一块是包含页面内所有网络请求的网络层,另外就是用来流转状态和处理复用的 Reducer、Redux-Saga,以及配套的各种工具类。业务逻辑层可以根据双端的差异把 Reducer 与 Saga 分拆更小的单元实现差异化的逻辑复用,提升逻辑复用层的代码可维护性。 7) 封装复用接口:业务逻辑层包含整个页面的业务逻辑,只要针对性开放接口给小程序,让小程序可能获取、更改、监听 Redux 的状态,那实质上就达到了逻辑复用的效果。开放接口给小程序有 2 种方式:逻辑 API 接口 和 Store。 8) 逻辑API接口:基于 Store 给小程序提供小程序真正需要的逻辑 API 接口,通过这些 API 小程序可以来获取数据来渲染 UI(如:渲染没有复用的组件),也可以更新数据,也能监听复用组件内部的数据变化。 9) Store:把 Redux Store 暴露出去,小程序便利用 Store 实例可以通过 getState、dispatch、subscribe来操作、监听状态机了,也就达到逻辑层复用的目的了。
这种方案的优势很明显,它保留组件模式的灵活特性,可以比较方便做差异化处理和性能优化。而逻辑复用层把 Redux 包含进来了,这样不仅转化容易、不易出错,而且逻辑复用接口基于 Redux 的 Store,接口较好设计,容易维护、不易出错。而对于逻辑层,可以根据业务上一些差异做 Reducer 与 Saga 分拆,把不需要复用的代码逻辑排查在外,逻辑层复用也可以做到像组件一样热插拔,按需引入,这样也比较好做差异化代码管理,挺高项目的可维护性,同时也能优先减少包大小风险。
3.4 跨端复用流程规范
为在代码跨端复用过程中尽可能提升开发效率并避免引入质量问题,我们制定了差异化编码规范、需求同步规范、复用组件规范等开发流程规范,以下将通过 RN 到小程序产品需求同步过程进行简单的介绍。
1. 评估业务需求是否需要同步
针对 PM 提出需要同步的需求,客户端尽量将 RN 业务代码复用至小程序,以提升开发效率。无需同步的需求将通过差异编码规范进行控制,避免同步至小程序后增加潜在风险与测试成本。通常可使用平台判断(如 iOS、Android、WX_Platform )的方式控制业务代码是否打入复用组件包,也可通过 module.rn.js、module.wx.js 不同后缀文件方式完成相同接口不同逻辑的实现。
2. 评估是否有关联依赖需求
如明确业务需求需要同步,先判断该需求是否有前置需求依赖,再评估技术方案。如无依赖可直接开始复用适配工作;如有依赖,需判断前置需求能否一起同步或做适当降级,以此递推,避免因前置依赖需求未同步出现不符合预期的问题。
3. 制定 RN 组件适配与小程序接入方案
明确需求同步范围评估工作后,需完成以下技术评估工作:(1)明确需求是否需要新建复用组件还是在原有的复用组件上进行迭代。如需新建复用组件 NPM 包,需根据组件复用规范进行技术选型,确定使用“组件+逻辑复用模式”、“页面模式”还是“组件模式”,并制定相应的复用组件接口协议;(2)明确该需求是否需要开发 RN-小程序映射方法、组件,并评估相应的开发量。完成技术评估后需提前与小程序侧沟通接入排期。
4. RN 组件适配开发
客户端完成 RN 侧需求开发后,便可进行复用组件适配小程序开发。完成适配开发工作后需在 RN 页面与小程序 Demo 页面中对复用组件同时进行测试,避免在适配小程序过程中引入 RN 页面 Bug。复用组件测试完毕后将 NPM 包以及相应的接口文档提供给小程序接入,但在打包前需严格审查当前版本与上个版本间的 diff,避免不符合预期的代码也被同步至小程序。
5. 小程序接入 RN 适配组件
适配完成后将组件打包提供给小程序侧接入,接入后需在美团民宿小程序环境下再次进行自测。原则上客户端同学提供适配好的 RN 组件后,由小程序侧同学接入并测试,但我们也鼓励客户端在完成 RN 组件开发与复用适配后,一并完成小程序侧的组件接入工作,这样需求开发完整度更高,并能有效减少跨端开发下的沟通成本。后续随着大前端融合推进,RN-小程序代码复用率将逐步提升,客户端(iOS、Android)与 小程序代码将倾向由一名同学完成多端开发。
6. RN 适配代码合入迭代分支
需求在小程序测试完毕后,将 RN 组件适配 Feature 分支代码合入 Release 迭代分支,并在客户端(iOS、Android)打包上线。
3.5 跨端复用质量保障
跨端复用场景下存在包括复用组件接口兼容性问题、组件间的依赖隐患问题、测试和监控的缺失问题,以及故障排查困难等各种质量隐患,我们在业务实践中,也探索出一系列解决这些隐患的质量保障措施,包括组件接口维护、组件依赖管理、双重自测卡控、异常监控融合、双端故障 SOP、跨端复用流程规范 。这些措施能有效保障复用场景下双端的线上质量,民宿业务在跨端复用推进中,因为这些措施的保障护航,没有出现任何的线上故障。
1. 组件接口维护
复用组件随着业务迭代会不断更新升级,组件升级过程中便会带来的组件接口、输入、输出的变动,进而产生兼容性隐患,比如组件输入参数类型变动,而小程序端或RN端没有及时兼容或者未知晓,非常容易引发线上质量问题。为此,我们制定了组件接口维护计划,包括复用组件接口规范、组件版本管理规范、组件接口文档建设等。复用组件接口规范要求复用组件接口、参数必须严格按照规范来,如参数类型使用基础类型、只增少减原则、接口命名清晰、参数个数限制等等,减少双端的接入组件难度,避免参数频繁变动产生质量隐患。组件版本管理规范要求组件版本升级必须遵循語意化 2.0,并且有相应的版本升级文档。组件接口文档建设也是很重要的一环,每个复用组件都有相应的文档维护,记录参数的增删改查,接入方对组件接口变动一目了然,自然减少了接入风险。
2. 组件依赖管理
组件依赖主要存在两个问题,第一,复用框架本身也在不断升级优化、新的复用组件可能用新的编译版本转化而来并且依赖新的运行时渲染器,但是旧的复用组件可能会出现不兼容问题,因此我们开发相关的工具,如果组件依赖的运行时渲染器版本和小程序内置的不一致就会发出警告,提示组件兼容性问题。第二,因为不同的复用组件来自不同的RN模块,它们可能依赖不同版本的第三方库,容易产生版本不一致的质量问题。目前的解决方案是把这些依赖库分别打入各自的包里,这样复用组件间依赖相互独立,互不影响。再结合 Tree-Sharking 的优化,打入的依赖的真实包大小并不大,用小量的包大小换取更稳健的质量保证。
3. 双重自测卡控
在跨端复用场景下,一个复用模块的改动要考虑双端兼容和新旧版兼容问题,相比与之前有更高的出错风险,更全面的自测能帮我们尽早暴露问题,减少故障风险。所以我们在 App 侧和小程序侧做了代码自测覆盖率卡控,要求改动代码执行覆盖率超过 90% 才能提测和上线。复用组件既在 RN 侧自测过一遍,在小程序接入后又强制要求再自测一遍,双重自测卡控更能保障组件质量和线上质量。
4. 异常监控融合
RN 和小程序侧都有单独的异常监控机制,包括 JS 异常监控、API 异常监控、自定义异常监控等。但是双端的异常监控机制差别较大,在复用场景下两者交叉混用导致异常监控体系混乱,上报数据格式、策略、日志不统一而造成监控体系误告、漏告、排查困难、运维混乱等问题。所以我们把双端的异常监控模块打通,适配了底层异常上报逻辑,统一了双端的上报规范,告警策略、日志、处理流程。异常监控体系双端融合后,异常上报、监控、运维都顺畅许多,也帮我们发现不少的线上异常,是 RN-小程序跨端复用场景线上质量的坚固屏障。
5. 双端故障SOP
鉴于双端同学存在技术上的隔阂和信息不对称,当出现复用组件的故障或异常时,如何快速排查问题成为一个痛点,小程序的错误日志 RN 同学不熟悉,小程序同学不熟悉 RN 的业务代码实现,框架层面的错误更难排查。为此,我们整体了梳理双端故障 SOP,这里面包括常见日志分析帮助鉴别是复用组件、小程序端、底层复用框架的问题和相应的解决方案,同时开发了 Source Map 错误反解工具协助RN同学反解小程序日志帮助快速排错等等。这些 SOP 和工具能够在第一时间帮助双端同学自主或协助排查相关故障,快速止损。
6. 跨端复用流程规范
流程规范包括前面提到的复用组件规范、编码规范、需求同步规范、分支管理规范等等也是质量保障的重要的一环,它让研发流水线每一环都有严格的法律约束,保障整条研发流水线最终能把完整的产品交付到用户手里。
3.6 成果
RN-小程序跨端复用的设计方案在业务实践中不断完善,探索出效率相对最大化的复用模式。从开发效率角度来看,提升显著。我们总结了代码复用率与人效提升率来评估效率的提升,两个指标具体计算公式如下:
- 代码复用率:∑(RN 复用模块代码行数-模块中 RN 与小程序平台分支判断代码行数) / ∑(RN 代码总行数+小程序原生代码行数);其中,RN 复用模块代码行数是根据框架转换生成的组件来确定。
- 人效提升率:∑(RN 开发耗时 + 小程序开发耗时 - (RN 开发耗时 + 转换适配耗时)) / ∑(RN 开发耗时 + 小程序开发耗时);复用前需要RN与小程序侧两端的开发耗时,复用后只需要 RN 开发与复用组件转换适配的耗时,根据复用前后的耗时可以得出人效提升率公式。
根据转换采用的模式不同,可以得出代码复用率与人效提升率,如下表所示:
转换模式 | 代码复用率 | 人效提升率 |
---|---|---|
组件+逻辑复用模式 | 76% | 42.36% |
页面复用模式 | 91% | 46.71% |
从表中可以看出,页面转换模式复用了页面与组件的代码,代码复用率可以达到 90% 以上;组件复用模式复用了组件与部分业务逻辑代码,复用率也可以达到 76%。在人效提升方面,所有模式都能达到较高的人效提升率,代码复用率越高人效提升率也越高,页面转换模式可以复用页面与数据状态处理逻辑人效提升比组件转换模式更高。
4. 总结
民宿大前端团队为解双端研发效率之痛,倾力而寻跨端之技,浅尝百草、深谙其理而后自建之,举偏补弊、终解跨端框架性能之桎梏,青出蓝而胜于蓝。而后践于实业,瑕弊昭然若揭。为此,重设框架以谋其变(复用架构设计),寻之新式以尽其效(复用模式设计),立之新法以固其序(跨端复用流程规范),磨之利器以护其城(跨端复用质量保障),至此成果初成。然朝夕变化不休,路漫远兮,吾当持之求索以适其变、顺其道。跨端复用前行之鉴,故记以文,望有启示,文毕。
作者简介
凯林、森伟、熙辰、戈弋、少元等,均为美团民宿前端团队研发工程师。
原文:https://tech.meituan.com/2021/06/10/react-native-hybrid-practice-dsl-in-meituan.html,如有侵权,请联系删除。
评论(0)