react-native大型项目开发实践

RN流行有很长一段时间,也有不少人陆续在业务中使用,我从16年开始接触RN开始运用在项目中也积累了一些经验,这么长时间给我感触最深的一点是,即便有不少人愿意尝试RN,但也只是浅尝辄止的心态,很少有人告诉我大型项目该如何架构,RN有哪些问题,每一步该怎样做等等。为此,我付出了很多时间与精力,我希望这篇文章与现在流行的那些helloworld教程有所区别,能够真正的帮助到大家,给你一些使用RN的信心。

在第一个RN项目开始之前,我对此还是一无所知,只能一遍写代码一边考虑架构与库的选型,为此我付出了很多代价,项目反复重构多次。像是数据通信方式、数据管理方式、组件化的应用等等,无不是从头摸索,当时我迫切的希望能够有一些人站出来写一些教程告诉我那些可以做那些不可以做,怎样才是最佳实践。现在我积累了一些自己的经验,即便称不上最佳实践,但我很肯定这篇文章还是能够给你的RN项目带来一些改变,能够给你一些新的观点。

一次编写,到处调试

和官方宣称的一次编写,运行在不同设备上的理念不同的是,这是一个漫长的过程,最后你会发现这不过是一次编写到处调试的过程。如果你真的希望能够开发一次就完成所有事情那明显是不可能的,特别是安卓与国产安卓,你会为此花费难以想象的时间。当然,无论如何,团队的工作时间相较于全部原生开发还是有所减少,但我真正想说的是,这并不是一个能够让你们挤出大量时间的技术选型,特别是你想要做一个足够好的应用时。

你会有一些疑问,这些问题都出在哪里,如何解决这些问题。我并不愿意细致到每个功能点来讲解RN的细节与实现的蹩脚之处,但以下几个问题你肯定会在实际开发中遇到:

  • 官方与第三方库并不全部支持安卓与iOS平台
  • 库的链接在不同版本会出现不同问题
  • UI在国产安卓上会有很大偏差
  • 全局动画在不同机型上表现差异过大
  • 可以实现的功能属于安卓与iOS的交集(更少)

这里可以简单的举几个例子,你可能对此似曾相识,当然我并非是在尽数RN的不成熟之处,一个框架必然有其长短,总是好的未必一定都是好的! 你需要实现某个业务需求,它包含一个非常复杂的功能,github当然有成熟的库供你使用,RN社区虽然很大,但由于本身更新速度很快,有不少第三方库都不一定能够跟上你的版本速度。为此你需要做一些兼容性处理,还要考虑iOS与安卓双方的兼容情况,在第三方库本身出现问题时你很可能会误以为是库的链接或其他bug,定位这些bug要付出相当长的时间。我曾经遇到过几个第三方库的问题,为此我反复提了多个PR,但还要考虑作者review以及版本发布的速度,最后你不得不为自己的选型付出代价,绕一段路来解决这些看似很简单的问题。 如果你需要兼容大多数版本的安卓,项目中javascript代码会变得臃肿很多,因为你必须不断的判断版本做出样式上的兼容处理,导致组件越来越大,这明显也会影响到iOS包的大小。而且你所能使用的功能永远是它们的交集,如果某个组件是iOS的特点,在安卓中实现就需要依赖js构建,这是一个很现实的问题:如果你希望它们表现相同就不得不用js模拟这个组件,相反的,你也可以为此构建两套代码,但项目的复杂度和代码量也会随之增大,RN的优势就会被掩盖

架构建议

上面例举的这些问题是希望你能够正视RN,而不是在一些helloworld教程里面看了几句话就不顾实际业务需求强行使用它,RN足够好,但并非适合所有的项目。当然我最后还是选择了它,并且完成了很多项目。好了,如果你正在使用它,我也希望能够为你带来一些有实际作用的建议。

1,数据库
移动端数据库有很多种,目前比较流行的有SQLite/LevelDB/Realm等等,我使用过其中几个,最后在实际项目中由于数据量较大最后选择了realm,当然还有一部分原因是它的文档很好,更新速度也足够快。关于数据库的选型你可以参考一些评测与实践文章,根据自身的业务需求准备,RN自身也有一个简化版的storage,小型项目也可以考虑直接使用自身的异步存储型数据库。

这里主要的问题在于大型项目需要一个基础数据库,基础数据库中携带类似于元数据的信息,你既可以在项目开始运行之后从服务端下载,也可以考虑将数据库放在包中直接安装。在实践多种不同的方式后,最后我选择了原生代码拷贝基础数据库,js代码链接被拷贝的数据库做法。

这样做并不难,你只需要会几句简单的OC/JAVA就可以做到,必须使用原生代码的原因在于原生代码总是优先执行,然后才会链接bundle,特别是在iOS中,应用是处于沙箱环境中,用javascript去解决还是需要映射原生函数才有可能。它的好处之一是,在未来你的安卓与iOS版本不对齐的情况下,可以优先为两者准备不同的基础书刊库去更新应用。(安卓总是可以更频繁快速的更新,iOS则受限于应用审核)

2,数据管理
RN的数据管理可以照搬react的方式,经验在于你的熟练程度与项目的大小。需要考虑的是大多数时候这些数据都不会直接从http去请求,而是从数据库中照搬,在合适的时候准备更新数据库。当然较小的项目可以忽略这单。

如果你准备从数据库中读取大部分数据,Redux是一个非常不错的选择。你可以在任何时候从数据库中同步读取数据,然后发起合适的Action,这些Action只是为了更新数据库而存在,同样的,在Redux中你也完全不必关心它们具体做了什么。我在合理的时间发起Action,然后它们运行了哪些代码对于页面来说不必在意的。

说到这里,你完全可以理解使用Realm的优越之处了:Realm的所有数据采取映射的方式,即从realm中取出的数据就是realm对象,它在realm被更新之后会自动更新,而且在你没有使用时完全不会有任何消耗。这与Rx的订阅机制类似。提到Rx是因为它也很适合RN的项目架构,特别你需要数据库与http更新混合时,你可以在页面中订阅这些数据,可以让页面中所有的数据得到有效的显示又能利用上realm的缓存。

在react-native的布局中,你会频繁的使用到listView组件,它为了性能考虑只有在listView数据源发生变化时才会重新渲染,有时候你不得不手动的cloneWith一次触发listView的render,当然你还得考虑列表的翻页、缓存、频繁diff的性能等等。这时候在项目中加入Rx也不失为明智之举,你可以用一些简单的实例操作符去过滤一些不想要的数据,控制数据的渲染速度等等,总之,它会大大的减少你的代码量。

3,项目分层

  • 图片与静态资源:我尝试把所有的图片与静态资源全部用对象的方式标示,例如在image文件夹下建立一个index.js文件,导出所有的图片对象,使用时不再频繁的require这些静态地址。静态资源的处理有一个很大的优点是项目变得更加的可控,无论合适你想要替换/更名/删除某一个静态资源时都轻而易举。多人协作也会更加简单优雅。
  • 数据操作:引入一些基础的库并不代表你可以完全屏蔽数据操作,最后还是会多出类似于model这样的管理层,我尝试把它们根据使用场景抽象,比如client负责本地数据库,server负责远端服务等等。
  • 公共服务:在RN中我尝试引入了ES7中的装饰器(decorator),将所有的公共服务注入需要的类。react或react-native中最常见的问题之一就是项目越大,import次数就越多,这会明显导致整体项目趋于臃肿难以维护。引入装饰器的用处之一就是消除这些频繁的引入,仅仅在需要的类前进行标明。
  • 组件划分:从大的角度看组件只有两类:木偶组件与智能组件,将他们区分开是第一步要完成的事,随后我们希望所有的组件可以按照使用类型来分布,如fom/button/modal等等,在每一个组件区域下用一个index.js来将它们集合起来统一返回对象。这是合理的,特别是身处于页面繁多的项目中。
  • 错误处理:如果你正在使用Redux可以很简单的处理所有可能的错误,我的经验是在任何Action集合中都保持一个error管理函数,它用来扩散属于自己的错误信息与类型,同时过滤公共的错误在公共错误中处理(如http错误),细分的错误处理可以在页面订阅错误时得到优美的体验。任何一步出错都可以在当前需要订阅的UI处予以最合理的显示。
  • 路由管理:Navigator是个好组件,你需要学会怎样使用它。建议你在任何使用到Navigator组件的地方都将它们剥离出来形成一个全新的文件,作为当前view文件夹的index.js。你可以选择跳过这些路由,但使用路由来默认管理views是一个明智的选择,特别是路由文件越来越多,套用了3层以上路由时,你会发现所有层级的view都一目了然,它们之间的关系也显而易见,当你需要对一个Navigator的UI做出改变时也很简单。我希望你再仔细研读Navigator的文档,正确合理的使用它是项目成功的关键一点。
  • 继承与组合:大多数时候建议你使用组合解决问题,如config= new Config,而非使用继承。如果继承的父类中有太多实例方法可能会致使子类函数不清晰,过于依赖状态等等。但也有一些例外,如你在构建一个基础的http类时即希望它能够被继承又希望它保持一个实例(有多个服务端或服务端方法各不相同),我给你一个不错的提示,可以建立一个公共的文件用来实例化,它只被使用一次且导出一个被共享的实例,需要继承时再返回去寻找原来未被实例化的父类。当然,具体组合还是继承也需要考虑项目本身的需求,使用类来做一些重复且具备缓存的事情是非常明智的,但你需要时刻警惕它们是否共享一个实例和类本身的可维护性。

其他建议与风格

虽然这有些不合时宜,但我还是非常推荐你尽可能使代码Immutable化,特别是在RN这样依赖状态的框架项目中。类似于函数式的编程可以大大简化函数对于状态的依赖,减少它们之间的互相作用,无论在什么情况下,一个函数的终点可以得到确认。这很关键,比如你有多个或复杂的listView需要维护,我想你不愿意一些状态随意的触发render,为此你会做出很多的状态判断,那为什么不试试从项目开始就避免这些state的多状态变化呢?即便现在你很难体会它的优雅之处,但总有一天当你考虑优化某个函数时,会一拍大腿再次打开这篇文章。

这与Redux的思想也并不违背,比如reducer就是纯函数,在智能组件与页面中也是一样,收到无论是Rx/Realm/Redux或是你的其他自建数据层发送的状态时,将它们在一个函数或几个函数段中统一进行处理,然后再分发,比ui/组件中处处接受状态要好得多。

我本意是给大家更多关于ReactNative的建议,但只要我想说一些关于编程的建议总会不由自主地为别人强加一些编程思想,希望你不会对此反感。我希望大家都能够写出足够好的代码,对RN或是任何框架都有自己的理解,但这在之前,尝试一些新东西也不为过,这里列出的几种方案可能你听说过但并不愿意尝试,今天请给它们一次机会,如果真的对你并不适合在去除也不是一件坏事。

附,常见错误及解决方案

  1. 新建逻辑或文件没有任何效果:这很可能是你的文件没有被引入所致,在新建一个组件/静态资源/逻辑代码时都不要忘记将它们引入,如果已经引入可以尝试再次编译。
  2. 数据库已连接但查询不到数据:如果你拷贝了数据库,检查iox/android的基础数据库是否存在问题,或更新数据库版本;如果没有请检查数据库是否被链接,是否有数据库对象,以及数据库版本。
  3. 无法找到xxxx(not found xxx):在编译过程中你新增了某个文件未能得到有效的watch,请尝试重新build。
  4. http错误:打印错误码对比逐步排除。
  5. 页面动画缓慢:检查ios模拟器是否打开了慢动画(debug/slowAnimations)或是否存在内存泄露。
  6. 编译失败:build过程报错多数是原生代码有问题,请根据命令行提示修改java/oc模板代码。
  7. 编译成功但无法找到模拟器:android出现此情况请优先考虑是否成功加载模拟器,模拟器是否链接网络等。ios出现此问题多数是本机使用了全局代理所致。
  8. string/float/bool/…类型错误:多数是数据库的schema有问题,检查模型定义与存入的数据类型是否一致。
  9. realm/xxxdata/… version …:可能存在一个比当前版本更高的数据库版本,升级当前版本可解决。
  10. 页面滑入时透明无高度:页面需要一个背景色。
  11. 出入页面时向右上方飞出飞入或类似问题:引入全局动画动画或引入组件携带了全局动画所致,在动画组件装载或卸载时view未能有效的计算高度。卸载全局动画组件或在更早/更晚时规定view的高度。
  12. Obejct/any prototype not define:判断对象是否存在此方法或属性。
1 1 收藏 评论

关于作者:WittBulter

简介还没来得及写 :) 个人主页 · 我的文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部