2014年八月,Codecademy为了更新用户的学习体验,决定采用Facebook的React.js库,这是一个用于编写JavaScript UI的类库。在开始阶段,我们的问题是很难找到React在复杂应用中应用的示例,例如像Codecademy这样复杂的应用。因为大多数的教程都是针对小型示例应用上的特性,而不是针对在开发大型应用中更常见的问题。在本文中,我不仅会对React的使用进行一番概述,还会特别说明在大型web应用程序中使用React的某些特别注意事项。
React是什么?
简单来说:React是一个使用JavaScript创建用户界面的代码库。与编写用户界面常见的方式不同,React将每个UI元素视为一个抑制的状态机。它并不是类似于AngularJS这样的“框架”。虽然Facebook有时会将React描述为“MVC中的V”,但我发觉这一描述并没有什么帮助,因为React应用并不需要遵守MVC模型。React能够帮助你创建快速的用户界面,处理复杂的交互,而无需编写大量糟糕的代码。
如果你打算在工作中使用React,你需要了解以下特性:
“React会为你处理DOM”
DOM操作的开销很大,而React的吸引力很大程度上来自于它对这一问题的处理方式。React通过对自身虚拟DOM的维护,只在需要时进行重新渲染,将DOM操作的数量降至了最低,这要归功于React中高性能的比较操作的实现。
这就意味着你很少会需要直接与DOM打交道,与之相反,React会替你处理DOM的操作。这一特性也是诸多React设计的基础。如果你打算滥用React的API,或是打算按照自己的方式进行改动,那有可能会影响到React对DOM的理解。
该特性也使得使用Node.js进行内置的服务端渲染成为可能,这一点就使你能够轻易地创建对于SEO友好的页面。
“React使用声明式风格以及组件”
在React中,所有的组件都必须继承自Component类。组件中包含了属性(由父类决定)和状态(能够自行改变,通常是基于用户行为进行改变),组件的渲染和行为应当完全由它们的状态和属性所决定(而不依赖于任何其它值),因此组件就是状态机。这一模型鼓励使用者创建模块化的UI,并且在实践中能够简化UI的操作与创建工作。
“React将标记紧密地结合在JavaScript中”
虽然在JavaScript中编写HTML代码听起来很奇怪,但在React中这是一种自然的选择。JSX是原生的JS与HTML标记相结合的一种语言,对于它的使用是可选的,但我强烈建议你选择这种方式。React认为,由于你的标记已经紧密地结合在控制这些标记的JavaScript中,因此可以将它们安置在同一个文件中,我也同意这一看法。
“单向信息流动”
这一点更多的是一种通用的React模式,而不是一种严格的规则。信息的流动在React中倾向于单向流动。在本文的稍后部分中,当我们开始考虑在大型应用程序中如何处理信息流动时,会再次提及这一模式。
解析React应用程序
为了让这些原则显得更为清晰,让我们看看Codecademy的学习环境是如何使用React进行构建的。
学习环境
正如你在这个屏幕截图中所看到的一样,主要的学习环境由多个不同的UI元素所组成。某些元素是始终展现在页面上的,例如header、menu和navigation。而有些组件会根据当前的练习的不同,处于显示或是不显示的状态。比方如,根据课程的不同,web浏览器、命令行和代码编辑器可能会进行混合或是匹配。
创建该界面的逻辑解决方案是为各个部分创建React组件。在下面的屏幕截图中,我将特别指出主要的React组件:
学习环境以及对应的组件
每个组件都有可能包含多个子组件:比方如,屏幕左方的Lesson面板实际上就包含了多个组件:
组成Lesson组件的各个子组件
在这个示例中,我们将使用React以决定哪些组件应该显示在该lesson面板中。举例来说:
只有在用户已登录的前提下,才会显示“Report a Problem”按钮
只有在该练习中包括测试的情况下,才会显示Instructions部分
此外,React会处理该组件与其它组件之间的信息流动。整个学习环境对应着一个父组件,该组件会持续跟踪多个状态,例如当前用户处于哪个练习中。父组件会为子组件的属性进行相应的赋值,以决定这些子组件如何显示。
现在,让我们来看一个组件通信的示例,以下组件来自经过大量简化后的组件树结构:
LearningEnvironment
CodeEditor
RunButton
ErrorDisplayer
Navigation
与代码提交相关的某些组件
如果某个用户打算运行他的代码,我们该如何处理这一工作流?我们将尝试对他们提交的代码进行测试,然后显示错误信息,或是允许用户继续下一题。以下是某个可能发生的工作流:
当用户单击RunButton之后,该组件会在事件的调用中,通过回调方式通知它的父组件CodeEditor。
CodeEditor组件会通过另一个回调函数通知它的父组件,即Learning Environment组件,并将用户的当前代码传递给父组件。
LearningEnvironment组件将针对用户的代码进行测试。
根据结果的不同……
LearningEnvironment组件会对CodeEditor中的属性errorMessage赋值,CodeEditor则会依次为它的子组件ErrorDisplayer中的属性errorMessage赋值。
如果用户已经完成了该练习的所有测试,LearningEnvironment组件就会为Navigation组件中的属性progress赋值。
如果我们的组件都能够像在LearningEnvironment组件中的render方法一样进行声明(同样进行了大量简化),那么就可以通过一个单一的函数调用实现整个UI的更新:
请记住,React中混合了JavaScript和HTML标记。在这个例子中,render方法定义了一个LearningEnvironment组件,其中包括了一个CodeEditor组件和一个Navigation组件。
我们可以更新LearningEnvironment组件的状态,它会触发组件的重绘,并在必要时更新子组件。
这就是全部的代码了。React以一种优雅而简单的方式替我们处理UI的更新操作。
大型应用程序中的考虑因素:信息流动
正如我之前所说的一样,React不一定要遵循MVC模型,实际上,你可以按照任何你喜欢的方式处理信息流动,但你需要一种确切的信息策略。为了让属性的变化传递给子-子-子-子-子组件,你是否需要将该属性一路传递下去,哪怕中间的那些子组件完全不需要了解该属性?如果该叶子节点接受用户输入,它又该如何将这一变更通知它的父-父-父-父组件呢?
在大型应用程序中,这种处理方式是很令人受挫的。即使是在以上那个简单的示例中,Run按钮该如何与LearningEnvironment组件之间传递用户的行为呢?我们需要传递回调函数,但这种方式很难写出真正模块化、可重用的组件。
Codecademy的解决方案是通过创建通信适配器(Adapter),以管理各别组件间的信息流动。与传递回调函数的方式不同,高层次的组件,例如CodeEditor会接收到一个Adapter,它为重要的通信任务提供了一种单一的接口。举例来说,当CodeEditor处于显示状态时,LearningEnvironment会创建一个Adapter,它能够生成和处理与用户提交代码相关的事件。
这种方式也不是完全没有缺陷的,我也在React大会的演讲中针对这一点进行了详细的论述。我的主要观点在于,无论你如何处理组件树中的信息流动,你的团队都应该坚持一种一致的策略。
大型应用程序中的考虑因素:整合
React的上手非常简单,但要在你的工作流中高效地使用它,你需要一些工具的支持。举例来说,我们使用了以下工具:
用一段脚本对.jsx文件的本地文化进行监控,并在必要时对它们进行重新编译
一个独立的node.js服务器,用于处理服务端的展示
用于在需要时自动生成新组件文件的开发者工具
以上这些工具都不是非常复杂。对于.jsx的监控来说,Gulp是一个很好的选择,不过我们选择了使用Go语言自行编写脚本。我们使用了一个简单的批处理脚本负责生成新的组件文件,这种方式也能够确保命名规范。如果你打算使用一个node.js服务器以负责服务端展示,你需要当心的是,要强制require.js能够获取到React代码中的变更可能会有些困难,因此我们创建了一个监控器,让它在必要时重启node服务器。
为什么使用React?
在我们重新设计整个学习环境时,我们需要决定选择使用哪一套工具或框架。我们最终选择了React,对这一决定我们感到非常满意。
我们对于React的欣赏之处主要在于以下几个方面:
它经过了实战检验
React已经在Facebook和Instagram的生产环境中得到应用,因此我们对于它的性能和可靠性很有信心。目前为止,它在我们的平台上同样表现良好,我们也没有遇到过任何严重的问题。
组件化的方式便于理解
React对每个独立的组件进行单独处理,这些组件会按照它内部的状态进行展现,因此对于某一时刻应该发生什么事,很容易形成概念化的理解。你的应用程序会有效地成为一个大型状态机。这意味着你可以单独测试UI中的每个片段,同样可以自由地添加新组件,而无需担心会影响整个应用程序中其它部分的代码。
SEO非常容易实现
因为React本身就支持服务端展现,因此在搜索引擎看来,你提供的是一个基本已完成的页面,这对于SEO来说是一个极大的优势,而所需的工作量非常小。的确,这一点必需由Node完成。由于Codecademy的主应用是由Rails编写的,因此我们搭建了一个独立的Node服务器,专门用于处理React的展现。
React能够兼容遗留代码,并且它的灵活性足以应对未来
虽然采用一整套框架的确是一件大事,但你也可以慢慢地尝试将React添加到现有的代码库中。与之类似,如果将来我们需要移除React,我们也可以轻易地实现这一点。在Codecademy,我们首先决定完全使用React来编写一个全新的项目,以便尝试它的功能,并学习如何以最佳的方式使用它。这个项目很成功,因此我们现在基本上在所有的新UI元素中都使用React了。我建议你首先做些功课,创建一些实验项目,然后再考虑怎样让React适应于你的现有代码库。
不必担心编写样板代码了
在编写样板代码上所花的时间越少,就意味着你可以将更多的时间花在更有意义的问题上了。从这个角度上来说,React是个既简洁又轻量级的类库。以下代码是创建一个新的组件所需的最少代码:
简短且切题,还有什么不满意的?
我们的社区正在成长
React社区的发展非常迅速。当你遇到各种问题时,你可以和许多社区成员讨论这一问题。并且,由于许多公司都已经在生产环境中使用了React(仅举几例,Facebook、Instagram、Yahoo!、Github和Netflix),因此我们并不独孤。
总结
React是一个轻量级、强大,并且经过实战检验的使用JavaScript创建用户界面的类库。它不是一个框架,而是一个强大的工具,或许会改变你进行前端开发的方式。我们认为它对于我们的前端开发来说,作用之大是难以置信的,而我们对于自己的选择也感到相当满意。对我自己来说,使用React进行工作至少是极大地影响了我思考编写用户界面的方式。我也乐于看到React的不断成长:现在Facebook已经通过React Native将React的功能带到移动开发上了,我想它的未来一定会是一片光明。
如果你打算上手使用React,它的教程是一个不错的逻辑起点。互联网上也有着大量介绍React中的关键概念的帖子。不要停下脚步,学习钻研,尝试着创建些什么,然后看看你对于React这种前端开发方式是怎么想的。我们非常乐于聆听你的想法。
关于作者
Bonnie Eisenman是一位来自于Codecademy.com的软件工程师。她最近刚刚从普林斯顿大学的计算机科学专业毕业。她对硬件也有一定兴趣,在业余时间喜欢从事一些Arduino方面的工作,以及乐曲编辑。她的Twitter帐号是@brindelle。
【QCon北京2015】永不止步的前端专题,由蚂蚁金服体验技术部负责人王保平(玉伯)主持,重点讨论:移动 H5 应用的开发,包括 Web 与 Native 的融合;企业平台类富应用的开发,涵盖 Angular、React、Polymer 等技术方向;前端工程与质量体系的建设,关注团队的高效协同、品质把控;前端工程师的历史转身,何去何从。敬请期待。
文章评论