为什么 ReactJS 不适合复杂的前端项目?
问题一:ReactJS组件难以在复杂交互页面中复用
ReactJS中的最小复用单位是组件。ReactJS的组件比AngularJS的Controller和View 要轻量些。 每个组件只需要前端开发者提供一个 render
函数,把 props
和 state
映射成网页元素。
这样的轻量级组件在渲染简单静态页面时很好用, 但是如果页面有交互,就必须在组件间传递回调函数来处理事件。
我将在《More than React(二)组件对复用性有害?》中用原生DHTML API、ReactJS和Binding.scala实现同一个需要复用的页面,介绍Binding.scala如何简单实现、简单复用复杂的交互逻辑。
问题二:ReactJS的虚拟DOM 算法又慢又不准
ReactJS的页面渲染算法是虚拟DOM差量算法。
开发者需要提供 render
函数,根据 props
和 state
生成虚拟 DOM。 然后 ReactJS 框架根据 render
返回的虚拟 DOM 创建相同结构的真实 DOM.
每当 state
更改时,ReacJS 框架重新调用 render
函数,获取新的虚拟 DOM 。 然后,框架会比较上次生成的虚拟 DOM 和新的虚拟 DOM 有哪些差异,然后把差异应用到真实DOM上。
这样做有两大缺点:
- 每次
state
更改,render
函数都要生成完整的虚拟 DOM. 哪怕state
改动很小,render
函数也会完整计算一遍。如果render
函数很复杂,这个过程就白白浪费了很多计算资源。 - ReactJS框架比较虚拟DOM差异的过程,既慢又容易出错。比如,假如你想要在某个
<ul>
列表的顶部插入一项<li>
,那么ReactJS框架会误以为你修改了<ul>
的每一项<li>
,然后在尾部插入了一个<li>
。
这是因为 ReactJS收到的新旧两个虚拟DOM之间相互独立,ReactJS并不知道数据源发生了什么操作,只能根据新旧两个虚拟DOM来猜测需要执行的操作。 自动的猜测算法既不准又慢,必须要前端开发者手动提供 key
属性、shouldComponentUpdate
方法、componentDidUpdate
方法或者 componentWillUpdate
等方法才能帮助 ReactJS 框架猜对。
我将在《More than React(三)虚拟DOM已死?》中比较ReactJS、AngularJS和Binding.scala渲染机制,介绍简单性能高的Binding.scala精确数据绑定机制。
问题三:ReactJS的HTML模板功能既不完备、也不健壮
ReactJS支持用JSX编写HTML模板。
理论上,前端工程师只要把静态HTML原型复制到JSX源文件中, 增加一些变量替换代码, 就能改造成动态页面。 理论上这种做法要比Cycle.js、Widok、ScalaTags等框架更适合复用设计师提供的HTML原型。
不幸的是,ReactJS对HTML的支持残缺不全。开发者必须手动把class
和for
属性替换成className
和htmlFor
,还要把内联的style
样式从CSS语法改成JSON语法,代码才能运行。 这种开发方式下,前端工程师虽然可以把HTML原型复制粘贴到代码中,但还需要大量改造才能实际运行。 比Cycle.js、Widok、或者、ScalaTags省不了太多事。
除此之外,ReactJS还提供了propTypes
机制校验虚拟DOM的合法性。 然而,这一机制也漏洞百出。 即使指定了propTypes
,ReactJS也不能在编译前提前发现错误。只有测试覆盖率很高的项目时才能在每个组件使用其他组件时进行校验。 即使测试覆盖率很高,propTypes
仍旧不能检测出拼错的属性名,如果你把onClick
写成了onclick
, ReactJS就不会报错,往往导致开发者额外花费大量时间排查一个很简单的bug。
我将在《More than React(四)HTML也可以编译?》中比较ReactJS和Binding.scala的HTML模板,介绍Binding.scala如何在完整支持XHTML语法的同时静态检查语法错误和语义错误。
问题四:ReactJS与服务器通信时需要复杂的异步编程
ReactJS从服务器加载数据时的架构可以看成MVVM(Model–View–ViewModel)模式。 前端工程师需要编写一个数据库访问层作为Model,把ReactJS的state
当做ViewModel,而render
当做View。 Model负责访问数据库并把数据设置到state
(即View Model)上,可以用Promise和fetch API实现。 然后,render
,即View,负责把View Model渲染到页面上。
在这整套流程中,前端程序员需要编写大量闭包组成的异步流程, 设置、访问状态的代码五零四散, 一不小心就会bug丛生,就算小心翼翼的处理各种异步事件,也会导致程序变得复杂,既难调试,又难维护。