近年来,伴随着大前端概念的提出和兴起,移动端和前端的边界变得愈来愈模糊,涌现了一大批移动跨平台开发框架和模式。从早期的PhoneGap、inoc等Hybird技术,到如今耳熟能详的React Native、Weex和Flutter等技术,无不体现着移动端开发的前端化。而提供一套三端统一的开发框架,一直是前端奋斗的目标,而React Native就是这么一个不错的三端统一的跨平台开发框架,这方面的知识能够参考我以前出的《React Native移动开发实战》关于跨平台相关的内容分析。html
注:本文原文地址开源的 React Native 组件库前端
一个 React Native 应用的基础组件库,基于 0.53.3 版本,提供一整套开箱即用的高质量组件,包含 JS 组件和复合组件(包含 Native 代码),涉及 FE、iOS、Android 三端技术,兼顾通用性和定制化,支持自定义主题,用于开发和服务企业级移动应用。开源地址:github.com/meituan/bee…node
据称,beeshell已经被普遍使用在美团外卖的多条业务线,经过了各类业务场景、操做系统、机型的实战考验,具有很好的稳定性、安全性和易用性等特色,基于此,美团将此开源出来以供你们使用和借鉴。react
在beeshell开源以前,React Native社区已经出现了不少流行且著名的脚手架框架。此处选取 Github Star 数 5000 以上的组件库,并从组件数量、通用性、定制化、是否包含原生功能、文档完善程度五个维度来进行对比分析。git
组件库 | 组件数量 | 通用性 | 定制化 | 是否包含原生功能 | 文档完善程度 |
---|---|---|---|---|---|
react-native-elements | 16 | 强,提供一套风格一致的 UI 控件 | 弱,若要定制化可能须要重写 | 否 | 高 |
NativeBase | 28 | 强,提供一套风格一致的 UI 控件 | 中,支持主题变量 | 是 | 高 |
ant-design-mobile | 41 | 强,提供一套风格一致的 UI 控件 | 中,部分能够支持定制化需求 | 是 | 低 |
beeshell | 25 | 强,提供一套风格一致的 UI 控件 | 强,不只支持主题变量,还支持使用继承的方式进行定制化扩展 | 是 | 高 |
经过对比能够看出,beeshell 只在组件数量上稍有劣势,在其余方面都一致或者优于其余项目。由于 beeshell 具有了良好的系统架构,因此丰富组件数量只时间问题,并且咱们团队也已经有了详细的规划来完善数量上的不足。github
系统设计是将一个实际问题转换成相应解决方案的主动过程,是解决办法的描述。在通用的软件工程模型中,需求分析完成后的第一步就是系统设计。一个项目最终的稳定性、易用性在很大程度上也取决于系统设计这一步。shell
beeshell 组件库是为了更加快速的搭建移动端应用,为业务开发提供基础技术支持,大幅提高开发人效。然而,面对不一样的业务方、不一样的功能需求、不一样的 UI 规范与交互方式,如何有效的兼顾全部的需求?这对系统设计提出了更高的要求,下面以抽象层次逐层下降的方式来详细介绍 beeshell 的系统设计。npm
beeshell 组件库基于 React Native,向下经过 React Native 与 iOS、Android 平台进行系统层面的交互,向上提供开发者友好的统一接口,抹平平台差别,为用户开发业务功能提供服务支持。beeshell 扮演了一个中间者的角色,从而保证了移动端应用基础功能的稳定性、易用性。其框架的设计原理以下图。编程
为了更好的介绍beeshell,咱们来看一下beeshell设计上的一些细节。总体上使用 JS 做为统一入口,多层封装隐藏实现细节,抹平 JS 与 Native、iOS 平台与 Android 平台的差别,开箱即用,下降了用户的学习和使用成本。局部上基于 React Native 的技术特色,分红 JS 组件部分和复合组件部分,两部分推行“松耦合”的开发模式,使得 Native 部分拥有替换变动的能力,提高组件库的灵活性。 react-native
复合组件部分能够直接暴露 JS 接口,若是有须要,也能够在 JS 组件部分进行定制化封装。咱们尽可能保证 Native 部分功能的原子性、简洁性,有任何定制化需求都使用 JS 来统一实现,遵循 JS 实现优先的设计原则,保证跨平台通用的特性。为了达到上面的要求,下面从JS 组件部分和复合组件两个部分来介绍。
一个软件的设计分为三个设计层次:体系结构、代码设计和可执行设计。咱们使用自上而下的方法,从体系结构开始进行 JS 组件部分的设计。
软件的体系结构的风格一般有 7 种:管道和过滤器,面向对象,隐式请求,层次化,知识库,解释程序和过程控制。
JS 组件部分使用了层次化的体系结构风格,总体分红三层:基础工具、通用组件、扩展组件,从上到下通用性逐渐减弱、定制化逐渐加强,功能渐进式加强,经过分层设计,各层各司其职,兼顾通用性和定制化。
咱们扩展组件部分会提供大量的定制化组件,若是仍然不能知足需求,用户就能够借鉴扩展组件的实现,根据本身业务需求,在某一继承层级上继承通用组件,自行进行定制化扩展,这点充分体现了 beeshell 定制化的能力。
既然是 React Native 组件库固然少不了 Native 部分,复合组件包含 Native 的功能。beeshell 组件库已经完成了 Native 部分的集成方案与规范,有良好的开发与使用体验,能够不断的集成原生功能。
复合组件部分经过 JS 封装接口,保证了跨平台。Native 部分主要分红 Native Bridge 和纯 Native 两大部分,Bridge 是针对 React Native 的封装,必须在组件库中实现;而纯 Native 部分则能够经过 Pods/Gradle 依赖三方实现,有效的吸取利用原生开发的技术积累。
React Native 提供了一些内置组件,咱们能使用 JS 来实现功能都是基于这些内置组件,这些内置的组件一些是跨平台通用的组件,如:View、Text、TextInput;而另外一些是两个平台分别实现的,如 DatePickerIOS 和 DatePickerAndroid、AlertIOS 和 ToastAndroid。例如:
iOS 平台的 DatePickerIOS 组件:
Android 平台的 DatePickerAndroid 组件:
不只功能交互彻底不一样,并且类名、调用方式各异,这不只知足不了业务需求,并且也有很高的学习和使用成本。这样相似的组件还有不少,如何抹平平台的差别,实现跨平台?咱们提出的方案是优先使用 JS 来实现功能,这也是咱们组件库的设计原则。针对上面的问题咱们开发了基于 ScrollView 的 Datepicker 组件,统一类名与调用方式,保证了跨平台通用性。下面是Android和iOS实现的Datepicker组件。
Datepicker 是使用 JS 彻底实现了一个完整功能,可是有的状况不须要实现完整的功能,咱们能够经过 React Native 提供的 Platform 来进行局部的跨平台处理。例如 TextInput 组件,默认在Android平台下是没有清空按钮的,可是咱们能够经过自定义来实现清空功能。
随着移动互联网的快速发展,各种移动端产品涌现而且不断发展,这也让软件知识不断被普及,业务方对产品功能的定位逐渐从厂商主导转变为用户主导。产品功能更加精准,个性化、细化、深化是必然趋势,经过定制化服务来知足产品发展的要求也应运而生。不一样行业、不一样类型的产品,功能、特色各不相同,用某一种既定的软件产品来知足不一样类型的需求,其适用性可想而知。定制化有良好的技术架构和技术优点,可定制、可扩展、可集成、跨平台,在个性化需求的处理方面,有着很好的优点,因此咱们须要定制化。
在组件库设计之初,就已经统一好了 UI 规范。咱们根据 UI 规范,统必定义样式变量并放置在基础工具层中,即 beeshell/common/styles/varibles.js 文件中,在 React Native 应用中,样式变量其实就是普通的 JS 变量,能够很方便的进行复用与重写操做。React Native 提供了 StyleSheet 经过建立一个样式表,使用 ID 来引用样式,减小频繁建立新的样式对象,在组件库的样式变量应用中灵活使用 StyleSheet.create 和 StyleSheet.flatten 来获取样式 ID 和样式对象。
在每一个组的实现中,会事先引入基础工具层中的样式变量,使用统一的变量对象而不是在组件中自行定义,这样就保证了 UI 样式的一致性。同时,beeshell 提供了重置样式变量的 API,能够实现一键换肤。咱们推荐 beeshell 的用户在开发移动应用时,事先定义好样式变量。一方面使用本身的样式变量重置 beeshell 的样式变量;另外一方面在业务功能开发时,使用本身定义好的样式变量,从而保证总体 UI 的一致性。
样式定制化能够从宏观和总体的角度来实现,而功能的定制化则须要具体问题具体分析,从微观和局部的角度来分析和实现。下文将以 Modal 系列的实现为例,来详细介绍功能定制化。
在移动端的弹窗交互,与 PC 端相比通常会比较简单,咱们把模态框、下拉菜单、信息提示等交互相似的组件统一归类为 Modal 系列,使用继承的方式实现。有人可能会问为何使用继承而不用使用组合?前文已经讲过,组合的主要目的是代码复用,而继承的主要目的是扩展。考虑到弹窗交互有不少定制化的可能性,为了知足更好的扩展性,咱们选择了继承的方式来实现。下面来看一下实现效果:
提供了遮罩、弹出容器以及淡入淡出(Fade)动画效果,弹出内容部分彻底由用户自定义。这个组件通用性极强,没有任何定制化的功能。这里须要说明下,动画部分独立实现,提供了 FadeAnimated 和 SlideAnimated 两个子类,使用了策略模式与 Modal 系列集成,Modal 组件默认集成 FadeAnimated。
继承 Modal 组件,对弹出内容作了必定程度的定制化扩展,支持标题、确认按钮、取消按钮以及自定义 body 部分的功能,通用性减弱,定制化加强。
经过以上部分,咱们已经对 Modal 系列已经有了直观的认识,而后咱们来看下 Modal 系列的类图以及分层。
React Native 应用的 JS 线程和 UI 线程是两个线程,与浏览器中共用一个线程的实现不一样,因此咱们能够看到 React Native 提供的操做 UI 元素的 API,都是经过回调函数的方式进行调用。
受益于 React,咱们通常不须要直接操做 UI 元素,可是有的组件确实须要复杂的 UI 操做,例如彻底由 JS 实现的 Scrollerpicker 组件。
React Native 为用户提供了 style 属性来控制元素的样式,咱们能够手动设置相关 UI 元素的尺寸。可是,在一些 Android 机器上,咱们设置的元素尺寸与 measure 方法获取的尺寸信息不一致,通过大量 Android 机器的实际的测试,咱们获得的结论是:有零点几像素的偏差。
在使用 Form 组件时,最多见的需求就是校验功能,一般组件库的 Form 组件都会内置校验功能。然而,由于校验方式有同步与异步两种,校验结果展现的样式、位置五花八门,这就致使了校验功能的复杂度变得很高。
为此,beeshell提供了如下几种布局方式: 绝对定位
何为CVD,下面看一个模型。
每一层都对单一数据源 Store 进行不可变数据更新,符合交互内聚和顺序内聚,内聚程度高。 每一层使用函数式组合的方式,定义 key(表单控件的惟一标志)与 key 对应的回调函数,避免了批量 if else,能够有效下降程序的圆环复杂度。
下面以 Input 组件录入姓名为例,来具体说明CVD的运做原理。
代码的终极目标有两个,第一个是实现需求,第二个是提升代码质量和可维护性,测试是为了提升代码质量和可维护性,检测代码的质量。
单元测试(Unit Testing),是指对软件中的最小可测试单元进行检查和验证。在结构化编程的时代,单元测试中单元指的就是函数。beeshell 组件库全面使用单元测试,由组件的开发者完成。研究成果代表,不管何时做出修改都须要进行完整的回归测试,对于提供基础功能的组件来讲更是如此,在生命周期中尽早地对软件产品进行测试将使效率和质量都获得最好的保证。Bug 发现的越晚,修改它所需的成本就越高,单元测试是一个在早期抓住 Bug 的机会。beeshell 组件库使用 Jest 作为单元测试的工具,自带断言、测试覆盖率工具,实现开箱即用。
测试用例的核心是输入数据,咱们会选择具备表明性的数据做为输入数据,主要有三种:正常输入,边界输入,非法输入。下面以组件库中提供的 isLeapYear 工具函数来举例说明。
函数使用了外部数据,正常输入确定会有,这里的 2000 和 '2000' 都是正常输入;边界输入和非法输入并非全部的函数都有,这里为了说明使用了有这两种输入的例子,边界输入是有效输入的极限值,这里 0 和 Infinity 是边界输入;非法输入是正常取值范围之外的数据, 'xx' 和 false 则是非法输入。通常状况下,考虑以上三种输入能够找出函数的基本功能点,单元测试与代码编写是“一体两面”的关系,编码时对上述三种输入都是应该考虑的,不然代码的健壮性就会出现问题。
上文所说的测试是针对程序的功能来设计的,就是所谓的“黑盒测试”。单元测试还须要从另外一个角度来设计测试数据,即针对程序的逻辑结构来设计测试用例,就是所谓的“白盒测试”。
白盒测试也是比较常见的需求,Jest 内置了测试覆盖率工具,能够直接在命令中添加 --coverage 参数即可以输出单元测试覆盖率的报告。
想要确保组件库的 UI 不会意外被更改,快照测试(Snapshot Testing)是很是有用的工具。一个典型的移动 App 快照测试案例过程是,先渲染 UI 组件,而后截图,最后和独立于测试存储的参考图像进行比较。
使用 Jest 进行在快照测试,在 beeshell 中第一次对某个组件进行测试时,会在测试目录下建立一个 snapshots 文件夹,并将快照结果存放在该文件夹中。快照结果文件以 <组件名>.js.snap 命名,其内容为某个状态下的 UI 组件树。下面以 Button 组件为例来讲明如何使用快照测试。
运行命令后获得快照结果以下:
常常与单元测试联系起来的开发活动还有静态分析(Static analysis)。静态分析就是对软件的源代码进行研读,查找错误或收集一些度量数据,并不须要对代码进行编译和执行。
静态分析效果较好并且快速,能够发现 30%~70% 的代码问题,能够在几分钟内检查一遍,成本低、收益高。beeshell 使用 SonarQube 进行静态代码检查。
SonarQube 是一个开源的代码质量管理系统,支持 25+ 种语言,能够经过使用插件机制与 Eclipse、VSCode 等工具集成,实现对代码的质量的全面自动化分析和管理。
SonarQube 经过对 Reliability(可靠性)、Security(安全性)、Maintainability(可维护性)、Coverage(测试覆盖率)、Duplications(重复)几个维度,对代码进行全方位的分析,经过设置 Quality Gates 保证代码质量。详细的使用状况能够访问SonarQube官网文档
beeshell 组件库使用 npm 包的形式下载使用,下载成功后会放置在项目根目录的 node_modules 目录,而后在项目中经过引入模块的方式,引入 beeshell 的组件来使用。
那咱们如何开发组件库?如何保证组件库的开发与使用的体验一致性?
首先,咱们须要一个 demo 项目,这个项目是 beeshell 组件库的开发环境,是一个 React Native 应用。而后,咱们把 beeshell 作为 demo 项目的依赖,在 demo 项目中下载安装。如今,问题就变成了 node_modules 目录中的 beeshell 如何和本地的 beeshell 源码进行同步。
咱们知道,可使用 npm link 来开发 npm 包,其工做原理以下图:
本质是就是使用 Symbol link,可是咱们创建好软连接后,运行打包命令却报错了,错误信息为 Expected path '/xxx/xxx/index.js' to be relative to one of project roots。
前端开发一般会用 Webpack 作为打包工具,而 React Native 应用使用的是 Metro,因此此处咱们须要分析 Metro 来定位问题。
通过 Metro 的源码分析,咱们发现 Metro 的打包方案与 Webpack 有较大差别,Webpack 是根据入口文件,即配置中的 entry 属性,递归解析依赖,构建依赖关系图而 Metro 是爬取特定路径下的全部文件来构建依赖关系图。分析发现 Metro 的特定路径默认是运行打包命里的路径,以及 node_modules 下第一层目录。
Metro 在爬取文件的时候,经过软连接找到了全局的 beeshell 可是并无继续判断全局的 beeshell 是否有软连接,因此没法爬取 beeshell 源码部分。
经过 ln -s 命令,直接创建 demo 项目 node_modules 下 beeshell 包 与 beeshell 源码的软连接。
本文为转载文章,原文地址:tech.meituan.com/waimai-bees…
附: beeshell开源地址