著作权归作者所有。商业转载请联系 Scott 获得授权,非商业转载请注明出处[务必保留全文,勿做删减]。
线下越重,线上需要越轻,这个轻指的是轻便轻巧和简洁易用,通过前面几章小菜技术与产品历史介绍,我们了解到 B2B 生鲜领域在线下是如此之重,那么在交易场景线上化的过程中,端的移动化就势在必行,试想一下,让菜市场摊位老板人手一台笔记本点开网页选购支付,让采购销售抱着电脑去拜访客户,一边聊蔬菜行情,一边打开笔记本进行记录,有没有一种回到世纪初的感觉。
产品的移动化,这将是我们展开这篇文章的背景,我们会先了解小菜的产品托管在哪些端上,然后感受这些端带来的挑战,最后是我们面对这些挑战所采取的策略,以及整个小菜前端团队历练后的技术成长和沉淀,和我们对于自己的一个评估和对未来的展望,本文将采用最通俗易懂的方式陈述,会略有繁琐,但力求对技术新人也足够友好。
一、小菜大前端的端有哪些
小菜早期围绕着蔬菜销地以客户集单批发的模式摸爬滚打几年,从上游的蔬菜供应商到下游批发市场的摊位老板,在这个长长的链路中,我们诞生了这样几款线上产品来服务于不同的人群和场景,之前文章中也有介绍,这里再汇总一下,共 7 款 App:
- 宋小菜 服务于销地批发老板的下单工具
- 宋小福 服务于小菜内部销售团队的 CRM 销售管理与客户管理工具
- 宋小仓 连接司机-物流-采购-销售的蔬菜在途位置监控工具
- 采秘 服务于小菜内部采购团队的蔬菜品类采购工具
- 麦大蔬 服务于上游蔬菜供应商的大宗农产品交易平台
- 宋大仓 服务于上游囤货配资的进出库管理平台
- 行情宝 服务于产销两地的行情采集和预测工具
前 6 款 App 都是基于 ReactNative 开发的 iOS/Android App,最后一个是微信小程序,它们涵盖了公司几乎所有的协同场景和工作流,其他涉及审核、数据观测和过程管理的部分,则会进入到我们 PC 端产品中,也就是:
- ERP 后台管理系统
生鲜的 toB 场景,角色众多,链路冗长,这种延伸到产地农民,延伸到小 B 交易的管理系统一定会角色杂,权限多,操作重,业务复杂度所带来的页面复杂度不是一般的小系统可比拟。
到目前为止,我们已经看到小菜的 7 个移动端 App,以及一个复杂的后台管理系统,这些都跟前端工程师息息相关,除了这些,还有 2 个重要的内部产品,就是:
- 大表哥 数据报表系统
- 大瓜子 市调模板配置系统
其中大表哥(谐音:搭 Excel 表格)由前端工程师独立研发和维护的数据报表系统,单拎出来这个系统,是因为在 B2B 公司,尤其涉及到供应链的长链路场景中,真实业务数据的及时反馈对于每一个执行团队都至关重要,没有这些数据抓手,就失去了多维度数据观测,都很难快速的做出正确的运营决策和业务调整,甚至很难发现业务中出现的漏洞和问题,比如不正常的非自助下单(也就是销售帮忙下单)的比例。
关于报表系统后文还有介绍,我们再为前端增加一个服务的产品场景,就是微信生态内产品,比如公众号或者小程序,它的技术栈和运行环境跟原生 App 和 PC 都不同,虽然小程序可以带来更多的业务可能性,也会对前端带来更大的挑战。
我们把这些端合并一下,小菜前端要服务的端或场景:
- 移动端(iOS/Android App/小程序)
- PC 端(ERP)
- 工具端(大表哥数据报表)
端上全部开花,这也应了我之前在掘金 JTalk 上小菜对于长链路流通交易分享的一个观点:链路足够长,每个节点上都可以长出产品。那这些端产品都是与业务有强关联的,还有更多技术基建的和服务于团队内的产品,比如:
- 大伯伯(谐音打包包) 实现 App 选仓库选分支选环境配置的自主打包与推包系统
- 大表姐(来自饥饿游戏,寓意开工没有回头箭) 实现 6 款 App 解包差分后下发热更新包的发布系统
- 姑奶奶 线上异常汇集分析与与 Bug 定级指派系统
- 大舅子 向下调用微服务接口向上提供 GraphQL 查询能力的数据聚合服务
- RGB 用户使用 App 的 PV/UV,以及业务数据监控相关的可视化平台
- 110 解决端异常收集与报警需求
- 堂哥工作台 团队记录资源分配与 redmine 同步的自动化周报系统
- ITms 解决内部 App 安装测试的配置生成和预装服务
- ...
这些是服务于团队内部的工具链,全部由小菜前端自行维护。到这里我们发现,在小菜这样一家创业公司内,前端要服务的端和场景的确较多,但这些产品和工具的背后,整个前端组也就 10 个人而已(我们当然也求才若渴),但是人虽少,效率不能自我妥协,所以我们能服务到这些端,也正是基于端的多样性和数量,我们称自己:宋小菜大前端。
先上小菜端上若干产品和工具的技术栈图,帮助大家理解我们的技术理念:
二、多端带来的挑战
1. 【物理现状】移动端的碎片化
古典互联网时代,因为要兼容 IE678 而痛苦不堪,Hack 黑魔法经验基本代表前端水平,如今互联网早已移动化,我们理想中的移动端开发,看上去是可以大胆使用新语法特性,只需要做好尺寸兼容就好了,但事实并非如此,不仅在移动端的浏览器不是如此,在移动端开发 RN App 也是如此,这是我们某一款 App 一段时间内,所收集上来的手机厂商分布:
可以发现 Android 的碎片化非常严重,每一个厂商下面有不同时期推出的不同型号的手机,这些手机有着不同版本的操作系统,不同的分辨率和用电策略,不同的后台进程管理方式和用户权限,要让一款 App 在哪怕头部 40% 的手机上兼容,都是一件艰难的事情,这个客观物理现状叠加下面的社区现状,App 质量保证这件事情会变得雪上加霜。
2. 【社区现状】技术框架的不稳定性
回到本文的开头,我们在长链路的 B2B 生鲜场景中,为了更快更轻,开发出 7 款 App,而且将来随着业务场景的拓展会诞生更多独立 App 甚至是集大成的 App,所以技术选型不太可能选择原生的 Java/Object-C 开发,尤其对于创业公司,6 款 App 得需要多少名原生开发工程师才能搞定,高频繁重的业务变化又怎样靠堆人来保证?
想清楚这些,一开始我们就调研 ReactNative,并最终全部从原生切换到了 RN,通过跑过来的这 3 年来看,使用 RN 为公司节约了大量的人力成本同时,也尽可能的满足到几乎所有的需要快速迭代的业务场景,又快又轻,成为宋小菜大前端团队做事的一个典型特征。
但换一个角度看,就是带来的问题,又快又轻的背后是 RN 版本的飞速迭代,截止到目前,也就是 2018 年 6 月份,RN 还没有推出一个官方的正式的长期维护的稳定版本,什么意思?就是 RN 目前依然处在不稳定的研发周期内,我们依然站在刀尖起舞,用不稳定的 RN 版本试图开发稳定的应用,三年走过来,我们在 RN 的框架里,多少次面对旧版本局限性和新版本不稳定性都进退不得,旧版本的 Bug 可能会在新版本中修复,新版本引进则会来新版本自己的问题。
除了 RN 自身版本,还有第二个问题,围绕着 RN 有很多业界优秀的组件,但这些社区组件甚至官方组件,都不一定能及时跟进最新的 RN 版本,同时还能兼容到较老的 RN 版本,所以 RN 升级导致的组件不兼容性,会引发你 Fork 修改组件的冲动,但这样会带来额外的开发成本和版本维护成本,取舍会成为版本升降的终极问题。
在国内开发,还有第三个问题,就是中文文档缺乏,社区资源匮乏,参考文献陈旧,可拿来主义的开源工程方案甚至社区线上线下会议分享都很缺乏,一个不小心就会踩坑,这就是 RN 社区的现状,我们在刀尖浪花上独步,App 选型背后的技术栈稳定性则成为悬在头上的一把铡刀,你不知道什么时候会咔嚓一声。
3. 【人才现状】人员能力的长短不齐
我们知道有一个词叫做主观能动性,表示没有条件创造条件也可以上,这个词的主体就是人,聊完移动端设备现状和社区现状后,我们来聊聊人的问题。RN 在国内真正开始普及使用,是从 2015 年开始,也就意味着,到 2018 年,一个 RN 工程师也就只有 3 年的工作经验,而 RN 的 “Learn once, write anywhere” 也刺激着一切 Care 人员开支, Care 产品研发投入性价比的公司纷纷跳水研究 RN,争抢 RN 人才,RN 是前端中的移动前端,前端有多抢手,那么 RN 工程师就比它还要抢手。
这导致基本上 RN 工程师,很难靠外部招聘,只能靠内部培养,这也是小菜前端的成长历程,我们有 2 名资深 RN 工程师,一个是从服务端 Java,一个是从原生 Android 开发转过来的。如果 RN 人手不足,产品支持的力度和速度就一定会遇到瓶颈,这就是我们曾经面临的问题,就是人才现状,外招数量不足,内培速度有限,RN 工程师的数量和能力就时不时成为公司业务扩张的瓶颈。
4. 【公司现状】高密集业务的交付质量
作为工程师,我们有很强的自尊心和不容挑战的代码洁癖,但在一个创业公司里面,甚至大公司的一个创业团队里面,我们需要对接一些关键的业务节点,冲刺一些特定的时间窗口,并且要及时响应多变的业务,和业务背后多变的产品形态,这都会带来非常密集的需求队列。
这些密集的需求队列对我们的代码实现质量有非常高的挑战,一个组件用 5 分钟思考如何抽象和用 50 分钟思考,实现后的稳定性、兼容性都是不同的,如何保证产品按期交付上线,会是摆在我们面前一个非常关键的命题,而这个难题之外,还有一个更难的命题等着我们,那就是如何保证交付不延期的同时,还能保证交付质量。
要知道,如果一个项目代码赶的太毛糙,后期维护起来的成本会是巨大的,甚至只能用更高的成本重构重写。本质上,再次重构就一定是公司在为早期的猛冲买单,为这些技术债买单,如何不去买单或者如何用最小的成本买单,这跟我们早期的业务密集程度,交付周期,质量把控有很大的关系。
综上,移动端碎片化所带来的兼容难度,RN 框架的局限性,版本间差异带来的不稳定性,技术社区资源的匮乏和前端团队技术能力掣肘,再叠加上高密度的业务排期,让前端开发这个本来很酷的事情,变得晴雨不定。
这些避不开的现实,是绕不过去的坎儿,是搭建团队必须搞定的基础,我们想要把 B2B 生鲜的线上线下场景通过端产品关联起来,想要通过前端团队的用户侧输出从而让这些产品落地,就必须面对这些现实挑战,而应对这些挑战,首先必须搞清楚有哪些挑战,搞清楚挑战以后,我们就会认识到,首当其冲的事情,是去搭建 B2B 生鲜公司的前端技术栈和人才梯队,现在我们进入到本文的重点。
三、如何应对井喷的挑战
1. 前端梯队如何搭建
创业公司的技术团队,本质上就是人和事,用合适的人搞定特定的事,人才的瓶颈就是这家公司产品落地速度和上线质量的瓶颈,因此人是第一位的,对于前端团队来说,如何一步步形成有综合战斗力的团队,取决于搭建什么层次的前端梯队,如果所有人一视同仁,培养同样的能力栈,发挥同样的兴趣向,跟进同样的业务线,那么这个梯队的扁平就会带来致命的团队瓶颈:能力可复制但不能互补,能力可递进但很难跨越,不能互补和很难跨越会导致团队内的技术路线过于单一,技术思维趋于固化,至于技术储备的丰富性和技术沟通带来的碰撞就更有限,最终导致人做事越来越机械化,甚至失去最初的技术初心。
那么小菜前端到底如何搭建,还是要从公司的人员、业务和技术现状出发,由于端的碎片化和技术框架的不稳定性,就必须在质量保障上投入巨大的人力保证产品可用,而人才能力局限性和数量的匮乏,就跟产品的质量保证成为了天然的矛盾,不可协调,代码撺太快,线上天天都是 Bug,代码撺太慢,产品节奏跟不上,至于跟工程师天天宣讲要小心小心再小心,能起到的作用也不大,因为工程师本身的能力也是参差不齐的,所以就必须把团队先拆成两部分,一部分做基建支持,一部分做业务支持,基建支持的同学研发整个团队的工具脚手架、抽象和打磨团队的基础通用组件、长期维护项目的通用架构,这些投入都会反哺到业务支持的同学,业务的同学可以放心大胆的基于基建的成果做上层业务开发,稳定的工程基础有了保障,上层的业务代码做质量保障难度就大大降低了。
除了分出来人做基建,做业务,还需要有核心的技术骨干,做技术前瞻性的研究,为团队 3 个月后,半年后,甚至 1 年后的技术方向,做必要的调研、测试和实验性开发,因为对于刀耕火种的早期技术团队,从原始人到迈向外太空跨空间作战,这中间还差着很多个关键的技术迭代节点,这些关键的技术迭代节点,一部分是靠外招技术专家和资深的工程师来输血发力,还有一大部分是需要靠团队内部长期的积累沉淀,也就是人才内部培养。
我们总结一下:
- 基建的同学负责输出工具系统、基础组件、流程规范,保证内部效率最大化和质量的有效保障
- 架构的同学负责攻克技术底层难点,调研先进技术,升级团队技术架构,沉淀技术方案,锁定和推进团队未来技术方向
- 业务的同学负责产品跟进,高频使用基建产品,并通过反馈来优化团队的技术基础设施,同时基于业务来抽象更多的基建需求
基建、架构、业务这三个角色并不是相互独立,而是互有重合各有侧重,一个业务的同学,可能也同时在负责基建的事情,一个基建的同学,可能也同时在参与架构的设计,在小菜就有同学以架构和基建为主,业务也时不时的参与开发,架构和基建必须依托于业务场景来做,不能脱离了场景,不然会输出畸形的难以落地的技术方案。
上面是人员的分工,还有三个重要的保障,这里不做引申,只列举一下:
- 团队人员的兴趣栈、能力栈和业务要尽量匹配
- 团队人员的阶段性目标、长期规划要跟进公司的职业晋升路线和能力模型
- 团队要有持续性的内部技术互动分享和对外的技术理念、方法方案分享
小菜的前端是大前端,对人的要求是:一专多精多能,至少在某个领域内朝着专家方向走,同时要慢慢精通多项技能,最后是具备多个特定技术栈的开发能力,比如 ReactNative,在小菜就是一个必须具备的开发能力,不要求每一个同学都成为 RN 专家或者精通,但要具备业务开发的能力,通俗点描述,就是能用 RN 开发业务产品。
最后一点,就是资源流转,架构的同学,基建的同学和业务的同学的梯次关系是从下到上,越下越接近技术本质,越上越接近业务结果,越向下需要越好的技术实力,越向上需要越好的业务理解能力,这两个能力都是核心能力,需要让团队成员沿着梯队关系慢慢流动起来,业务中技术能力好的同学可以有机会沉下来做做基建,长期埋头基建的同学可以有机会上去做做业务,业务理解不错技术沉淀又好的同学可以继续沉下去参与架构,这样团队内部的同学都可以有多样性的技术场景和业务场景,一旦有同学请假、陷在别的业务不能抽调,马上就有同学可以补位进来开发,不会影响到产品上线节点。
关于团队如何搭建,目前小菜是走到了这个算是 v1.0 的阶段,未来还有更多挑战,也会带来更多的基于公司现状的新调整,无论如何变迁,方法论我们先沉下来:
- 人才梯队要有层次:基础架构、基建和业务上层等
- 人才成长要有规划:兴趣栈、能力栈和公司关系
- 人才能力要有扩展:单人能力和互补后的团队能力
以人为过程,以事为结果,人事之间要有动态的机制形成互惠互补的关系,只有这样,团队才会初心不变,激情常在。
2. 如何做技术选型
技术选型是一个行业老话题了,方法论也有很多,在小菜我们遵循的是:技术方向性预研大踏步,业务基建型开发小碎步,前者尽可能激进,后者尽可能保守,比如 数据报表系统,我们激进的采用 GraphQL 来解决 SQL => 页面 Dom 的链路问题,在宋小福 App 上面,我们就求稳的采用 v0.48 的 ReactNative 版本,而不是用当时较新的 v50+ 版本。
在做技术选型之前,还有一些比较重要的基础性问题需要搞定,那就是团队技术动作的一致性,这个一致主要包含两点:
- 代码规范共用一套
- 仓库合作方式共用一套
这两点如果不一致,会给技术选型后的落地带来内耗成本,千万不可大意。
再回到技术选型本身,抛开激进保守的大踏步和小碎步,我们需要回到技术本质和工程师的本质来看待如何选的命题,技术的本质是效率,工程师的本质是兴趣,如果这一套技术选型不能带来效率,如果工程师普遍不感兴趣,那么通常这一个选型我们不会采纳,我觉得这一个主观一些的标准,大家可以参考,但这里面也要权衡好历史包袱、维护成本,上手难度等这些客观现实,如果一个新技术会带来革命性的效率提升,那么即使有上手难度和维护成本,我们也会果断入坑,比如 GraphQL 对于数据报表对于解放前后端有大幅度的提升,我们会果断入坑大力推行,如果一个技术对于团队是锦上添花,那么我们会慎重选用,比如 TypeScript,可以给工程稳定性带来了较大的保障,但我们只选择在热更新这种 RN SDK 和 Server 端的去集成,而不是一下子推广到整个团队项目中铺开用,这里面就会考虑到实际得到的好处,以及历史包袱和上手难度,反复权衡后并没有带来更大的价值,所以这两类场景的推行和不大力推行,就又不会太依赖于工程师的喜好兴趣。
那么我们技术选型后的结果是如何呢?
文章最开始的那张图,里面就是我们的技术栈,这里再做一下总结:
- 工具类:强依赖 Node,多而杂的其他技术,如:MongoDB/Redis/MySQL/Shell/Python
- 业务类:强依赖 React/ReactNative,适度集成其他技术,如: Redux/GraphQL/Apollo
- 框架类:除了 React 全家桶会谨慎选择,Node 端框架则相对宽松:Koa/Thinkjs/Eggjs
这些相对求稳,不求稳的部分,如小程序开发,我们会使用 mpvue,也会用原生,还会集成进去 GraphQL,同时一些涉及到数据爬取和视频图像识别,我们也会集成 Python/C++/TenserFLow 等等,不过这些往往是前瞻性的技术尝试,会让团队的同学适当分配精力持续研究。
3. RN 的 App 工程如何架构
小菜的主要产品类型,尤其是对外的产品,主要是 RN App,而且数量较多,那么 RN 项目的合理架构就变得尤其重要,我们这里探讨下小菜前端在 RN App 上面的沉淀,涉及到原生层面的技术细节太多,这里暂不做讨论。
首先,我们在构建 RN App 工程时需要关注这几个关键要素:
- 配置管理
- 静态文件管理
- 网络请求
- 组件管理
- 路由管理
- 数据缓存
- App 的热更新
- 数据搜集
配置管理是指可以灵活合理的管理 App 的内部环境,主要包括:
- App 本身的一些配置
- 所使用三方插件的配置
我们在构建工程时尽量将所有的配置抽象统一放置在一个地方,这样便于查找和修改,但是由于大多数配置都统一放在同一个地方,那么就难免有部分文件要使用某个配置时其引用路径比较长,比如:
import { pluginAConfig } from '../../../../../config'
这样就造成了阅读性很差且代码不美观,因此我们可以使用 Facebook 的 fbjs
模块提供的一个功能providesModule
:
//config.js
/**
* config for all
* @providesModule config
* 使用 providesModule 将 config 暴露出去
**/
import pluginAConfig from './plugin_a_config'
export default {
pluginAConfig
}
// 然后在其他文件中调用
// A.js
import { pluginAConfig } from 'config'
这样就能很方便地在 App 的任意一处使用 config 了,但是我们要避免滥用 providesMoudle
,因为使用了 providesMoudle
进行声明的模块的源码,想要在编辑器中使用跳转到定义的方式去查看比较困难,不利于团队多人合作。
静态资源泛指会被多次调用的图片或 icon,我们一般在 RN 使用图片时是直接引用的:
import { Image } from 'react-native'
render(){
return (
<Image source={{uri: './logo.png'}} />
)
}
当图片需要在多处使用时,我们可能会将这些可能会被反复使用的图片统一管理到 assets
文件夹中,统一管理和使用,但是当需要使用图片资源的文件嵌套较深时,引用图片就变得麻烦:
render(){
return (
<Image source={{uri: '../../../../assets/logo.png'}} />
)
}
这个问题与配置管理的问题一样,可以首先将图片资源按照类型进行分类,比如 assets 文件夹下有 button/icon/img/splash/svg 等,每一个类型的结构如下:
- icon/
- asset/
- index.js
其中 asset
文件夹保存我们的图片资源,在 index.js
中对图片进行引用并暴露为模块:
// index.js
export default {
IconAlarmClockOrange: require('./asset/icon_alarm_clock_orange.png'),
IconAvatarBlue: require('./asset/icon_avatar_blue.png'),
IconArrowLeftBlue: require('./asset/icon_arrow_left_blue.png'),
IconArrowUpGreen: require('./asset/icon_arrow_up_green.png')
}
然后再在 assets
文件夹下编辑 index.js
,将所有的图片资源作为 assets
模块暴露出去,为了避免和其他模块冲突你可以修改模块名为 xxAssets
// assets/index.js
/**
* @providesModule myAssets
**/
import Splash from './splash'
import Icon from './icon'
import Img from './img'
import Btn from './button'
import Svg from './svg'
export {
Splash,
Icon,
Img,
Btn,
Svg
}
// A.js
import { Icon } from 'myAssets'
render(){
return (
<Image source={Icon.IconAlarmClockOrange} />
)
}
这样,我们就能很方便地将分散在项目各处的图片资源统一到一个地方进行管理了,使用起来也非常方便。
网络请求这块,react-native 使用 whatwg-fetch,我们也可以选在其他的三方包如 axios 来做网络请求,但有我们在开发中遇到过一个问题,那就是我们明明已经在代码里已经修改了 cookie, 但是每次请求可能还是会带上之前的 cookie 从而造成一些困扰,所以这里推荐一个实用的组件 Networking
:
import { NativeModules } from 'react-native'
const { Networking } = NativeModules
// 手动清除已缓存 Cookie,这样就能解决上述的问题了
Networking.clearCookies(callBack)
当然,Networking
的功能不止于此,还有很多其他有趣的功能可以发掘,可以直接用它来包装自己的网络请求工具,还支持 abort
,可以参考 源码 来具体把玩。
使用 RN 开发 App 本身效率就比较高,如果想要继续进阶就要考虑组件化开发,一旦涉及到组件化开发,就不可避免地会涉及到组件管理的问题,这里的组件管理比较宽泛,它实际上应该指的是:
- 组件规范
- 组件类型划分
- 组件开发标准
组件规范指的是 UI 设计规范,我们可以与设计同学交流规定好一套特定的规范,然后将通用的样式属性(如主题颜色,按钮轮廓,返回按键,Tab 基础样式等)定义出来,便于所有的组件开发者在开发时使用,而不是开发者各自为政在开发时重复写样式文件,这里推荐一个比较好用的用于样式定义的三方插件 react-native-extended-stylesheet ,我们可以使用这个插件定义我们的通用属性:
// mystyle
import { PixelRatio, Dimensions } from 'react-native'
import EStyleSheet from 'react-native-extended-stylesheet'
const { width, height } = Dimensions.get('window')
const globals = {
/** build color **/
$Primary: '#aa66ff',
$Secondary: '#77aa33',
$slimLine: 1 / PixelRatio.get(),
/** dimensions **/
$windowWidth: width,
$windowHeight: height
}
EStyleSheet.build(globals)
module.exports = {
...EStyleSheet,
create: styleObject => EStyleSheet.create(styleObject),
build: (obj) => {
if (!obj) {
return
}
EStyleSheet.build(_.assign(obj, globals))
}
}
// view.js
import MyStyleSheet from 'mystyle'
const s = MyStyleSheet.create({
container: {
backgroundColor: '$Secondary',
width: '$windowWidth'
}
})
render....
这样,我们就能在开发的任意插件或者 App 中直接使用这些基础属性,当某些属性需要修改时只需要更新 mystyle
组件即可,还可以衍生出主题切换等功能,使得开发更加灵活。
关于组件类型我们会抛开三方组件以及原生组件,因为一旦涉及到这两者,需要写的东西就太多了,我们将组件按使用范围分为通用组件和业务组件两大类。
首先什么是业务组件?即我们在开发某个业务产品常用到的组件,这个组件绑定了与业务相关的一些特殊属性,除了这个业务开发以外,其他地方都不适用,但是在开发这个业务时多个页面会频繁地使用到,所以我们有必要将其抽象出来,方便使用。
什么是通用组件?即可以在 App 范围内使用甚至于跨 App 使用的组件,这里可以对这个类别进行细分,我们将能跨 App 使用的组件上传到了自己的搭建的私有 npm 仓库,方便我们的 App 开发者使用,同时,具有 App 自己特色的组件则放到工程中统一管理,同样适用 providesModules
暴露出去。
制定一整套组件开发标准的是很重要的,因为很多组件开发可能是多人维护的,有一套既定的规范就可以降低维护成本,组件使用的说明文档的完善也同样重要。
开发 App 就不可避免地会遇到如何管理页面以及处理页面跳转等问题,也就是路由管理问题,自从 Facebook 取消了 RN 本身自带的 Navigator 以后,许多依赖于这个组件的开发者不得不将目光投向百花齐放的社区三方组件,FB 随后推荐大家使用的是 react-community 推出的 react-navigation ,现在这个路由组件已经独立出来了。我们在开发时就是使用的这个组件作为路由管理组件,只不过是在其基础上做了一些定制 ,使得使用更加简单,部分跳转动作更加符合我们的产品场景,推荐大家使用这个组件。当然,除去这个组件还有很多其他的组件可供选择:
- 基于
react-navigation
进行深度定制的 react-native-router-flux - 基于原生解决方案的 react-native-navigation
- airbnb 的 native-navigation
路由管理作为整个 App 的骨架,它是这几个部分中最重要的一部分,合理地定制和使用路由管理可以极大地简化我们的开发复杂度。
一般情况下需要缓存的数据基本上就可能是我们会在 App 很多地方都会使用到的全局数据,如用户信息,App 设置(非应用层面的设置)等,RN 提供一个 AsyncStorage 存储引擎,通常的使用方式是对这个数据引擎进行包装后暴露出符合我们要求的读写接口。这里推荐另外一种使用方式:
既然需要缓存的数据可能是会在 App 很多地方使用到的全局数据,那么我们可以将这些全局数据使用 redux 来进行管理,而利器 redux-persist 则能让我们很优雅地读写我们的缓存数据。
同时,如果对 react-navigation
进行合理的定制,接管其路由管理,那么我们还能实现保存用户退出 App 之前最后浏览的页面的状态,用户在下次打开 App 依然可以从之前浏览的地方继续使用 App,当然,这个功能要谨慎使用!
App 的版本更新,RN 除了传统的 App 更新外还有一个热更新的可选项(传统 App 更新也有热更新,其原理就不太一样了),社区大多数人都推荐使用 codepush 来进行热更新,至于其后端解决方案 貌似已经有了一个 code-push-server ,我们是使用自己的热更新方案,其原理就是在不更新原生代码的基础上更新 JS 代码和静态资源文件。
搜集的 App 使用数据(包括异常数据)并对此分析,根据分析来定位问题是保证 App 质量的有效手段之一。你可以选择自己搭建一套数据搜集服务,包括客户端 SDK 和服务端搜集服务,或者选择市场上已有的工具,目前较为成熟的收据搜集工具比较多,如友盟,mixpanel, countly 等等,在此不作赘述。
总结一下,一个 RN App 架构应该要保证 App 的运行稳定以及开发的便捷。运行稳定这一方面,除了从 JS 层面(如单元测试,JS 错误上报等)保证之外,很大程度上还要依赖于原生层面的处理,所以团队里面要有同学的精力可以投在原生研究上面,至于开发便捷,我们尽量将复杂重要或者简单繁琐的操作在构建工程时就做掉,这样也可以大幅度提高我们的开发效率,降低开发者之间的合作沟通成本。
4. 效率协同工具如何打造
效率协同往往不分家,效率宽泛一点,就是又快又好,协同宽泛一点,就是顺滑无内耗,而且效率协同在不同的场景下,一定有不同的表现,所以效率协同一定要具体到某一个场景才有意义,比如:
我们要发布 6 款 RN App 中的若干款,在一周内的若干天发布,由若干人自行打包测试自行发布,那么这里面就有巨大的协同问题,同时还有一些效率问题,如果一个同学进来改了 3 行接口调用代码,他至少要有这几个阶段:
- 开发阶段本机切新分支调试
- 测试阶段打一个连接测试环境的包测试有效性
- 测试完再打一个连接正式环境的本地包测正确性
- 最后再打一个连接正式环境的用来热更新的包进行发布
那么多人之间都各自来做这个发布,就会出现一些发布冲突的协同问题,如果把发布权限全回收到某一个人,协同貌似能解决,但是会带来效率问题,大家要让这个发布人频繁打包,或者打好的包,反复传给发布人,发布人的时间线就被他人的开发进程给打断了,变成了一个打包员,关于这个我专门做了一张图:
这里面的一个圆点,就代表一个编译后的包,比如 A 打出来的不需要 Debug 的连接正式环境的需要热更新的 iOS 的 ipa 包,那么 A 的这个包,跟 B 打出来的不需要 Debug 的连接正式环境的需要热更新的 iOS 的 ipa 包,即便是在同一个仓库的同一个分支,也不能保证 100% 一模一样的包,原因在于,这些本地打的包,还会受到 Node/NPM 版本(语义化),XCode 版本,原生热更新版本控制等等因素影响,导致这个包自身很容易出问题,甚至是一些人肉引发的分支和人工上传等等的影响,也会导致这个包发布出问题,举一个我们真实发生过的故障,A 打完包,把包文件钉钉传给 B,B 在发布的时候,选择本地文件时候选错了一个老版本直接发布上线,导致线上部分用户直接版本回退,我们后来不得已采用紧急回滚,才把影响范围控制住。
上面大篇幅的介绍打包的这个场景,是小菜前端早期非常痛苦的一个场景,协同方式和规范无论我们如何三令五申总是避免不了人肉的问题,一旦出问题,就是大问题大故障,那么这时候,就必须投入基建的力量来打造一款或者一套流程工具,通过工具一劳永逸的解决这些主要的协同问题,把琐碎人肉的事情交给机器去做,机器比人做的快,也比人做的好。
我们来总结一下,团队跑一段时间,一定会挤压一些问题,这些问题不可以视而不见,也不可以拿业务支持第一这样的借口来无限期推迟解决问题,而是时不时评估一下,有没有可能通过系统和工具,来约束一些行为,来取代一些人肉工作,进而可以一劳永逸的解决掉一些问题,一旦决定去解决了,那么如何打造协同工具就变得顺水推舟了,因为工程师最擅长的干的事情之一,就是造轮子造工具。
小菜前端造了哪些轮子哪些工具呢,文章最开始就已经列出来了,这里再陈述和解释一下:
- 大伯伯(谐音打包包) 解决人肉打包带来的协同问题
- 大表姐(代号 Laurence - 来自饥饿游戏) 解决人肉发布、人工维护版本,问题追溯定位的效率问题
- 姑奶奶 解决去多个三方平台查看异常日志和 Bug 反馈指派的人肉效率问题
- 大舅子 解决前后端耦合在 Restful/Mock/冗余的接口 的合作效率问题
- RGB 解决纯数字报表分析页面访问数据和用户行为的效率问题
- 堂哥工作台 解决组员与 Leader 日报周报难回忆带来的书写效率和后期回顾问题
- 小菜图书馆 解决小菜书架上面借书还书靠人肉记录维护的效率问题
- ...
其中的大舅子这个单独拿出来说一下,现在前后端常见的合作方式是基于 restful API 的接口合作,前后端经过一轮接口评审,后端再为前端写 Mock 数据,可能还会加上一个 Proxy 服务,最终前端本地的页面上,做 Mock 环境、测试环境和正式环境的切换,这种方式最大的问题有 2 个:
- 前端比较依赖于后端的接口定义,后端为前端 Mock 做完后,前端才方便的进行页面中的数据替换和逻辑判断,有等待成本
- 前端复杂多变的页面会影响到后端的接口复杂度和体积,页面上的字段增减,都会反映到接口的字段增减,接口本身变得不稳定,会带来很多隐患点,比如接口体积越来越臃肿,或者接口有多个版本,一旦接口文档更新没跟上,会导致后期的同一个接口的不同版本之间,增加调用出错概率等等
当然业界也有各种各样规避这些问题的策略,可能是文档建设,可能是流程约束,小菜早期,哪怕到现在,也是在使用这种方式合作的,直到现在我们有了大舅子,前后端合作的方式开始进化,大舅子系统架构图如下:
大舅子目前的架构是放到网关下面,网关层做一些鉴权和安全的处理,向下把一个 GraphQL 的请求转发给大舅子,大舅子上面根据这个 Query Type 对应的 Resolver 去调用下层的服务接口,下层可能是另外一个 GraphQL 服务,也可能是微服务,也可能是数据库,兼容度很高,无论是哪一种,大舅子的角色扮演就是配置和聚合:配置客户端上页面对应的数据类型,嵌套关系和数据结构,向下连接和聚合不同的数据源。
内部的开发正式环境关系图如下:
这个事情并不新鲜,多年前,Nodejs 就在扮演数据聚合层的角色,把多个 API 聚合成一个 API,或者打散一些 API,聚合成新的 API,但本质上依然是向客户端提供 API,这种 API 依然是面向页面,可以看做是页面驱动的 API,大舅子因为整个建模基础是 GraphQL,所以页面和数据结合的权利,交给了客户端自己去做,它需要什么数据,就在客户端声明什么数据结构,带来的好处很多,这里列举两条我认为有价值的:
- 前端可以不再局限于接口评审阶段,可以继续往前提到数据库/表结构评审阶段进入开发流程,在表结构评审期就能拿到字段定义与含义,从而再大舅子上提前定义前端自己的 Type 和 Resolver
- 后端不再耽误自己的时间为前端提供 Mock,也不再受页面 API 的约束,可以下沉精力专心做下层业务领域能努力的建设,下沉的领域能力多大,那么端上可使用和组合的能力就有多大
从此,尘归尘,土归土,前后因为页面数据控制权的分离而解耦,也因为数据能力的回收而同时贴近业务,前端也被倒逼去了解业务,不再仅仅是界面和产品交互驱动,现在大舅子还在早期的迭代阶段,关于它的好处和优化的空间还非常非常大,今天不做深入讨论,我们来总结一下:
小菜前端已经从工具基建中受益,因为工具带来了协同和效率的优化只是结果之一,最重要的收获还有两个:
- 解放了小菜前端团队以及技术团队的人肉时间,可以有精力做其他事情
- 通过基建工具研发培养了小菜前端分析问题和解决问题的能力,同时沉淀了一些不错的技术方案
那么小菜的成长和沉淀,我们接下来就可以来总结一下了。
四、技术成长和沉淀
技术成长就是工程师的能力变化,我在 4 月份给大家做了一个 10 个月前后的能力评估,这 10 个月,是小菜前端 3 年来基建密度和团队内调整最大的几个月,也是团队整体战斗力提升最大的几个月,本文的所有分析、策略和实际的解决办法,也都是在这几个月里面进行实施的,挑了几个同学,挑了几个主要的能力维度,我们感受下他们的技术成长,白色的 * 代表 10 个月之前的能力值,2 颗星代表可以熟练的开发,三颗星代表基本精通或擅长,四颗星是比较精通。
可以看到每个人都有不同程度不同层面的成长,有的全面开花,有的某些领域内快速积累,也要同学技术成长不多,但是协作能力工程能力有很大提升,其实还少了一个维度,就是参与业务拿到的结果或者说业务能力,图上放不下了,稍后会做分析和补充,我们再来看下这些同学做的事情:
如果仔细比照一下,我们很容易得出三个结论:
- 整个技术团队综合技术能力有大幅度的提升
- 承担职责越多的同学能力成长越多越快(如组员 A)
- 承担繁重基建和工具开发的同学比承担业务开发的同学技术成长更多(如组员 E)
业务能力没有放到图上,这是要补充的第四个结论
- 承担业务越多的同学,项目管理能力/沟通能力/业务理解能力也越好
以上是人才成长,那么沉淀下来的内容一共是三部分:
- 通用的工具技术解决方案
- 通用的技术模块和业务组件库
- 团队整体的问题解决套路(分析解决问题的思维方式)
通用的技术解决方案可以不断的快速复用,比如我们宋小福用新架构前后调整优化有 1 个月,把这同一套架构放到麦大蔬上 2 周就够了,再次迁移到新项目宋大仓里面只需要 1 周就搞定了。
通用的技术模块和业务组件库,则是我们的组件三步走策略,首先是某个业务产品线下面的组件模板,比如 筛选组件或者列表组件,能在这个业务场景下的产品形态中通用,如果它可以跨产品线,那么就会跃升为 App 内通用组件,如果它还能继续抽象具有可重用性,那么就可以跃升为跨 App 的通用业务组件,比如热更新组件,地理位置定位组件,登录组件,异常提示弹窗组件等等。
团队整体的问题解决套路,这个是我们最大的收获,再直白一点,就是如何更快更好更有创造性的做事,这种思维方式,解决问题的套路,本质上是可以在团队内不断传承的,无论我们后面遇到什么样的业务和团队问题,我们用这一套场景-技术-长短期投入产出比评估的路子,都能用较轻的方式把问题解决掉,这个对于我们培养新人有很大的帮助。
五、对未来的展望
小菜三年走过来,前端团队从早期的技术和人员不稳定,到现在趋于稳定,站在公司的角度,最大的收获就是培养和磨炼了一批有创业热情,有担当勇气,有技术底蕴的一群人,这一群人抱团在一起,可以在所谓大前端这个框框内玩出更多的花样,支持到更多的业务场景。
站在今天看明天,虽然有很多东西对我们来说依然是未知的,但我们不再像过去一样临场发怯,手忙脚乱,取而代之的是无论多大多难的业务类型,我们都可以坐下来利用这帮人的智慧汇聚出一个最优选择,胸有成竹的去做技术探索和工程尝试,在跟公司一起成长变大的过程中,站在今天看明天,虽然有很多东西对我们来说依然是未知的,但我们不再像过去一样临场发怯,手忙脚乱,取而代之的是无论多大多难的业务类型,我们都可以坐下来利用这帮人的智慧汇聚出一个最优选择,胸有成竹的去做技术探索和工程尝试,在跟公司一起成长变大的过程中,小菜前端也一定会沉淀出来更有实践价值,更有效率的技术方案,而这些就是我们将来可推广复用的宝贵技术资产,当然除了宝贵的技术资产,最最最重要的还有我们这群人,可以开心有趣有挑战性的 Coding,想进一步了解我们团队的可以移步这里。
Scott 近年面试或线下线上技术分享,遇到太多前端同学,由于团队原因/个人原因/职业成长/技术与管理通道,甚至家庭城市等等原因,在理想国与现实之间,在放弃与坚守之间,摇摆不停,心酸硬扛,大家可以找我聊聊南聊聊北,对工程师的宿命和价值有更多的看见与了解,Scott 微信: codingdream,也可以来关注 Scott 跟进我的动态。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。