1. 为什么选择React
React是当前前端应用最广泛的框架。三大SPA框架 Angular,React,Vue比较,Angular出现最早,但其在原理上并没有React创新的性能优化,且自身相对来说显得笨重。Vue出现最晚,其核心原理学习了React,只是语法形式的变化,关系上来说React是开拓者,Vue是学习者。React社区有强大活力与创新能力,不断涌现革命性的创新产品,其中包括可以使用JS操作原生控件的React Native,Vue后来跟进学习出了类似的Weex,但两者成熟度差很多。目前来看React的生态系统要比Vue大的多,在github, stackoverflow等最大的技术社区搜索两者,React的搜索结果是Vue的十倍左右,另外据近期统计使用React的站点是Vue的几百倍以上。更大的生态意味着更多可用的资源,以及遇到问题可以得到更多的有效参考与帮助,这也是除了性能之外选择React的核心原因。
选择React之后,应用会在以下几个方面有提升。第一,原先的html间跳转会有短暂的白屏现象,这一点在安卓性能较差的机器上尤为明显,而React作为单页应用没有这个问题。第二,React 提供的虚拟DOM包含Diff算法,即将原dom copy一份,与改动后的dom对比,只渲染不同的dom节点,实现最小代价渲染,vdom创新的性能优化方式对性能的提升毋庸置疑。第三,React中核心组件化技术,更加容易的绑定事件行为,动态更新特定的dom,代码更加模块化,重用代码更容易,结构清晰易维护。
2. 在移动端使用React
三大框架在移动端分别有自己的东西。Angular的ionic,React的React Native,Vue的Weex。其中ionic 是基于cordova技术,依然是浏览器应用。而后两者已上升到操作原生控件的层面,做出来的是原生界面,其中React Native的成熟度远高于Weex,已经被很多公司使用,而Weex使用者很少。
综合来看选择React 生态明显最佳,由当前的cordova过渡为cordova+Reactjs,然后可以平滑地过渡到React Native,媲美原生性能的最优混合开发方式。之所以说平滑是因为React Native中近90%的代码(JS)可以在IOS和Android端使用,剩余的涉及原生的代码也基本可以找到可用的资源,就像cordova 的插件一样。毕竟如果需要同时掌握JS, OC(或swift),java(或kotlin)才能开发React Native的话,那这门技术不会有人用;当然反过来如果有原生开发知识的话会对开发React Native有一定帮助。
直接转型为React native的话涉及了应用底层架构的变动,有比较大的跨度,而转为cordova+Reactjs相对容易,而由cordova+Reactjs到React Native同样容易不少,因为其中大部分Reactjs代码可以重用。
3. Reactjs开发工具的选择
首先开发脚手架官方出了Create-react-app,集成了webpack-当前最流行的打包工具,babel-提高js版本兼容性的转码器,以及ESLint-代码检测工具和其它一些常用工具,同时对这些工具进行了比较优的配置。值得一提的是该脚手架将这些工具的配置文件进行了隐藏,本意是让使用者专注于编码即可,但实际使用时通常会有自己配置的需求,此时执行npm run eject即可出现被隐藏配置文件。
React-router 是官方推荐的路由管理工具,由于是单页应用区别于原先的html界面间跳转,跳转实质是在组件间进行,所以需要有路由管理工具来统一化管理。这里值得一提的是,React-router配合webpack可以实现代码的按需加载。一般来说,webpack打包后会在生成一个压缩的js文件,在单页应用打开会整体加载这个文件,由于该js文件包含之前所有的js代码,虽然进行了压缩,一般仍至少有几百kb,当应用稍微复杂点,打包后文件会相应变大。而加载的时候,不管那些代码有没有执行到,都会下载下来并进行加载,造成性能浪费,这一点在显然在web端很重要,而在cordova中是将js代码直接打包在本地,等于跳过了下载步骤但仍然会有加载过程。通过在router中写require.ensure代码并在webpack中相应地修改配置即可将js分成多个文件,在需要时加载对应的js文件,实现按需加载。
Redux 是应用最广泛的第三方状态管理工具,其作用是当应用中多数据状态交互时,可以更有方便且代码结构清晰地统一管理状态,下图给出了形象的阐释。由于在实际开发中一般是分人员/分功能模块独立开发,考虑引入redux的成本(redux本身略复杂),可以在没有多数据交互的模块不使用redux,而在类似涉及增删改查的表单以及即时通讯websocket等契合redux的模块使用。
为项目选取合适UI组件库,一定程度上简化UI样式的开发并且考虑使用其提供的过渡动画效果。这方面有比较多的选择,Google Material Design 风格的Material-UI在github上最受欢迎,但其设计语言与我们当前APP截然不同,腾讯的weui和阿里的antd-mobile 较为相近,其中antd-mobile与create-react-app脚手架配合使用时配置项比较繁杂,因为阿里本意是用来配合自己的脚手架dva(封装了react-router和redux),因此暂时选择weui,后期开发有特定组件需求可结合其他ui库使用。
至于页面跳转时的过渡动画,有些UI库给出了一些过渡样式,比如touchstone。但该库已不再维护,文档不佳,且与新版本的react-router配合使用有不兼容情况。后来浏览官方文档发现官方有动画库react-addons-css-transition-group,使用该库结合css3的动画三件套animation,transition,transform即可实现各种动画效果包括基本的过渡效果,比如渐进平移等。
另外关于css,因为是单页应用,所以如果不加处理,直接import css文件的话最终打包生成一个css文件会导致样式应用到全局,造成同类名样式相互污染影响。解决这个问题有很多种方案。Facebook积极探索css in js方式,但直接写内联样式代码可读性太差。目前解决方案中应用最广泛的是css-modules,即在webpack配置中开启module选项,使用styles对象来写样式。解决的原理是将css类名在打包后编译成哈希字符串,保持其唯一性。但当想要使用全局样式时要再配置,稍显繁杂,且它类名编写的方式为对象的方式,需要整体修改,另外在使用它时,发现不支持-横线的类命名方式,支持下划线方式,推荐驼峰式,而我们之前html中的样式类名大多是横线命名,这意味着原html和css中的类名都要对应修改,考虑到样式类名非常多,这一方式舍弃。另外有基于css-modules使用高阶组件的react-css-modules使用人数也比较多,允许横线命名方式且全局本地样式区分简单,但有benchmark测试表明其会较大程度拖累性能,所以也舍弃。解决这个问题要最大程度兼容原先css的写法,即改动最小,因为之前的css类样式数量庞大。Webpack css-loader 有个属性 :local 加上之后类会变成局部作用域,即webpack会对该类型的类进行自动哈希转码处理,但显然类名一个个加:local 是有些呆板的工作,于是想到可以利用scss的嵌套属性将:local在一个css文件中统一加到类名前。这里涉及到在脚手架create-react-app 添加对scss的支持,在命令行执行安装,并在package.json的scripts中添加watch-css指令,将原css文件改为scss文件,然后在最外层添加:local,执行watch-css命令,即可在scss文件旁自动产生css文件,且类名前自动添加:local 前缀,这种方法实践中发现并非所有类的样式都与:local 兼容良好,相应的可以使用文件名代替:local,要做的就是保持文件名的唯一性,这一点原工程下的文件名已满足。这样原先文件中引入css的方式,全局css引入的方式都不需要变化,做到最小代价。scss 是 sass 3 引入新的语法,其语法完全兼容 css3,并且继承了 sass 的强大功能,sass和less是前端扩充css常用的方式,添加了嵌套,变量,继承等语法,但需要编译成css来最终使用(稳定性考虑)。
4.Reactjs 和cordova结合有哪些需要注意的
开发Reactjs使用官方提供的脚手架Create-react-app,最终通过npm run build生成一个单页网页应用,放入cordova的www目录下即可。由于这两部分开发时独立,而原先开发是在含www目录的cordova工程目录下直接开发,这种不同会产生一些问题。比如cordova中某些插件安装后export函数或者变量供引入使用,因为一开始是分离的,在create-react-app中并找不到这些变量,就造成在build的时候产生变量undefined的错误,尽管最终放到cordova工程中后可以找到变量并正常运行,但在第一步react开发时控制台报一堆error会妨碍调试,影响开发体验。在github上有一些react cordova 库,但实质上它们都需要通过npm run build来打包,所以并没有解决引入插件变量的问题,且会与create-react-app 有相斥的地方。所以要想办法使插件提供的变量在React中不报错,这里在不影响ESLint 检错机制的情况下可以采取迂回的方式。Build时控制台报错仅针对src文件夹下的代码,而在public文件夹下还有个index.html这个文件会最终被打包放到www目录下,因此可以在这个文件中deviceready时添加全局的插件变量(注意该类全局变量的唯一性,可以添加plugin前缀或使用命名空间等方式保证),并将值传给src目录下的代码中,这样即可绕过控制台build以及调试时的报错。
另外一个小技巧可以将react工程直接放在cordova工程目录下,指定最终build生成的文件放入www目录下,省掉手动转移文件的过程。
还有需要注意的一点是由于React中默认配置的公共路径是绝对路径,当放在cordova中时需要使用file协议放本地,需要在webpack的production配置的public路径前加".",或者在package.json 文件增加一行"homepage": "../www"或"homepage": "."改为相对路径,否则会出现找不到文件的情况,这里推荐最后一种方式。
5. React项目的目录结构
首先IDE选取webstorm,功能强大,之前项目组在用可以沿用下来,但需要注意的一点是当目录中包含了安装的依赖node_modules时,由于该文件夹下文件数量非常多,webstorm在智能建立代码关联时会占用大量资源,在某些电脑上会偶尔会出现卡死现象,这一现象在我配置比较高(固态硬盘加8g运存)的电脑上同样出现了,解决办法是在file-setting-File types中配置ignore node_modules 文件夹。
上图是create-react-app 项目的目录,主要代码放在src目录下。Components中包含所有组件。React严格地执行组件技术,组件化不仅方便重用,同样可以将一个页面清晰地分割为几个部分最后放入一个父组件展示,因为jsx技术将js和html放在了一起,分割后每个部分有自己的功能逻辑与页面展示,这样更加清晰易维护。事实上react提出了一切皆组件的思想,只是有的组件render了部分界面,而有的没有render。上图中components下有common文件用来放项目成员自己写的公用组件比如公共请求方法等,external放外部引入的组件,work_log里放的是我写的工作日志模块的组件,各个功能模块都各自创建一个文件夹,命名规则统一使用下划线方式,这也是之前使用的方式。具体功能模块的划分与层级关系可以参考之前的,值得一提的,以前html的层级关系必须严格为两层(涉及到跳转路径的逻辑),导致最后出现没有把一个功能模块放到一个文件夹里的情况,比如上面的工作日志之前所包含的各个文件直接和其它的一些功能模块一起放到了setting文件夹内。而现在只要在React-router统一配置好路由,实质上是往某个组件跳转,不存在跳转路径的限制。
Constants文件夹下存放各种常量,比如各种接口路径。Fonts存放字体图标文件,images存放图片,redux文件夹下是redux的几个组成部分,styles下放scss/css样式文件。Index.js是入口也是最顶层的父组件,router.js则维护了整个应用的路由关系。
上面就是本次调研的技术分析文档,浏览大量技术文档,开源社区以及技术论坛并结合实践摸索得出的选型思路和理由,可能依然会有一些点没有添加进去,以后会结合新知识和实践继续完善。
接下来抛开思路,给出具体配置方式和相关执行代码供开发人员参考。