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