转载

React.js在Codecademy中的实际应用

自从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是什么?

简单来说: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进行构建的。

(单击图片以放大)

React.js在Codecademy中的实际应用

图1:学习环境。

正如你在这个屏幕截图中所看到的一样,主要的学习环境由多个不同的UI元素所组成。某些元素是始终展现在页面上的,例如header、menu和navigation。而有些组件会根据当前的练习的不同,处于显示或是不显示的状态。比方如,根据课程的不同,web浏览器、命令行和代码编辑器可能会进行混合或是匹配。

创建该界面的逻辑解决方案是为各个部分创建React组件。在下面的屏幕截图中,我将特别指出主要的React组件:

(单击图片以放大)

React.js在Codecademy中的实际应用

图2:学习环境以及对应的组件。

每个组件都有可能包含多个子组件:比方如,屏幕左方的Lesson面板实际上就包含了多个组件:

(单击图片以放大)

React.js在Codecademy中的实际应用

图3:组成Lesson组件的各个子组件。

在这个示例中,我们将使用React以决定哪些组件应该显示在该lesson面板中。举例来说:

  • 只有在用户已登录的前提下,才会显示“Report a Problem”按钮
  • 只有在该练习中包括测试的情况下,才会显示Instructions部分

此外,React会处理该组件与其它组件之间的信息流动。整个学习环境对应着一个父组件,该组件会持续跟踪多个状态,例如当前用户处于哪个练习中。父组件会为子组件的属性进行相应的赋值,以决定这些子组件如何显示。

现在,让我们来看一个组件通信的示例,以下组件来自经过大量简化后的组件树结构:

  • LearningEnvironment
  • CodeEditor
  • RunButton
  • ErrorDisplayer
  • Navigation

(单击图片以放大)

React.js在Codecademy中的实际应用

图4:与代码提交相关的某些组件。

如果某个用户打算运行他的代码,我们该如何处理这一工作流?我们将尝试对他们提交的代码进行测试,然后显示错误信息,或是允许用户继续下一题。以下是某个可能发生的工作流:

  • 当用户单击RunButton之后,该组件会在事件的调用中,通过回调方式通知它的父组件CodeEditor。
  • CodeEditor组件会通过另一个回调函数通知它的父组件,即Learning Environment组件,并将用户的当前代码传递给父组件。
  • LearningEnvironment组件将针对用户的代码进行测试。

根据结果的不同……

  • LearningEnvironment组件会对CodeEditor中的 属性 errorMessage 赋值,CodeEditor则会依次为它的子组件ErrorDisplayer中的属性errorMessage赋值。
  • 如果用户已经完成了该练习的所有测试,LearningEnvironment组件就会为Navigation组件中的属性progress赋值。

如果我们的组件都能够像在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文件的本地文化进行监控,并在必要时对它们进行重新编译
  • 一个独立的node.js服务器,用于处理服务端的展示
  • 用于在需要时自动生成新组件文件的开发者工具

以上这些工具都不是非常复杂。对于.jsx的监控来说,Gulp是一个很好的选择,不过我们选择了使用Go语言自行编写脚本。我们使用了一个简单的批处理脚本负责生成新的组件文件,这种方式也能够确保命名规范。如果你打算使用一个node.js服务器以负责服务端展示,你需要当心的是,要强制require.js能够获取到React代码中的变更可能会有些困难,因此我们创建了一个监控器,让它在必要时重启node服务器。

为什么使用React?

在我们重新设计整个学习环境时,我们需要决定选择使用哪一套工具或框架。我们最终选择了React,对这一决定我们感到非常满意。(关于我们如何选择一套JavaScript框架的详细过程,可以在以下演讲视频中找到: https://www.youtube.com/watch?v=U5yjPG5mHZ8 )

我们对于React的欣赏之处主要在于以下几个方面:

它经过了实战检验

React已经在Facebook和Instagram的生产环境中得到应用,因此我们对于它的性能和可靠性很有信心。目前为止,它在我们的平台上同样表现良好,我们也没有遇到过任何严重的问题。

组件化的方式便于理解。

React对每个独立的组件进行单独处理,这些组件会按照它内部的状态进行展现,因此对于某一时刻应该发生什么事,很容易形成概念化的理解。你的应用程序会有效地成为一个大型状态机。这意味着你可以单独测试UI中的每个片段,同样可以自由地添加新组件,而无需担心会影响整个应用程序中其它部分的代码。

SEO非常容易实现。

因为React本身就支持服务端展现,因此在搜索引擎看来,你提供的是一个基本已完成的页面,这对于SEO来说是一个极大的优势,而所需的工作量非常小。的确,这一点必需由Node完成。由于Codecademy的主应用是由Rails编写的,因此我们搭建了一个独立的Node服务器,专门用于处理React的展现。

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 。

关于作者

React.js在Codecademy中的实际应用 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

正文到此结束
Loading...