颠覆式前端 UI 开发框架:React

阅读数:134006 2014 年 12 月 14 日

话题:Web框架语言 & 开发架构

基于 HTML 的前端界面开发正变得越来越复杂,其本质问题基本都可以归结于如何将来自于服务器端或者用户输入的动态数据高效的反映到复杂的用户界面上。而来自 Facebook 的React框架正是完全面向此问题的一个解决方案,按官网描述,其出发点为:用于开发数据不断变化的大型应用程序(Building large applications with data that changes over time)。相比传统型的前端开发,React 开辟了一个相当另类的途径,实现了前端界面的高效率高性能开发。

首先,对于 React,有一些认识误区,这里先总结一下:

  • React 不是一个完整的 MVC 框架,最多可以认为是 MVC 中的 V(View),甚至 React 并不非常认可 MVC 开发模式;
  • React 的服务器端 Render 能力只能算是一个锦上添花的功能,并不是其核心出发点,事实上 React 官方站点几乎没有提及其在服务器端的应用;
  • 有人拿 React 和 Web Component 相提并论,但两者并不是完全的竞争关系,你完全可以用 React 去开发一个真正的 Web Component;
  • React 不是一个新的模板语言,JSX 只是一个表象,没有 JSX 的 React 也能工作。

1. React 的原理

在 Web 开发中,我们总需要将变化的数据实时反应到 UI 上,这时就需要对 DOM 进行操作。而复杂或频繁的 DOM 操作通常是性能瓶颈产生的原因(如何进行高性能的复杂 DOM 操作通常是衡量一个前端开发人员技能的重要指标)。React 为此引入了虚拟 DOM(Virtual DOM)的机制:在浏览器端用 Javascript 实现了一套 DOM API。基于 React 进行开发时所有的 DOM 构造都是通过虚拟 DOM 进行,每当数据变化时,React 都会重新构建整个 DOM 树,然后 React 将当前整个 DOM 树和上一次的 DOM 树进行对比,得到 DOM 结构的区别,然后仅仅将需要变化的部分进行实际的浏览器 DOM 更新。而且 React 能够批处理虚拟 DOM 的刷新,在一个事件循环(Event Loop)内的两次数据变化会被合并,例如你连续的先将节点内容从 A 变成 B,然后又从 B 变成 A,React 会认为 UI 不发生任何变化,而如果通过手动控制,这种逻辑通常是极其复杂的。尽管每一次都需要构造完整的虚拟 DOM 树,但是因为虚拟 DOM 是内存数据,性能是极高的,而对实际 DOM 进行操作的仅仅是 Diff 部分,因而能达到提高性能的目的。这样,在保证性能的同时,开发者将不再需要关注某个数据的变化如何更新到一个或多个具体的 DOM 元素,而只需要关心在任意一个数据状态下,整个界面是如何 Render 的。

如果你像在 90 年代那样写过服务器端 Render 的纯 Web 页面那么应该知道,服务器端所要做的就是根据数据 Render 出 HTML 送到浏览器端。如果这时因为用户的一个点击需要改变某个状态文字,那么也是通过刷新整个页面来完成的。服务器端并不需要知道是哪一小段 HTML 发生了变化,而只需要根据数据刷新整个页面。换句话说,任何 UI 的变化都是通过整体刷新来完成的。而 React 将这种开发模式以高性能的方式带到了前端,每做一点界面的更新,你都可以认为刷新了整个页面。至于如何进行局部更新以保证性能,则是 React 框架要完成的事情。

借用Facebook 介绍 React 的视频中聊天应用的例子,当一条新的消息过来时,传统开发的思路如上图,你的开发过程需要知道哪条数据过来了,如何将新的 DOM 结点添加到当前 DOM 树上;而基于 React 的开发思路如下图,你永远只需要关心数据整体,两次数据之间的 UI 如何变化,则完全交给框架去做。

可以看到,使用 React 大大降低了逻辑复杂性,意味着开发难度降低,可能产生 Bug 的机会也更少。至于 React 如何做到将原来 O(n^3) 复杂度的 Diff 算法降低到 O(n),大家可以参考这篇文章

2. 组件化的开发思路

虚拟 DOM 不仅带来了简单的 UI 开发逻辑,同时也带来了组件化开发的思想,所谓组件,即封装起来的具有独立功能的 UI 部件。React 推荐以组件的方式去重新思考 UI 构成,将 UI 上每一个功能相对独立的模块定义成组件,然后将小的组件通过组合或者嵌套的方式构成大的组件,最终完成整体 UI 的构建。例如,Facebook 的 instagram.com 整站都采用了 React 来开发,整个页面就是一个大的组件,其中包含了嵌套的大量其它组件,大家有兴趣可以看下它背后的代码。

如果说 MVC 的思想让你做到视图 - 数据 - 控制器的分离,那么组件化的思考方式则是带来了 UI 功能模块之间的分离。我们通过一个典型的 Blog 评论界面来看 MVC 和组件化开发思路的区别。

对于 MVC 开发模式来说,开发者将三者定义成不同的类,实现了表现,数据,控制的分离。开发者更多的是从技术的角度来对 UI 进行拆分,实现松耦合。

对于 React 而言,则完全是一个新的思路,开发者从功能的角度出发,将 UI 分成不同的组件,每个组件都独立封装。

在 React 中,你按照界面模块自然划分的方式来组织和编写你的代码,对于评论界面而言,整个 UI 是一个通过小组件构成的大组件,每个组件只关心自己部分的逻辑,彼此独立。这样最外层的界面的 Render 只需要如下代码:

通过这种方式,每个组件的 UI 和逻辑都定义在组件内部,和外部完全通过 API 来交互,通过组合的方式来实现复杂的功能。React 认为一个组件应该具有如下特征:

(1)可组合(Composeable):一个组件易于和其它组件一起使用,或者嵌套在另一个组件内部。如果一个组件内部创建了另一个组件,那么说父组件拥有(own)它创建的子组件,通过这个特性,一个复杂的 UI 可以拆分成多个简单的 UI 组件;

(2)可重用(Reusable):每个组件都是具有独立功能的,它可以被使用在多个 UI 场景;

(3)可维护(Maintainable):每个小的组件仅仅包含自身的逻辑,更容易被理解和维护;

(4)可测试(Testable):因为每个组件都是独立的,那么对于各个组件分别测试显然要比对于整个 UI 进行测试容易的多。

3. 一个 React 组件开发的例子:Tab 选择器

上面从总体上介绍了 React 带来的全新的前端开发方法,以及其带来的影响,并没有介绍如何使用。为了让大家对其有一个具体的印象,这里实际来开发一个简单的组件:Tab 选择器。网店的产品页面通常需要这样的控件来选择产品属性,例如选择衣服的颜色。这个控件接受一个数据源展示多个 Tab 供点击,点击后就选中了某个颜色,界面通常如下图所示。

按传统方式,我们可以用如下代码来实现一个 jQuery 插件:

用 React 方式,代码如下:

通过比较可以看到,jQuery 插件方式,开发者首先需要考虑控件第一次 Render 出来时的 DOM 构建;其次,需要知道如何切换 UI 上的选中状态。

而 React 的方式,开发者仅仅需要考虑整体界面的 DOM 构建,不再需要关心局部更新,每次在一个 React 的 Component 上调用 setState 方法,都会触发 render 来重建整个界面。从开发思想的角度看,你可以认为每次数据的更新都会做整体的完全刷新。逻辑简单而直接。

如果我们再多考虑一步,控件的值不只在初始化和点击时可以设置,而且还可以通过程序动态的去设置。那么对于 jQuery 的方案而言,我们需要额外的方法和入口去做对应的 UI 更新。而对于 React 方式,则无需做任何改变,外部只需调用 setState 方法改变它的状态即可。这就是简化 UI 逻辑带来的好处。

完整的代码和演示已上传在 Github 上: https://github.com/supnate/react-tab-selector ,大家可以实际试用一下。

4. 结论

如上所述,React 是一个全新思路的前端 UI 框架,它完全接管了 UI 开发中最为复杂的局部更新部分,擅长在在复杂场景下保证高性能;同时,它引入了基于组件的开发思想,从另一个角度来重新审视 UI 的构成。通过这种方法,不仅能够提高开发效率,而且可以让代码更容易理解,维护和测试。Facebook 以这样一种方式将沉淀多年的前端开发经验和技术的积累完全开源出来,值得所有前端开发者去借鉴和学习。并且 React 在发布一年的时间里就获得了极大的关注,Github 上拥有超过 1 万的 Star,相信其对前端开发的方向,甚至 Web Component 的标准,都将产生一定的影响。

作者简介

王沛,邮件:supnate@gmail.com

关注 IT 趋势,承载前沿、深入、有温度的内容。感兴趣的读者可以搜索 ID:laocuixiabian,或者扫描下方二维码加关注。


感谢郭蕾对本文的审校。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ)或者腾讯微博(@InfoQ)关注我们,并与我们的编辑和其他读者朋友交流。