React.js 在 Codecademy 中的实际应用

阅读数:5773 2015 年 3 月 24 日 11:14

自从 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 进行构建的。

(单击图片以放大)

1:学习环境。

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

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

(单击图片以放大)

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

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

(单击图片以放大)

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

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

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

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

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

  • LearningEnvironment
  • CodeEditor
  • RunButton
  • ErrorDisplayer
  • Navigation

(单击图片以放大)

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

关于作者

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

收藏

评论

微博

用户头像
发表评论

注册/登录 InfoQ 发表评论