React-入门指南-全-
React 入门指南(全)
原文:
zh.annas-archive.org/md5/7f9f750654ee9c470c2b501ee0ba1d55译者:飞龙
前言
学习 ReactJS 是一种轻量但强大的构建出色 UI 组件的方法!这本书将帮助你使用 ReactJS 开发健壮、可重用且快速的用户界面。这本书将确保平稳无缝地过渡到 ReactJS 生态系统。本书充满了实际应用。从设置到实现、测试和部署:探索前端 Web 开发的新前沿。ReactJS,作为 MVC 架构的 V,由 Facebook 和 Instagram 的开发者开发。我们将深入 ReactJS 的世界,探索单向数据流、虚拟 DOM、DOM 差异,这些都是 ReactJS 利用以提高 UI 性能的。你将通过逐步过程学习 ReactJS 的关键概念。你还将学习 ReactJS 中用于未来浏览器的 ES6 语法,以及用于在当前浏览器中支持它的转译技术。
你不仅将学习如何应用和实现 ReactJS 概念,还将了解如何测试基于 JS 的应用程序并将它们部署。除此之外,你还将使用 Flux 架构开发一个完整的应用程序。你还将了解 Redux,它让你通过引入一些对更新的限制,轻松地理解如何在 ReactJS 应用程序中操作数据。通过涵盖概念及其理论解释的大量代码以及应用程序的截图,你将获得对 ReactJS 的深入理解。
本书涵盖内容
第一章, ReactJS 入门,是关于 React 的简要概述,包括在哪里下载以及如何在你的网页上使其工作。它将演示如何创建你的第一个 React 组件。
第二章, 探索 JSX 和 ReactJS 结构,将展示与第一章中创建的相同简单的 React 组件,使用 JSX 语法构建。它将解释 JSX 的目的并揭开其使用的神秘面纱。它将比较一些较旧的模板技术与 JSX,并试图澄清一些关于它的常见问题。
第三章, 使用属性,将帮助你开始开发自己的应用程序。它将使用 Facebook Open Graph API。这将涵盖如何配置它、获取你的朋友列表以及使用 React 渲染它。在此之后,我们将把 UI 分解成小的组件。
第四章, 有状态组件和事件,涵盖了具有状态的组件、它们之间的通信实践以及如何响应用户的输入/事件,以便 UI 反映这种状态。本章还涵盖了状态如何通过虚拟 DOM 改变 React UI 的性能。
第五章, 组件生命周期和 React 中的新 ECMAScript,探讨了这种 React 组件的生命周期。此外,我们还将深入研究未来的 ECMA Script 语法以及 React 社区从 0.13.0 版本开始使用的少量变化。为此,我们将回顾 react 库中的某些 ES6 和 ES7 特性。
第六章, 使用 Flux 进行 React 操作,将解释 Flux 架构,该架构用于构建客户端 Web 应用程序。它通过使用单向数据流来补充 React 的可组合视图组件。对 FLUX 架构的所有组件(视图、存储、动作和调度器)都有深入的解释。
第七章, 使你的组件可重用,将涵盖 React 的良好实践和模式。这包括开发可重用组件的实践,如何以更好的数据流结构化你的组件层次结构,以及如何验证你的组件行为。
第八章, 测试 React 组件,将展示如何测试你的 React 代码,因为这在 React 中从未如此简单。为此,我们将对迄今为止开发的 app 进行单元测试。
第九章, 为部署代码做准备,告诉我们 React 自带一个 JSX 转换器,它可以在运行时工作。然而,这不应该在生产环境中部署。本章将指导你使用 node 库(如 Webpack 和 Gulp)离线构建这些文件的方法。
第十章, 接下来是什么,解释了一些其他高级概念,例如 react-router、react-ajax、热重载、redux、同构应用程序等。
你需要这本书的以下内容
基本要求是安装 NodeJS,然后安装 npm 包,如 react、react-tools、express 等。按章节提供的完整列表如下:
| 章节编号 | 需要的软件(含版本) |
|---|
| 1 | Nodejs 4.2.4ReactJS:
JSXTransformer:
安装 Python 或 httpster 以提供 web 服务器 Chrome / Mozilla ReactJS 插件/扩展以用于浏览器 JS 工具 |
| 2 | npm install react-tools |
|---|---|
| 3 | Open-Graph JavaScript SDK: developers.facebook.com/docs/javascript |
| 5 | ReactJS 版本 0.13.0 或更高 JSXTransformer(0.13.3) |
| 8 | npm install -g -d chai mocha jest-cli babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-2 react-addons-test-utils |
| 9 | npm install -g webpack browserifynpm install --save-dev gulp gulp-concat gulp-uglify gulp-react gulp-html-replacenpm install --save-dev vinyl-source-stream browserify watchify reactify gulp-streamify |
| 10 | npm install expressnpm install react-redux |
这本书适合谁阅读
无论您是 JS 世界的初学者还是一个经验丰富的 JS 开发者,这本书都将确保您在 ReactJS 生态系统中顺利过渡。您不仅将了解并实现 ReactJS 的概念,还将学习如何测试基于 JS 的应用程序并将它们部署。除此之外,您还将被介绍到 Flux,并基于 Flux 应用架构构建应用程序,这不仅仅是一个完整的框架,而是一种架构。您还将了解 Redux,它通过引入一些更新限制,让您能够轻松地操作 ReactJS 应用程序中的数据。通过涵盖理论解释的概念的大量代码和应用程序的截图,您将获得对 ReactJS 的简单而深入的理解。
习惯用法
在这本书中,您将找到多种文本样式,用于区分不同类型的信息。以下是一些这些样式的示例及其含义的解释。
文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 昵称将如下所示:“一旦安装了 Sublime 编辑器,请转到安装目录,您可以通过在当前目录中运行subl来从终端打开 Sublime,您将在 Sublime 中打开当前目录的文件。”
代码块设置如下:
<!DOCTYPE html>
<html>
<head>
<script src="img/fb-react-0.12.2.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
当我们希望您注意代码块中的特定部分时,相关的行或项目将以粗体显示:
function loadUserAndLikes () {
FB.api('/me', function (userResponse) {
React.render(<UserDetails userDetails={userResponse} />, document.getElementById('user'));
var fields = { fields: 'category,name,picture.type(normal)' };
FB.api('/me/likes', fields, function (likesResponse) {
React.render(<UserLikesList list={likesResponse.data} />, document.getElementById('main'));
});
});
}
任何命令行输入或输出将如下所示:
sudo npm install jest-cli –save-dev
新术语和重要词汇将以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,在文本中如下所示:“我在 MacOS X Yosemite 上使用 Atom 的经验是,字体质量看起来比 Sublime Text 差。如果您遇到这种情况,您只需在 Atom 的设置中取消选择使用硬件加速选项即可。”
注意
警告或重要注意事项将以这样的框框显示。
提示
技巧和窍门如下所示。
读者反馈
我们始终欢迎读者的反馈。请告诉我们您对这本书的看法——您喜欢或不喜欢的地方。读者反馈对我们很重要,因为它帮助我们开发出您真正能从中获得最大收益的标题。
要向我们发送一般反馈,请简单地通过电子邮件发送至 <feedback@packtpub.com>,并在邮件主题中提及本书的标题。
如果您在某个领域有专业知识,并且您有兴趣撰写或为书籍做出贡献,请参阅我们的作者指南,网址为www.packtpub.com/authors。
客户支持
现在您已经是 Packt 图书的骄傲拥有者,我们有一些事情可以帮助您从您的购买中获得最大收益。
下载示例代码
您可以从www.packtpub.com的账户下载本书的示例代码文件。如果您在其他地方购买了此书,您可以访问www.packtpub.com/support并注册,以便将文件直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
使用您的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在顶部的支持标签上。
-
点击代码下载与勘误。
-
在搜索框中输入书籍名称。
-
选择您想要下载代码文件的书籍。
-
从下拉菜单中选择您购买此书的来源。
-
点击代码下载。
下载文件后,请确保您使用最新版本解压缩或提取文件夹:
-
WinRAR / 7-Zip for Windows
-
Zipeg / iZip / UnRarX for Mac
-
7-Zip / PeaZip for Linux
下载本书的彩色图像
我们还为您提供了一个包含本书中使用的截图/图表彩色图像的 PDF 文件。彩色图像将帮助您更好地理解输出的变化。您可以从www.packtpub.com/sites/default/files/downloads/GettingStartedwithReact_ColorImages.pdf下载此文件。
勘误
尽管我们已经尽一切努力确保内容的准确性,但错误仍然可能发生。如果您在我们的某本书中发现了错误——可能是文本或代码中的错误——如果您能向我们报告这一点,我们将不胜感激。通过这样做,您可以节省其他读者的挫败感,并帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问www.packtpub.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并输入您的勘误详情来报告它们。一旦您的勘误得到验证,您的提交将被接受,勘误将被上传到我们的网站或添加到该标题的勘误部分下的现有勘误列表中。
要查看之前提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索框中输入书籍名称。所需信息将出现在勘误部分下。
盗版
互联网上对版权材料的盗版是一个跨所有媒体的持续问题。在 Packt,我们非常重视我们版权和许可证的保护。如果您在互联网上发现任何形式的我们作品的非法副本,请立即提供位置地址或网站名称,以便我们可以寻求补救措施。
请通过链接将涉嫌盗版材料发送至 <copyright@packtpub.com>。
我们感谢您在保护我们的作者和我们为您提供有价值内容的能力方面的帮助。
问题
如果您对本书的任何方面有问题,您可以联系 <questions@packtpub.com>,我们将尽力解决问题。
第一章:ReactJS 入门
在本章中,我们将探讨 ReactJS 的概述——它是什么以及这个强大且灵活的库的一些亮点。我们还将学习如何下载并在小型应用程序中使用它。本章将涵盖以下主题:
-
介绍 ReactJS
-
下载 ReactJS
-
工具
-
尝试 ReactJS
介绍 ReactJS
ReactJS 是一个由 Facebook 和 Instagram 创建的 JavaScript 库,旨在构建能够响应用户输入事件的用户界面(UI),同时创建和维护状态。状态用于维护组件的变化,这将在后续章节中详细说明。页面通过仅比较网页的更改和更新部分来加快加载速度(我们将在第四章“有状态组件和事件”中更详细地介绍虚拟 DOM(文档对象模型))。与传统的数据绑定系统相比,React 提供了一向数据流,这降低了复杂性,有助于创建可重用和封装的组件。我们还将探索 React 数据流在第七章“使你的组件可重用”中,以及如何使你的 UI 组件更具可重用性。在第七章“使你的组件可重用”中。
尽管许多开发者认为 ReactJS 只是 MVC 应用程序的 V,但它驱使你构建可重用组件,重新思考你的 UI 和最佳实践。如今,性能和可移植性对于构建用户界面至关重要,这主要归因于大量使用互联网可访问设备和项目快速的开发阶段。这可能导致前端代码复杂。使用一个有助于你的代码在性能和质量上增长的库的需求真的很重要;否则,你只会写出包含 UI 逻辑的大 HTML 文件,修改起来耗时且可能降低代码质量。ReactJS 鼓励以下最佳实践:
-
跟随一个模式
-
分离关注点
-
将 UI 分解为组件
-
组件之间的一向数据流通信
-
正确使用属性和状态
ReactJS 是一个库,它以与框架不同的方式处理 UI(视图)。假设我们正在构建一个单页应用(SPA),并希望处理路由系统,我们可以使用任何我们想要的处理路由的库。这适用于构建 SPA 所需的技术堆栈的每个其他部分,除了 UI 或,如一些人所说,当在 MVC/MV*架构上工作时,视图。在 ReactJS 的世界里,当你谈论视图时,实际上你是在谈论一个组件。它们之间有一点不同。React 组件持有视图的逻辑和行为。一般来说,一个组件代表视图的一个小部分,而许多这样的组件一起代表应用的整体视图。
我们将在第六章使用 FLUX 反应中更详细地讨论 MVC/MV*和 FLUX 架构。
注意
MVC 代表模型-视图-控制器,而 MV*代表模型-视图-任何东西。
构建或更改您 Web 应用的一个小部分非常简单。Facebook 就是用他们的评论界面做到了这一点。他们用 ReactJS 制作的一个新界面替换了它。有关如何在 Facebook 上使用 ReactJS 显示评论的详细代码,请参阅facebook.github.io/react/docs/tutorial.html。
这个由 Facebook 开发团队解释的评论界面为我们提供了实时更新和乐观评论,其中评论在服务器上保存之前就显示在列表中。还有一个 Facebook 开发者插件,允许用户使用他们的 Facebook 账户在您的网站上添加评论(developers.facebook.com/docs/plugins/comments)。
我的一个经验是在 ReactJS 中构建一个调查应用,并将其放置在生产中的某个 Web 应用中。ReactJS 提供了一组生命周期事件,这有助于与其他库、插件甚至框架的集成。在第五章组件生命周期中,我们将讨论 React 组件的所有生命周期阶段,而在第七章使您的组件可重用中,我们将引入验证并使用 Mixins 来组织我们的代码。
ReactJS 将 UI 元素视为对象。在构建 React 组件时,我们将通过封装视图逻辑和视图表示来模块化代码。这是支持组件化的另一个特性,也是虚拟 DOM 能够工作的原因之一。React 代码也可以用另一种语法编写,即 JSX(ECMASCRIPT 的扩展),而不是 JavaScript。尽管使用 JSX 不是强制性的,但它易于使用,并提高了代码的可读性。我们将在 第二章 探索 JSX 中深入了解 JSX 并了解它是如何工作的以及为什么它是必要的。
谁使用 ReactJS?
ReactJS 是用于构建 Web UI 组件的兴起 JavaScript 库之一,一些大型公司已经在生产中使用它。它们如下:
-
Instagram 网站
-
Facebook 评论、页面洞察、商业管理工具、Netflix、Yahoo、Atlassian 以及大多数新的 JS 开发
-
新的 JS 开发用于 Khan Academy、PayPal、AirBnb、Discovery Digital Networks 以及更多
-
一些在 《纽约时报》 内的项目
下载 ReactJS
在我们开始编写一些 ReactJS 代码之前,我们需要下载它。你可以通过他们的网站下载 ReactJS,facebook.github.io/react/downloads.html。
在撰写这本书的时候,ReactJS 目前处于版本 0.14.7。提供了两种 ReactJS 脚本版本——一个是用于开发的,如果你想要调试或者甚至贡献代码,它包含了所有核心代码和注释。另一个是用于生产的,它包含了额外的性能优化。以下是脚本版本的下载链接:
0.13.0 版本及更高版本包含了一组巨大的增强功能。它支持 ES6 类语法,并移除了混合模式,这些内容在 第五章 组件生命周期和 ReactJS 中的新 ECMAScript 中有所介绍。
在 ReactJS 下载页面中,还有其他带有附加功能的 ReactJS 脚本版本。这个脚本扩展了 ReactJS 库以支持动画和过渡,并提供了一些不属于核心 React 的其他实用工具。目前我们不需要下载这个版本,因为我们不会在接下来的示例中使用这些功能。
此外,还有一个 JSX 转换器脚本的下载链接。你可以在 cdnjs.cloudflare.com/ajax/libs/react/0.13.3/JSXTransformer.js 下载它。
它仅应在开发环境中使用,而不应在生产环境中使用。JSX 将在第二章,探索 JSX 和 ReactJS 结构 中更详细地介绍。
如果您正在使用工具来控制您的依赖项,例如 Node 包管理器 (NPM) 或 Bower,您也可以通过这些工具下载 ReactJS。详细信息请参阅 facebook.github.io/react/downloads.html。
使用 NPM 安装 ReactJS
使用 node -v 检查您的机器上是否已安装 node。
否则,根据您的操作系统,从其网站 (nodejs.org/en/) 安装节点包。
我们在第八章,测试 React 组件 和第九章,准备代码以进行部署 中介绍了通过 NPM 安装包。
如果您的机器上已配置 Node 和 NPM,请从任何控制台工具中在您的应用程序文件夹内执行以下命令以安装 react-tools:
npm install react-tools
安装完成后,您可以按以下方式引用 React 依赖项:
Var React = require('react');
从现在开始,您可以使用 React 变量和其方法,例如 React.createClass({…});。请记住,因为您使用 NPM 安装了它,所以在测试您的应用程序之前,您需要打包您的代码或将其转换为静态资源。在 第二章,探索 JSX 中,我们将介绍一些您可能使用的转换工具。您可以在 第八章,准备代码以进行部署 中查看更多关于部署的详细信息。
使用 Bower 安装 ReactJS
与 NPM 不同,Bower 控制浏览器就绪的包,因此也是相同的。除了使用 NPM 包外,我们还可以使用 Bower 就绪的包 (facebook.github.io/react/downloads.html)。Bower 通过安装和维护所需包的正确版本来帮助维护所有包 (bower.io/)。
首先,请确保您已安装并配置了 Bower。之后,执行以下命令:
bower install --save react
这将在您的 Bower 配置文件中将 ReactJS 作为依赖项保存。现在您只需在您的 HTML 代码中引用它即可。默认情况下,它位于 ./bower_components/react/react.js。同一文件夹中还提供了压缩版本 react.min.js。
工具
社区已经开发了许多工具来提高我们的编码体验和生产力。在本节中,我们将介绍一些文本编辑器、它们的包以及一个用于提高 ReactJS 应用程序调试的浏览器扩展。
文本编辑器
当今大多数文本编辑器都提供了 JSX 的语法高亮以及 ReactJS 的有用片段和辅助工具。以下是我建议使用的文本编辑器:
-
Emacs 编辑器—
www.gnu.org/software/emacs/ -
Sublime Text—
www.sublimetext.com/ -
Atom—
atom.io/ -
Brackets—
brackets.io/
Sublime Text 虽然在免费模式下工作,但需要付费许可证,并且会不时显示可能打扰你的弹出窗口。此外,你还需要单独安装其包管理器。你可以在 packagecontrol.io/ 上找到 Sublime Text 包以及如何安装其包管理器的更多信息。一旦安装了 Sublime 编辑器,前往安装目录,你可以在终端中运行 subl 来从当前目录打开 Sublime,这样你就可以在 Sublime 中打开当前目录的文件。
Atom 是一款新推出的免费编辑器,由 GitHub 开发。它包含内置的包管理器,无需单独安装。你只需前往设置并安装 React 包。它包含语法高亮、片段等功能。我在使用 Atom 在 MacOS X Yosemite 上时遇到的问题仅是字体质量看起来比 Sublime Text 差。如果你遇到这种情况,只需在 Atom 的设置中取消选择 使用硬件加速 选项即可。
Brackets 也是免费的,并且具有许多出色的功能,例如实时预览;例如,你可以编辑你的代码文件,并在浏览器中看到应用的变化。Brackets 内置了扩展管理器,你也可以安装 ReactJS JSX 语法高亮。然而,在撰写本书时,一些高亮功能表现不佳。
所有这些文本编辑器都相当不错,功能丰富,但本书的目的不是展示它们。如果你还没有偏好的文本编辑器,请随意选择。
Chrome 扩展
ReactJS 团队为 Google Chrome 创建了一个浏览器扩展程序。它允许你检查组件层次结构,当你需要调试应用程序时非常有帮助。你可以打开 Chrome Web Store,搜索 React Developer Tools 并安装它。你需要打开 Chrome 开发者工具(Windows 和 Linux 上按 F12,Mac 上按 ⌘-Option-I)来使用此扩展程序。我们将在后续章节中使用此扩展程序来理解 ReactJS 组件层次结构。为了使 React 扩展/附加组件在 Chrome/Firefox 中工作,我们需要在网页上全局提供 React 组件。
尝试 ReactJS
是时候编写一些代码并使用 ReactJS 创建我们的第一个应用程序了。我们将通过添加 ReactJS 脚本依赖项来在简单的网页中配置 React。然后,我们将创建一个 JavaScript 文件来保存我们的 ReactJS 组件代码,并在 HTML 元素中渲染它。
然后,我们将使用 JSX 语法重建相同的示例,并学习如何在页面中配置它。现在不用担心 JSX,因为它将在第二章 Exploring JSX and the ReactJS Anatomy 中详细介绍。
这将是一个简单的学习用途的应用程序。在接下来的章节中,我们将创建一个完整的网络应用程序,该应用程序将消费 Facebook Open Graph API,使用你的 Facebook 账户登录,渲染你的朋友列表等。所以,让我们动手吧!
在网页中配置 ReactJS
在下载 ReactJS 脚本依赖项后,我们需要创建一个包含简单元素的 HTML 文件,并将其命名为 root.html。它将负责渲染我们的 ReactJS 组件。
这就是你的 HTML 文件应该看起来像什么样子:
<!DOCTYPE html>
<html>
<head>
<script src="img/react-0.12.2.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
它引用了 Facebook CDN 脚本,但你也可以引用我们下载的本地脚本(fb-react-0.12.2.js)。
如果使用本地下载的 ReactJS 文件而不是 CDN,你的 HTML 文件将看起来如下:
<!DOCTYPE html>
<html>
<head>
<script src="img/fb-react-0.12.2.js"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
创建你的第一个 React 组件
现在创建一个名为 hello-world.js 的 JavaScript 文件,并在 HTML 文件中将此代码放置在 root div 元素之后来引用它:
<div id="root"></div>
<script src="img/hello-world.js"></script>
我们将使用 React.createElement 来创建 React 元素。React.createElement 的格式是:
ReactElement createElement(
string/ReactClass type,
[object props],
[children ...]
)
将以下代码粘贴到 hello-world.js 中:
var HelloWorld = React.createClass({
render: function () {
return React.createElement('h1', null, "Hello World from Learning ReactJS");
}
});
React.render(
React.createElement(HelloWorld, null),
document.getElementById('root')
);
In the above code
return React.createElement('h1', null, "Hello World from Learning ReactJS");
h1 → Is the type of HTML element to be created
null → means there is no object properties presentation
Third argument → the content of the h1 tag
以下章节将更详细地介绍此代码的细节。
现在,在任何浏览器中打开页面,检查它是否创建了一个 h1 HTML 元素并将文本放在其中。你应该看到如下内容:

配置 JSX
现在,我们将使用 JSX 语法创建相同的应用程序。首先,我们需要在 HTML 页面中配置它,通过在 head 元素中添加 JSX 转换器脚本文件 JSXTransformer-0.12.2.js 来在 ReactJS 脚本 react-0.12.2.js 之后:
<head>
<script src="img/react-0.12.2.js"></script>
<script src="img/JSXTransformer-0.12.2.js"></script>
</head>
你还需要在 HTML 页面中将 hello-world.js 的类型引用更改为 text/jsx。它必须是这样的:
<script type="text/jsx" src="img/hello-world.js"></script>
通过网络服务器提供文件
Google Chrome 不接受对类型为 text/jsx 的本地文件的请求,它会抛出一个跨源请求错误(通常称为 CORS 错误)。CORS 是在不同于当前域的域上共享资源。Chrome 安全默认不允许这样做;然而,你可以在 Firefox 和 Safari 上访问它。你也可以通过启动本地服务器(如 Python、Node 或任何其他你想要的网络服务器)来绕过 CORS 错误。
另一种方法是安装 node 包 httpster:
npm install -g httpster
安装完成后,从 react 应用程序目录中运行httpster命令。应用程序将在你的浏览器中加载(默认端口3333):

另一种方法是安装简单的 Python 服务器。安装它,然后在您想要提供服务的文件夹内运行其命令,然后您就可以开始了。您可以在www.python.org/上找到如何安装 Python 的说明。安装 Python 后,您可以在项目文件夹内运行以下命令:
python -m SimpleHTTPServer
它将输出一条消息,说明正在运行的服务端口,例如Serving HTTP on 0.0.0.0 port 8000。你现在可以导航到http://localhost:8000/。如果此端口已被其他应用程序使用,请考虑在相同命令中将期望的端口号作为最后一个参数传递,如下所示:
python -m SimpleHTTPServer 8080
如果你不想使用 Python,ReactJS 提供了一个包含其他语言脚本的教程页面,用于运行简单的 Web 服务器,你应该能够测试它。你可以在github.com/reactjs/react-tutorial/上查看。
使用 JSX 语法创建 React 组件
在配置好我们的 HTML 页面后,我们现在可以将hello-world.js脚本文件更改为遵循 JSX 语法。你的脚本文件应该看起来像这样:
var HelloWorld = React.createClass({
render: function () {
return <h1>Hello World from Learning ReactJS</h1>;
}
});
React.render(
<HelloWorld />,
document.getElementById('root')
);
它将生成与上一个Hello World示例相同的结果。正如你所看到的,没有必要显式调用createElement方法。
因此,JSX 将产生与 JavaScript 相同的输出,但不需要额外的花括号和分号。
在下一章中,第二章,你将学习 JSX 是如何工作的,以及为什么它被高度推荐。
摘要
在本章中,你学习了 ReactJS 是什么,下载了它,并在一个小应用程序中使用了它。我们已经创建了我们的第一个 React 组件,并回顾了此强大库的关键优势。
在下一章中,我们将深入探讨 JSX,学习如何构建一些展示此强大扩展语法的实用组件。我们还将学习一些“陷阱”和最佳实践,并了解为什么 JSX 在开发 React 组件展示时满足我们的需求。
第二章:探索 JSX 和 ReactJS 结构
在本章中,你将探索 JSX 语法,了解它是什么以及为什么它使我们更容易理解 UI 组件。你将了解 ReactJS 的结构,并编写一些常见场景的代码,以展示这种高效的语法,以便我们可以在下一章中前进并构建一个完整的应用程序。本章将带你了解以下主题:
-
JSX 是什么?
-
ReactJS 的结构
-
JSX 的陷阱
JSX 是什么?
JSX 是一种类似于 XML 的 JavaScript 语法扩展。它用于在 ReactJS 中构建 UI 组件。它与 HTML 非常相似,但有细微的差别。JSX 以一种方式扩展了 JavaScript,使得你可以用与构建 HTML 页面相同的方式轻松构建 ReactJS 组件。它通常与你的 JavaScript 代码混合使用,因为 ReactJS 以不同的方式考虑 UI。这个范式将在本章后面解释。
说你把 HTML 和 JavaScript 混在一起是错误的。如前所述,JSX 扩展了 JavaScript。实际上,你并不是在写 HTML 标签,而是在 JSX 语法中写 JavaScript 对象。当然,它必须首先转换为纯 JavaScript。
当你编写这个示例时:
var HelloWorld = React.createClass({
render: function () {
return <h1>Hello World from Learning ReactJS</h1>;
}
});
它被转换成这样:
var HelloWorld = React.createClass({
render: function () {
return React.createElement('h1', null, "Hello World from Learning ReactJS"); }
});
这个转换器脚本文件检测 JSX 语法并将其转换为纯 JavaScript 语法。这些脚本和工具绝不应该放在生产环境中,因为每次请求都要转换脚本对服务器来说都是痛苦的。对于生产环境,我们应该提供转换后的文件。我们将在本章后面介绍这个过程。
如 第一章 中所述,ReactJS 入门,请注意以下内容:
-
ReactElement是 React 的主要 API。ReactElement 有四个属性:type、props、key和ref。 -
ReactElement 没有自己的方法,原型上也没有定义任何内容。
-
可以通过调用
React.createElement方法来创建 ReactElement 对象。 -
在前面提到的突出显示的代码中,我们可以看到
React.createElement方法的第一个参数是创建一个h1元素,属性被传递为 null,而h1元素的实际内容是字符串Hello World from Learning ReactJS。 -
ReactElements 被传递到 DOM 中,以在 DOM 中创建一个新的树。
-
ReactElements 被称为虚拟 DOM,并且与 DOM 元素不同。虚拟 DOM 的细节将在后面的章节中讨论。
-
根据官方 React 文档(
facebook.github.io/react/docs/glossary.html),"ReactElement 是 DOM 元素的一个轻量级、无状态、不可变、虚拟表示"。
让我们再次检查我们之前的示例,当时我们没有使用 JSX 语法:
React.createElement('h1', null, "Hello World from Learning ReactJS");
这段代码正在创建一个h1元素。想想看,这就像通过 JavaScript 的document.createElement()函数创建一个元素,这使得代码非常易于阅读。
JSX 不是强制性的,但强烈推荐。使用 JavaScript 创建大型和复杂的组件是非常痛苦的。例如,如果我们想使用 JSX 创建嵌套元素,我们需要做以下操作:
var CommentList = React.createClass({
render: function() {
return (
<ul>
<li>ReactJS</li>
<li>JSX</li>
<li>
<input type="text" />
<button>Add</button>
</li>
</ul>
);
}
});
然而,使用纯 JavaScript ReactJS 对象,它看起来会是这样:
var CommentList = React.createClass({displayName: "CommentList",
render: function() {
return (
React.createElement("ul", null,
React.createElement("li", null, "ReactJS"),
React.createElement("li", null, "JSX"),
React.createElement("li", null,
React.createElement("input", {type: "text"}),
React.createElement("button", null, "Add")
)
)
);
}
});
我们可以看到一个可能随着更复杂逻辑而增长的大而可怕的组件。这样的复杂组件难以维护和理解。
为什么使用 JSX?
通常,在框架中通过定义 UI 或视图来表示它们可变的数据,这通常是一个模板语言和/或显示逻辑解释器。以下是一些 jQuery 代码:
<html>
<head>
<title>Just an example</title>
</head>
<body>
<div id="my-awesome-app">
<!-- Here go my rendered template -->
</div>
<script id="my-list" type="text/html">
<ul>
{{each items}}
<li>
${name}
</li>
{{/each}}
</ul>
</script>
</body>
</html>
script元素代表一个将在my-awesome-app div元素中渲染的模板组件。这里的代码是一个 JavaScript 文件,它将数据推送到模板,并要求 jQuery 完成工作并渲染 UI:
$("#my-list").tmpl(serverData).appendTo("#my-my-awesome-app");
无论何时你想在代码上添加一些显示逻辑,你都需要依赖于 JavaScript 和 HTML 文件。换句话说,一个组件是文件的混合体——通常,一个控制视图的 JavaScript 文件,一个代表视图的模板/标记文件,以及一个从服务器获取数据并发送到视图的模型/服务。在 MVC 应用程序中,M(模型)、V(视图)和 C(控制器)的逻辑通常是分离的,以便提供关注点的分离,以及代码的更好可读性和维护性。
假设我们现在必须更改这个视图,并在用户未登录时隐藏列表。考虑到模型/服务代码已经带来了这个信息,我们将不得不更改控制视图的代码和标记代码,以应用这些更改。更改越困难,应用这些更改就越痛苦。我们的代码最终变成了大型的 JavaScript 和 HTML 文件,混合了显示逻辑、模板表达式和业务代码。
尽管你是一位经验丰富的前端开发者,但应用一些关注点的分离,并将你的 UI 拆分成更小的视图,最终你会拥有数百个文件来表示单个 UI 元素:视图控制器、HTML 模板、样式表以及你的模型。这么多文件会让一个小应用看起来很复杂,你肯定会感到混乱,不知道哪个文件是特定视图或组件的一部分。
我们想在这里展示的是,我们一直在混合标记和逻辑代码,但除此之外,我们还把它们分到了其他文件中,这使得它们更难找到和修改。
ReactJS 与 JSX 将你引向另一个方向。ReactJS 官方页面中有一个非常有趣的段落,坦率地解释了这个强大的库及其范式:
"我们坚信,组件是分离关注点的正确方式,而不是“模板”和“显示逻辑”。我们认为标记和生成它的代码紧密相连。此外,显示逻辑通常非常复杂,使用模板语言来表示它变得繁琐。(
facebook.github.io/react/docs/displaying-data.html#jsx-syntax)"
我们喜欢将 ReactJS 组件视为单一的真实来源。所有其他使用您组件的位置都只是引用。您对原始组件所做的任何更改都将传播到所有引用它的其他地方。可以通过属性和子组件化轻松进行定制。JSX 就像是一个中间件,将您的标记代码转换为 ReactJS 可以处理的对象。
JSX 加速了 ReactJS 的前端开发。您不是创建代表您 UI 的字面量对象,而是创建类似于 XML 的元素,非常类似于 HTML,您甚至可以引用您创建的其他组件。此外,重用第三方组件或甚至发布您自己的组件非常简单。在企业环境中,您可能有一个常用的组件仓库,其他项目可以从该仓库导入。
转换 JSX 的工具
如前所述,JSX 转换器文件和其他工具负责将您的 JSX 语法转换为纯 JavaScript。ReactJS 团队和社区为此提供了一些工具。这些工具可以处理任何类型的文件,因为它们包含 JavaScript 代码和 JSX 语法。在 React 的旧版本中,需要在 .js 文件的第一行添加注释,例如 /** @jsx React.DOM */。幸运的是,在版本 0.12 之后,这一要求被移除了。
JSX 转换器现在已被弃用。相反,我们可以使用 babeljs.io/repl/ 将 JSX 语法编译成 JavaScript。要在脚本标签中包含 JSX,可以使用 <script type="text/jsx"> 或在转换时使用 babel
<script type="text/babel">.
以前有一个在线工具 facebook.github.io/react/jsx-compiler.html。然而,React 开发团队已经停止了它,JSX 转换器也已弃用。
由于这种 JSX 转换将在客户端进行大量的计算,因此我们不应该在生产环境中进行这些转换。相反,我们应该使用:
npm install -g bab
el-cli

我们还可以使用 ReactJS 团队构建的 node npm 包来转换您的 JSX 文件。首先,您需要使用以下命令安装 react-tools NPM 包:
npm install react-tools –g
这将全局安装 react-tools。现在您只需要从您的项目文件夹中运行以下命令:
jsx --watch src/ build/
此命令将src文件夹中的每个脚本转换,并将其放入build文件夹。watch参数使得每次src文件夹中的文件发生变化时,此工具都会运行相同的命令。这是一个非常有用的工具,因为您正在使用 node 来打包您的前端代码。
如果您熟悉 Grunt 或 Gulp 等任务运行器工具,它们也有 JSX 转换器包,可以使用npm安装。在这种情况下,它们提供了更多选项,可以更好地适应我们的部署/构建过程,尤其是如果您已经使用其中之一。本书的目的不是深入探讨 Grunt 或 Gulp。为了配置和安装它们,您可以遵循以下链接中的指南:这些细节将在第九章,准备代码以部署中介绍。
-
Grunt –
www.gruntjs.com -
Gulp –
www.gulpjs.com
这两个网站都有一个/plugins页面,您可以在其中搜索可用的插件。以下是一些下载工具的链接:
-
Grunt React 任务—
www.npmjs.com/package/grunt-react -
Gulp React 任务—
www.npmjs.com/package/gulp-react/
它们的工作方式与 React 工具非常相似。在接下来的示例中,我们将使用放置在我们 HTML 页面head元素中的transformer脚本文件,因为这更容易操作。在第九章,准备代码以部署中,我们将使用webpack和gulp作为npm包来转换我们的 JSX 代码,并为其部署做准备。
ReactJS 的解剖结构
在进一步探讨 JSX 之前,我们需要了解一些基本规则来构建 ReactJS 组件。首先,我们将详细介绍您已经用来创建和渲染组件的基本方法。然后,我们将转向创建它们的规则,最后,我们将讨论子组件。
创建一个组件
为了创建一个组件,我们需要使用React.createClass函数。ReactJS 组件基本上是类。此方法返回一个具有名为render的方法的 ReactJS 组件定义,这是必须实现的。本书将介绍许多其他方法来配置您的组件并改变其行为。
这是一个如何使用createClass和render方法的示例:
var HelloMessage = React.createClass({
render: function() {
return (
<h1>Have a good day!</h1>
);
}
});
注意
将所有类和组件命名为 PascalCase 是一个好的做法。除了在 JavaScript 中是一个常见的模式外,它还有助于将它们与其他变量区分开来。
渲染一个组件
一旦你有了组件定义,就像我们在最后一个例子中看到的HelloMessage组件,我们就可以使用ReactJS的render方法来渲染它。它需要组件定义和目标位置,即组件将被渲染的位置。让我们用以下示例来演示这一点:
React.render(<HelloMessage />, document.body);
在前面提到的代码中,你可以用你页面上的任何其他元素替换document.body。例如,你可以使用document对象的方法document.getElementById('id')通过 ID 找到元素,或者使用任何其他返回 DOM 元素的辅助方法。在特定的 DOM ID(在这种情况下是id),React 组件将被渲染。
根的最大数量
在render方法中返回超过一个元素是不可能的。至少现在还不行,正如官方 ReactJS 文档facebook.github.io/react/tips/maximum-number-of-jsx-root-nodes.html中所说:
var HelloMessage = React.createClass({
render: function() {
return (
<h1>Have a good day!</h1>
<h2>This is going to BREAK!</h2>
);
}
});
ReactJS 库将抛出一个奇怪的错误,这个错误并没有清楚地指出你正在渲染多个元素。因此,请注意不要这样做;否则,你可能会陷入寻找问题的困境。
当一个 ReactJS 组件代表多个元素时,你必须将它们包裹在一个单一父元素中。下一个示例将演示这一点:
var HelloMessage = React.createClass({
render: function() {
return (
<div>
<h1>Have a good day!</h1>
<h2>This is NOT going to BREAK!</h2>
</div>
);
}
});
你可以使用任何支持子元素的有效的 HTML 元素。也可以渲染一个具有子支持的自定义 ReactJS 组件(更多内容将在下一节中介绍)。
这就是选择一个好的文本编辑器和好的 lint/lint 包的原因之一,它可以监视你的代码,并在你犯错时提醒你。
子组件
当你谈论创建可重用用户界面时,如果真的有必要的因素,那一定与组件嵌套有关。这样你可以更好地组织并分离你应用程序的各个部分。在 Web 世界中,这也是一个相当常见的事情,因为 HTML 内置了这一功能。正如你在本书的上一节和前面的示例中看到的那样,ReactJS 也支持这一功能,JSX 语法使得它非常直接。
假设你有一个Header组件,并且你想要在其中放置其他组件。ReactJS 允许这样做,并且包括对放置其他 ReactJS 组件的支持:
var Header = React.createClass({
render: function () {
return (
<nav>
<h1>This is my awesome app</h1>
{this.props.children}
</nav>
);
}
});
var Clock = React.createClass({
render: function () {
return <span>{new Date().toLocaleTimeString()}</span>;
}
});
var ComponentThatHasHeader = React.createClass({
render: function () {
return(
<Header>
<h2>This is my another component</h2>
<Clock />
</Header>
);
}
});
React.render(<ComponentThatHasHeader />, document.body);
你可以使用内置组件,如h2,或者自定义组件,如这里描述的Clock组件。在这个例子中,表达式{this.props.children}将被视为一个 JavaScript 数组。如果有单个组件,就像后面提到的那个,它将被视为一个 JavaScript 对象而不是数组。这节省了数组分配,但我们应该小心,不要尝试迭代它或检查它的长度:
<Header>
<Clock />
</Header>
支持的属性
一些 HTML 属性与 JavaScript 保留字冲突,由于 ReactJS 元素基本上是 JavaScript 对象,因此这些属性在 ReactJS 中具有不同的名称,以匹配 DOM API 规范:
-
类是
className -
for是htmlFor -
支持自定义属性,如
data-*和aria-*,ReactJS。有一个官方支持的 HTML 属性列表,如下所示:acceptacceptCharsetaccessKeyactionallowFullScreenallowTransparencyaltasyncautoCompleteautoPlaycellPaddingcellSpacingcharSetcheckedclassIDclassNamecolscolSpancontentcontentEditablecontextMenucontrolscoordscrossOrigindatadateTimedeferdirdisableddownloaddraggableencTypeformformActionformEncTypeformMethodformNoValidateformTargetframeBorderheighthiddenhrefhrefLanghtmlForhttpEquiviconidlabellanglistloopmanifestmarginHeightmarginWidthmaxmaxLengthmediamediaGroupmethodminmultiplemutednamenoValidateopenpatternplaceholderposterpreloadradioGroupreadOnlyrelrequiredrolerowsrowSpansandboxscopescrollingseamlessselectedshapesizesizesspanspellChecksrcsrcDocsrcSetstartstepstyletabIndextargettitletypeuseMapvaluewidthwmode
在撰写本书时,此信息可在facebook.github.io/react/docs/tags-and-attributes.html找到。
支持的元素
官方的 ReactJS 网站还提供了一个支持的元素列表。ReactJS 支持大多数 HTML 元素。所有支持的元素的综合列表可以在其网站上找到,facebook.github.io/react/docs/tags-and-attributes.html。
HTML 元素
以下是一些支持的元素。还有更多元素在列表中:
a |
abbr |
address |
area |
|---|---|---|---|
article |
aside |
audio |
b |
base |
bdi |
bdo |
big |
blockquote |
body |
br |
button |
canvas |
caption |
cite |
code |
col |
colgroup |
data |
datalist |
dd |
del |
details |
dfn |
dialog |
div |
dl |
dt |
em |
embed |
footer |
fieldset |
figcaption |
figure |
form |
h4 |
h1 |
h2 |
h3 |
h5 |
h6 |
head |
header |
hr |
html |
i |
iframe |
img |
input |
ins |
kbd |
keygen |
label |
legend |
li |
link |
main |
map |
mark |
menu |
menuitem |
meta |
meter |
nav |
noscript |
object |
ol |
optgroup |
option |
output |
p |
param |
picture |
pre |
progress |
q |
rp |
rt |
ruby |
s |
samp |
script |
section |
select |
small |
source |
span |
strong |
style |
sub |
summary |
sup |
table |
tbody |
td |
textarea |
tfoot |
thead |
time |
tr |
track |
u |
ul |
video |
wbr |
SVG 元素
以下是一些支持的 SVG 元素:
circle defs ellipse g line linearGradient mask path pattern polygon polyline radialGradient rect stop svg text tspan |
|---|
学习 JSX 和注意事项
现在是时候掌握 JSX 并学习一些注意事项了。你将学习一些基本概念,使用 JSX 构建 ReactJS UI 组件。这包括编写表达式、条件和创建组件列表的实践。它还将向您介绍 JSX 在某些方面如何与 HTML(因为它不是 HTML)不同。
表达式
考虑以下代码:
var Clock = React.createClass({
render: function () {
var today = new Date();
return <h1>The time is { today.toLocaleTimeString() }</h1>;
}
});
React.render(<Clock />, document.body);
JSX 在你想在演示代码中嵌入 JavaScript 代码时理解花括号{}。
在下一个示例中,让我们通过支持根据时间不同的问候语来改进我们的Clock组件。
在后面提到的突出显示的代码中,如果当前小时数小于4,则应返回day,如果小时数大于4但小于18,则应返回night:
var GreetingsClock = React.createClass({
render: function () {
var today = new Date();
return <h1>Hey! Have a good { today.getHours() > 4 && today.getHours() < 18 ? 'day' : 'night' }!</h1>;
}
});
React.render(<GreetingsClock />, document.body);
如您所见,在花括号内可以创建三元表达式。您可以在其中放置任何有效的 JavaScript 代码。在渲染组件之前创建一个变量并将其分配给此表达式的结果更为常见。这使得您的代码更干净、更易于阅读。
在下一个示例中,我们将演示如何根据条件渲染组件。有两个组件,一个用于登录,另一个用于用户详情。这取决于用户是否已登录;如果用户已登录,则显示用户详情组件;否则,将渲染登录组件。检测用户是否登录的代码将被跳过,因为这只是为了演示如何使用 JSX 语法在 ReactJS 组件中放置渲染逻辑:
var loginPane;
if (IsUserLoggedIn) {
loginPane = <UserDetails />
} else {
loginPane = <LoginButton />
}
React.render(loginPane, document.getElementById('login-div'));
您可以将此代码放入一个包含所有其他组件作为子组件的组件中,如下一个示例所示:
var App = React.createClass({
render: function () {
var loginPane;
if (isUserLoggedIn) {
loginPane = <UserDetails />
} else {
loginPane = <LoginButton />
}
return (
<nav>
<Home />
{loginPane}
</nav>
)
}
});
React.render(<App />, document.body);
属性/属性
属性允许您自定义组件,JSX 以与 HTML 元素非常相似的方式支持它们。您可以将属性传递给 ReactJS 元素,并在渲染组件之前获取它们。这是 ReactJS 的一个非常关键的基本概念,你将学习如何使用 JSX 与它们一起工作。在下一章中,我们将深入了解属性的工作原理,并讨论如何使用它们的良好实践。
考虑以下示例:
var HelloMessage = React.createClass({
render: function() {
return (
<h1>Have a good day {this.props.name}</h1>
);
}
});
为了渲染此组件,您必须向其传递属性,就像我们在 HTML 元素中所做的那样:
React.render(<HelloMessage name="reader" />, document.body);
您也可以在属性中使用表达式:
React.render(<HelloMessage name={1 + 1} />, document.body);
如果我们没有设置组件所需的属性(名称),在我们的上一个示例中,它将被渲染为空字符串。因此,如果有表达式试图访问该属性,那么它将抛出错误。
转移属性
在 ReactJS 中,在整个组件层次结构中传递属性是一个非常常见的做法。你可以把属性看作是一种使你的组件动态化的方式,因为你将组件拆分成更小的部分,你需要一种高效的方法来传递传入的配置和数据。
考虑以下嵌套组件:
var UserInfo = React.createClass({
render: function () {
return (
<section id="user-section">
<h2>{this.props.firstName} {this.props.lastName}</h2>
<h3>{this.props.cityName} / {this.props.stateName}</h3>
</section>
);
}
});
var App = React.createClass({
render: function () {
return (
<div>
<h1>My Awesome app!</h1>
<UserInfo firstName={this.props.firstName}
lastName={this.props.lastName}
cityName={this.props.cityName}
stateName={this.props.stateName} />
</div>
);
}
});
React.render(<App firstName="Learning"
lastName="ReactJS"
cityName="Florianopolis"
stateName="Santa Catarina" />, document.body);
如我们所见,向子组件传递属性是可能的。如果你有很多属性需要传递,那么这变成了一项繁琐的任务,你的代码也会变得非常混乱。
幸运的是,你可以使用 JSX 提供的优雅方式来传递它们。你所要做的就是更改你的 App 组件,使其能够传递通过 React.render 函数接收到的所有属性。这是通过 JSX 理解的展开运算符 {...this.props} 表达式来实现的。以下是一个解释这个的例子:
var App = React.createClass({
render: function () {
return (
<div>
<h1>My Awesome app!</h1>
<UserInfo {...this.props} />
</div>
);
}
});
清晰多了!然而,这个解决方案仍然存在一个问题。它可以覆盖子组件上的属性。让我们再举一个例子,假设你有一个名为 name 的属性,并且你想传递它。一些元素,基本上是 HTML 输入元素,有这个属性来定义它们在表单中的名称。如果你改变它,可能会导致意外的后果。另一个例子是输入复选框或单选按钮。两者都有一个名为 checked 的属性,它定义了控件是否会被视觉上选中。如果你传递一个名为 checked 的属性,它肯定会引起不良行为。为了避免这种情况,你可以使用相同的表示法跳过一些属性。你只需要指定它们为:
var App = React.createClass({
render: function () {
var {name} = this.props;
return (
<div>
<h1>My Awesome app! {name}</h1>
</div>
);
}
});
在三个点 ... 之前的所有内容都将被视为独立的变量,而在三个点之后的内容,...,将被分配到一个包含所有剩余属性的数组中。这是一个实验性的 ES6(ECMA Script)语法,有一些方法可以将这段代码转换为普通的 JavaScript。ES6 的详细信息将在第五章 组件生命周期和 React 中的新 ECMAScript 中介绍。
通过查看 www.npmjs.com/package/react-tools,你可以找到你可以通过 JSX 转换器传递的不同选项的详细信息。
--harmony:开启 JS 转换,如 ES6 类等。
因此,ES6 语法将被转换为 ES5 兼容的语法。
第一种方法是在你的 HTML script 元素上添加一个名为 harmony 的额外参数,这样转换器就会知道它需要理解 ECMAScript 的新版本才能进行转换。这是你的脚本标签应该看起来像的样子:
<script type="text/jsx;harmony=true" src="img/properties.js"></script>
注意
ES6(也称为 Harmony)是 ECMAScript 的一个版本(实际上是 ES5),它是一种标准化的脚本语言。这个标准的最知名实现是 JavaScript 语言,但还有许多其他语言。
您还可以使用react-tools节点包。它暴露了一个名为jsx的命令,可以离线转换您的文件。为了使用它,您需要从应用程序文件夹中的任何控制台工具运行此命令:
jsx -x jsx --harmony . .
-x选项允许您指定要搜索的语法。在我们的示例中,我们正在创建.jsx文件,但您也可以使用包含 JSX 代码的.js文件来完成此操作。--harmony选项与上一个示例相同。它告诉转换器理解 JavaScript 语言的 ES6/ES7 特性。
您可以在本章前面讨论的转换 JSX 的工具部分中找到如何安装react-tools的说明。
使用.jsx文件的原因是它使文本编辑器能够匹配已安装的语法高亮,而无需进行配置。
修改属性
一旦您的 React 组件被渲染,不建议修改其属性。这被认为是一种反模式。属性是不可变的,它们大致代表了渲染后的表示。考虑以下代码:
var HelloMessage = React.createClass({
render: function () {
return <h1>Hello {this.props.name}</h1>;
}
});
var component = <HelloMessage />;
component.props.name = 'Testing';
React.render(component, document.body);
虽然这项工作有很大可能产生意外结果。在 ReactJS 组件中,组件的状态是唯一修改数据的方式。接下来的两章将涵盖 props 和状态的详细信息以及它们何时何地应该被使用。状态和属性都是 ReactJS 核心工作方式的关键。
注释
JSX 允许您在代码中放置注释;它们的语法取决于您是将它们放置在嵌套组件内部还是外部。请参考以下示例:
var Header = React.createClass({
render: function () {
return (
//this is the nav
<nav>
{/* this is the nav */}
<h1>This is my awesome app</h1>
{this.props.children}
</nav>
);
}
});
在嵌套组件内部时,您只需将注释用大括号(如表达式)括起来。
组件样式
您可以使用className或style属性来设置组件样式。className属性与 HTML 类的作用方式相同,而style属性则将内联样式应用于组件,这与 HTML 类似。您可以选择您更喜欢的其中一个;在 ReactJS 中,它们都有简单的方式来处理,尤其是在您需要它们动态时。
样式
每当您想为组件应用样式时,您可以使用一个 JavaScript 对象来实现。此对象的属性必须与 DOM 样式规范匹配,例如height、width等。请参见以下示例:
var StyledComponent = React.createClass({
render: function () {
return (
<div style={{height: 50, width: 50, backgroundColor: 'red'}}>
I have style!
</div>
);
}
});
React.render(<StyledComponent />, document.body);
这将渲染一个带有文本的红色小正方形div。您可以将此样式对象移动到变量中,并根据组件属性或状态动态设置它。属性和状态将在第三章、将 UI 拆分为组件和第四章、有状态组件和事件中详细讨论。为了演示目的,这是将此样式对象从组件标记中移出的方法:
render: function () {
var style = { height: 50, width: 50, backgroundColor: 'red' };
return (
<div style={style}>
I have style!
</div>
);
}
当你的样式名称由破折号 - 分隔时,你需要将它们写成驼峰式,就像你可以在我们之前示例中的 backgroundColor 样式属性中看到的那样。除了 ms 之外的其他供应商前缀应该以大写字母开头;例如,WebkitTransition 将被转换为 webkit-transition,而 msTransition 将被转换为 ms-transition。所有其他供应商名称都必须以大写字母开头。
CSS 类
为了向你的组件添加 CSS 类,你需要为它们指定 className 属性:<component className="class1 class2" />。不幸的是,className 不支持像 style 那样的对象字面量。如果我们想动态地改变它们,我们需要连接字符串或使用来自 github.com/JedWatson/classnames 的类名。考虑以下示例:
var ClassedComponent = React.createClass({
render: function () {
var className = 'initial-class';
if (this.props.isUrgent) {
className += ' urgent';
}
return (
<div className={className}>
I have class!
</div>
);
}
});
React.render(<ClassedComponent isUrgent={true} />, document.body);
在这个例子中,我们正在连接字符串,但这是一项非常繁琐的任务,可能会导致错误和错误。ReactJS 插件提供了一个类操作实用工具。如果我们使用 ReactJS 库脚本文件,我们应该获取带有插件内嵌的那个,如第一章中提到的下载 ReactJS 部分ReactJS 入门:
<script src="img/react-with-addons-0.12.2.js"></script>
如果你使用 node 或其他 CommonJS/AMD 包来要求 ReactJS 依赖项,你可以通过 require('react/addons') 来引用插件,而不是仅仅要求 React。
现在,让我们检查我们的代码使用这个实用工具代码是如何工作的:
var Button = React.createClass({
// ...
render () {
var btnClass = 'btn';
if (this.state.isPressed) btnClass += ' btn-pressed';
else if (this.state.isHovered) btnClass += ' btn-over';
return <button className={btnClass}>{this.props.label}</button>;
}
});
尝试更改 isUrgent 属性,并查看当你重新加载页面时类属性是如何改变的。
摘要
在本章中,你学习了 JSX 是什么,它的语法,以及为什么它是必要的。我们查看了一些示例,并了解了如何使用 JSX 构建。我们涵盖了 ReactJS 的非常基本的原则,以及 JSX 如何帮助你更快、更容易阅读和合理地构建组件。
在下一章中,我们将深入探讨 ReactJS 属性以及如何将 UI 分解成更小的组件。你将通过创建一个小型应用程序来学习,该应用程序将消耗 Facebook Open Graph API 并列出你喜欢的页面。
第三章. 使用属性
在本章中,我们将探讨如何使用 ReactJS 属性。我们还将学习如何将 ReactJS 与外部 API(Facebook Open-Graph API)集成,并在一组组件中渲染传入的数据。本章将涵盖以下内容:
-
组件属性
-
组件的数据流
-
配置和消费 Facebook Open-Graph API
-
创建 ReactJS 组件并从 API 列出数据
组件属性
在第二章中,探索 JSX 和 ReactJS 结构,我们讨论了很多关于 ReactJS 属性的内容,并在我们的示例中使用了它们,但到目前为止,我们只是像使用 HTML 属性一样使用它们。它们的作用远不止于此。通常,我们会使用它们在组件树中传递数据,以定义视图;传递来自父组件的配置属性;传递用户输入的回调,以及需要在外部触发的 UI/自定义事件等。
ReactJS 组件的属性一旦在 DOM 中渲染,就不能更改。
属性定义了组件的声明式接口。例如,在一个渲染名称属性的 h1 元素中,一旦渲染,你无法更改此名称,除非你创建另一个组件实例并在 DOM 的同一位置重新渲染,以替换旧的渲染组件。
var GreetingsComponent = React.createClass({
render: function() {
return (
<h1>Hello {this.props.name}!</h1>
);
}
});
React.render(<GreetingsComponent name="Readers" />, document.body);
// instead of rendering on the body rendering in a specific id ('app)
React.render(<GreetingsComponent name="Folks" />, document.getElemenById('app'));
这是因为 ReactJS 代表了组件在任何时间点的状态,而不仅仅是初始化时。考虑以下示例:
var CustomInput = React.createClass({
render: function() {
return (
<input type="text" value={this.props.text} />
);
}
});
React.render(<CustomInput text="Learning ReactJS" />, document.body);
正在渲染的输入文本被视为受控组件,因为它即使在尝试键入时也不会更改值。如果我们没有指定 value 属性,则被视为非受控组件。受控组件通过 ReactJS 数据流和组件周期来更新其数据。然而,如果你没有指定值属性,值属性将不由 ReactJS 控制,并且存在于 ReactJS 数据流之外。从表单输入或其他组件更改值的正确方法是设置它们的 state,这将在下一章中更详细地描述。
属性的数据流
ReactJS 的一个基本原理和最佳实践是通过属性将数据传递给嵌套组件。这样,子组件只需负责渲染它们必须渲染的内容,并将任务传递给更进一步的组件,从而确保关注点的分离。这也用于配置嵌套组件,以便层次结构顶部的组件可以通过传递属性来指定子组件应具有的特定方面。这还常见于在父节点上定义函数并将它们作为回调传递给子节点,以便在子组件需要时触发,从而提高组件的可重用性和可测试性。
让我们通过一个小示例来模拟一个静态待办事项列表。列表被拆分成小的组件,这些组件只渲染必要的部分并将属性传递给子组件,从而定义整个视图的功能。这就是最终将要渲染的内容:

我们将把我们的视图拆分成更小的组件,并从最内层的组件开始。在我们开始之前,让我们更详细地讨论每个组件及其在视图中的作用:
-
TaskList– 这个组件代表一个列表(ul元素),它接受一个要渲染的任务数组。它遍历任务数组,创建一个TaskItem组件。除了通过属性传递任务详情以在TaskItem组件中渲染外,它还传递了一些函数回调,目前这些回调将只是模拟一些操作以简化演示。 -
TaskItem– 这个组件代表一个单独的任务(li元素),它渲染任务名称、一个表示任务是否完成的输入复选框以及一个删除任务的按钮。同样,输入和按钮将只记录一些文本以简化演示。TaskItem组件应该看起来像这样:var TaskItem = React.createClass({ render: function() { var task = this.props.task; return ( <li> <span>{task.name}</span> <div> <input type="checkbox" if (task.completed) {checked = "checked";} else { checked = "";} onChange={this.props.markTaskComplete <button onClick={this.props.removeTask}>Remove</button> </div> </li> ); } });TaskList组件应该如下所示:var TaskList = React.createClass({ markTaskCompleted: function (task) { console.log('task ' + task.name + ' has been completed!'); }, removeTask: function (task) { console.log('task ' + task.name + ' has been removed!'); }, render: function() { /*The map() method creates a new array with the results of calling a provided function on every element in this array. Here this.props.tasks will create a new array, with the callback as task. (source HYPERLINK "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map HYPERLINK ""). */ var taskItems = this.props.tasks.map(function (task) { return <TaskItem task={task} markTaskCompleted={this.markTaskCompleted} removeTask={this.removeTask} />; }.bind(this)); /*The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments (if any) this (source HYPERLINK "https://developer.mozilla.org/en- US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind"https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind). .bind is used in this case to simply more arguments and esnsure a parent chind relationship, which is not new to ReactJS but a concenpt of core Javascript. */ return ( <ul> {taskItems} </ul> ); } });
任务列表有更多的功能和代码。它负责渲染任务列表并处理其上的操作。这是 ReactJS 中一个非常常见的模式,即在单一位置维护你的模型对象的控制权,当然,如果它有责任这样做的话。在更复杂的场景中,你会有一些“控制器”或“容器”(ReactJS 社区喜欢这样称呼它们),每个容器都有自己的任务并封装它们负责的内容。因此,所有的容器都有自己的责任,不会相互干扰。
在我们的静态任务列表示例中,只有一个缺失的部分,那就是在 HTML 元素中渲染组件:
var tasks = [
{ name: 'Write this book with love <3', completed: false },
{ name: 'Learn isomorphic web apps', completed: false },
{ name: 'Study FLUX architecture', completed: true },
];
React.render(<TaskList tasks={tasks} />, document.body);
我们向列表传递了一些静态任务来解释属性的工作原理以及一些使用它们的实践。我们认为像待办事项、任务或 hello-world 这样的简单示例并不能代表我们每天面对的真实世界问题;尽管它们对于开始学习工具功能来说很棒,但当尝试制作更合理的示例时,例如具有异步操作、处理外部 API、身份验证等,它们就失败了。因此,以下主题将涵盖如何使用 Facebook API(也称为 Open-Graph API)设置我们的真实场景应用程序,包括 Facebook 登录以及登录用户喜欢的列表。
配置 Facebook Open-Graph API
在接下来的章节中,我们将学习更多关于 Facebook Open-Graph API 的知识,并对其进行配置,以便我们可以开始编写代码来构建我们出色的应用程序。
它是什么以及如何配置
Facebook Open-Graph API 是一个用于获取、编辑和添加常见 Facebook 资源的服务。您可以在自己的应用程序中使用的一些功能包括:登录;请求特定于用户的资源权限,如管理事件、在朋友墙上发布,等等。它有一系列您可以使用并集成到应用程序中的功能。第三方应用程序使用的主要功能之一当然是登录集成。您可以用它作为一个登录平台,例如,如果您不想或没有时间构建一个。
小贴士
API 文档可在 developers.facebook.com/ 查找,建议您查看。
如果您想测试一些对他们的 API 的请求,而不必从头开始开发应用程序,您可以使用一个非常有用的工具,称为 Graph API Explorer。它通常用于在开发之前测试端点或只是检查返回的 JSON 结果。Graph API Explorer 可在顶部导航栏的 工具 & 支持 菜单项中找到。这个工具看起来是这样的:

Facebook Open-Graph API 上大多数可用的资源都需要授权。只有少数资源可以在不提供 访问令牌 的情况下使用。访问令牌是一种票据,您的应用程序使用它代表用户行事,因此您可以获取或提交数据。基本上,您向 Facebook 请求一些用户的权限;Facebook 打开一个弹出窗口,要求用户登录并显示您的应用程序请求的权限;一旦用户允许,Facebook 将通过生成的访问令牌将用户信息/对象发送回您的页面。从那时起,您就可以访问应用程序需要的其他 Open-Graph API 资源。您只需在您发出的每个后续请求中提供此令牌即可。这是 OAuth 授权标准的工作方式,但本书不会详细介绍 OAuth,因为它不是本书的目的。
注意
您可以在 oauth.net/2/ 找到有关 OAuth 的更多详细信息。
在 Graph API Explorer 中,您可以通过点击 获取访问令牌 按钮来获取访问令牌。这将打开一个弹出窗口,其中包含您的操作可以使用的一组权限。一旦您选择了所需的权限,Graph API Explorer 将显示一个弹出窗口,要求您确认请求的权限。这个过程生成一个新的访问令牌,允许您对这些受限制的资源进行请求操作。尝试检查 user_likes 权限,并通过 /me/likes 端点请求您的用户喜欢的列表:

您可能会在 提交 按钮下方的框中看到 JSON 结果。
这在 Graph API Explorer 中工作得非常好,但这只是为了测试目的。为了使其工作,我们必须在 Facebook 开发者网站上创建一个应用程序(APP-ID)。这遵循 OAuth 标准规范,并允许登录到你的应用程序的用户在授予访问权限之前了解更多关于你的应用程序的信息。为了在 Facebook 上创建一个应用程序并获得这个 APP-ID,请转到页面顶部的“我的应用”菜单;那里将有一个创建新应用的选项。

在撰写这本书的时候,Facebook Open-Graph API 推荐使用的版本是 v.2.2。
在 Facebook 开发者网站上创建应用 ID
当你点击前面图片中显示的“添加新应用”按钮时,Facebook 会询问你需要哪种类型的应用。

选择“网站”选项,这表示我们的应用将在一个独立的网页上运行,周围没有 Facebook 内容。之后,给它起一个花哨的、原创的、不可预测的名字,就像我这样做:learning-reactjs。完成之后,你将被重定向到应用详情和配置页面,看起来可能像这样:

哈哈!我们的应用 ID 已经准备好了;现在我们可以用它来向 Facebook Open-Graph API 发起请求调用。
注意
有一个重要的事情需要注意,即某些权限在生产前需要 Facebook 进行进一步分析。由于我们只是用它来获取我们自己的用户数据,所以没问题。如果你尝试用不同的用户登录,将不可能成功,因为user_likes权限要求你的应用程序提交给分析,而这需要花费很多时间,并且只有在你想将应用程序投入生产时才应该这样做。
Open-Graph JavaScript SDK
Facebook Open-Graph API 为常见的编程语言提供了 SDK,以简化使用其资源的过程。JavaScript SDK 可以在developers.facebook.com/docs/javascript找到。他们的文档(developers.facebook.com/docs/javascript/quickstart/v2.5)非常全面,他们还提供了登录、处理多个请求等教程。如果你考虑学习使用 JavaScript SDK 的高级课程来学习 Open-Graph API,那么在之后查看它是有价值的。
只为了回顾一下我们即将要做的事情:我们将创建一个简单的 HTML 页面,该页面将加载 Facebook JavaScript SDK。之后,我们将登录到 Facebook,请求我们的登录用户喜欢的列表,并通过属性将其传递给我们将要创建的 ReactJS 组件。
Facebook JavaScript SDK 需要在后台加载,页面加载完成后不需要等待时间(异步)。为了做到这一点,我们需要创建一个script HTML 元素,将其src(源)属性指向 SDK 脚本,并将其最终插入 DOM 中。幸运的是,SDK 页面有一个现成的示例可以使用,你只需要替换其中的APP-ID属性。我们将使用他们的示例。脚本看起来是这样的:
<script>
window.fbAsyncInit = function() {
FB.init({
appId : '784445024959168',
xfbml : true,
version : 'v2.2'
});
};
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk/debug.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
注意
window.fbAsyncInit是一个初始化函数,包含你的 Facebook appId和其他详细信息。
一旦异步初始化完成,如果找到相关元素,JavaScript(js)就会连接到//connect.facebook.net/en_US/sdk/debug.js。
我们需要将appId参数更改为t Facebook-app-id。
因此,让我们开始创建一个位于单独文件夹中的index.html文件来组织这些内容。页面将如下所示:
<html>
<head>
<title>Rahh</title>
<script src="img/react-0.12.2.js"></script>
<script src="img/JSXTransformer-0.12.2.js"></script>
</head>
<body>
<h1>Facebook User's list of likes</h1>
<a onClick='logout()' href='#'>Logout</a>
<div id="main"></div>
<script>
window.fbAsyncInit = function() {
FB.init({
appId : '784445024959168',
xfbml : true,
version : 'v2.2'
});
checkLoginStatusAndLoadUserLikes();
};
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
//js.src = "//connect.facebook.net/en_US/sdk.js";
js.src = "//connect.facebook.net/en_US/sdk/debug.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
<script type="text/jsx" src="img/index.jsx"></script>
</body>
</html>
我们正在注释js.src行,并使用一个调试 JavaScript 文件进行复制。这有助于查找错误和调试你的脚本。
注意
内容分发网络或内容分发网络(CDN)是一个全球分布式的代理网络。来源:https://en.wikipedia.org/wiki/Server_(computing)
服务器部署在多个数据中心。来源:en.wikipedia.org/wiki/Data_center
CDN 的目标是以高可用性和高性能向最终用户提供服务。来源 en.wikipedia.org/wiki/Content_delivery_network
首先,我们需要引用 ReactJS 依赖项,我们将引用 CDN 版本以使其更容易演示和学习目的。在引用依赖项之后,我们创建一些 HTML 元素,一个标题,一个用于从 Facebook 注销的锚点,以及一个div,它将成为要渲染的 ReactJS 组件的宿主。稍后,我们将按照之前解释的方式配置 Facebook Open-Graph JavaScript SDK,但增加一个额外的命令checkLoginStatusAndLoadUserLikes();,这个命令将在稍后解释。最后,我们将引用包含使一切发生的魔法的index.jsx文件。
我们将checkLoginStatusAndLoadUserLikes调用放在了fbAsyncInit函数中,因为 Open-Graph SDK JavaScript 会在加载后触发该函数,所以这里是调用 Open-Graph API 调用的正确位置。继续我们的开发,在index.jsx文件内部,让我们实现logout和checkLoginStatusAndLoadUserLikes函数,并测试一下以查看 Facebook 集成是否工作。
logout函数应该像这样简单:
function logout() {
FB.logout();
}
这将仅将用户从 Facebook 上注销,需要他再次登录。由于我们将保持简单,为了学习目的,我们不会处理用户在发生这些事件时注销和登录来管理点赞列表的情况。因此,让我们实现我们的 checkLoginStatusAndLoadUserLikes 函数:
function checkLoginStatusAndLoadUserLikes() {
FB.login(function(response) {
console.log('We are live!!');
});
}
这段代码也非常简单明了。Open-Graph JavaScript SDK 所做的每个调用都将异步执行,因此我们必须提供一个函数回调,该回调将在向 Facebook API 发出请求并返回响应时触发。在这段代码中,我们将在登录到应用后登录到控制台。这个过程将调用 Facebook 登录弹出窗口:

尝试一下!加载屏幕,一旦登录,点击注销锚点并重新加载页面,从您的浏览器中查看控制台,查看我们打印的日志。以下图像显示了登录弹出窗口。
注意
为了使其在 localhost:3000 上工作,您需要在代码文件夹内运行 Python 的 SimpleHTTPServer 命令,即 python -m SimpleHTTPServer。有关此命令和其他运行您应用的方法的更多详细信息,请参阅第一章, 使用 ReactJS 入门。
您也可以使用以下命令实现:
npm -g install httpster
httpster:这是一个简单的 http 服务器,用于运行静态内容。在 Chrome 浏览器中,index.html 文件有时会因为 X-origin 错误而无法渲染。因此,从您的应用程序目录运行此 web 服务器将更容易测试您的应用程序。只需从您的应用程序的 root 目录运行 httpster 命令。
默认情况下,服务器在端口 3333 上运行,因此浏览器中的 localhost:3333 应该显示您应用的 index.html 页面。
既然我们的集成工作已经完成,让我们获取已登录用户的点赞列表。将您的 checkLoginStatusAndLoadUserLikes 函数修改如下:
function checkLoginStatusAndLoadUserLikes() {
FB.getLoginStatus(function(response) {
if (response.status === 'connected') {
loadUserAndLikes();
} else {
loginAndLoadUserLikes();
}
});
}
第一步是检查用户已经登录的位置。调用 FB.getLoginStatus 函数可以做到这一点。在回调函数内部,传递的参数代表来自 API 的响应。此响应包含有关用户登录状态的信息。这将是一个非常常见的参数,因为其他 API 调用也会将响应对象返回到您的回调函数。表示用户已授权应用并成功登录的状态是 connected。如果用户已经登录,我们只需调用 loadUserAndLikes 函数,但如果未连接,则调用另一个函数来登录,然后调用 API 加载用户的点赞列表。
函数 loginAndLoadUserLikes 应该如下所示:
function loginAndLoadUserLikes() {
FB.login(function(response) {
loadUserAndLikes();
}, {scope: 'user_likes'});
}
登录功能已移动到这个方法。一旦登录操作完成,我们就调用 loadUserAndLikes。注意,我们现在在登录函数调用末尾传递一个对象 {scope: 'user_likes'};这个对象代表 Facebook 上的作用域/权限,这是我们的应用程序所要求的。在下面的示例中,在第一个函数调用(loadUserAndLikes)中,返回了登录用户的 userResponse。然后 API 列出了该登录用户的全部点赞。
函数 loadUserAndLikes 应该如下所示:
function loadUserAndLikes() {
FB.api('/me', function(userResponse) {
console.log(1, userResponse);
FB.api('/me/likes', function(likesResponse) {
console.log(2, likesResponse);
});
});
}
您可以在 JSX 文件中包含尽可能多的 JavaScript 代码。它不是一种只理解其自身语法的特定类型的文件,它只是将您使用 JSX 特定语法标记的地方转换过来。
注意,我们正在对 API 进行两次请求:第一个请求将获取用户详情,另一个请求将获取用户的点赞列表。我们在实现我们的 ReactJS 组件以渲染它之前,将它们记录到控制台以进行测试。这是我用户的输出:

到目前为止,我们已经探讨了 Facebook Open-Graph API 及其配置方法。你可能想知道这与学习 ReactJS 有什么关系?我只能说,根据我的经验,大多数人在教授一个新的库或框架时,所提供的示例都依赖于 待办事项 应用程序、同步操作,并且当它们使用外部资源时。这并不适用于常见的集成任务,如登录。这种集成让我们更好地了解当我们将更现实的场景放入其中并尽可能使其简单时,所教授的事物是如何工作的。完成这个示例后,我们将了解如何将 ReactJS 与您自己的私有 API 集成,例如。
在 ReactJS 组件中渲染数据
我们现在有了要传递给我们要创建的 ReactJS 组件的数据。首先,让我们从 UserDetails 组件开始。这个组件将显示一个带有登录用户名的链接以及该用户的 Facebook 页面链接。首先,从 index.html 文件中移除我们的旧登录锚点,因为这个功能现在不再需要了。我们的登出功能将移动到我们的 ReactJS 组件中。我们还会在主 div 上方创建另一个 div,命名为 user;这个新元素将包含 UserDetails 组件。index.html 中的更改应该如下所示:
<h1>Facebook User's list of likes</h1>
<div id="user"></div>
<div id="main"></div>
您可以在 index.jsx 文件的底部创建 UserDetails ReactJS 组件:
var UserDetails = React.createClass({
handleLogout: function () {
FB.logout(function () {
alert("You're logged out, refresh the page in order to login again.");
});
}
render: function () {
return (
<section id="user-details">
<a href={this.props.userDetails.link} target="__blank">
{this.props.userDetails.name}
</a>
{' | '}
<a href="#" onClick={this.handleLogout}>Logout</a>
</section>
)
},
});
现在,我们需要将 loadUserAndLikes 函数修改为调用 React.render 方法,将其指向用户 HTML 的 div 元素:
function loadUserAndLikes () {
FB.api('/me', function (userResponse) {
React.render(<UserDetails userDetails={userResponse} />, document.getElementById('user'));
FB.api('/me/likes', function (likesResponse) {
console.log(2, likesResponse);
});
});
}
如你所见,UserDetails ReactJS 组件非常简单和基本;它就像一个模板,只是渲染传递给它的数据。不要对此感到失望,因为我们将在接下来的章节中给它添加更多功能,例如,在用户注销时渲染登录按钮,而不是要求刷新页面,隐藏列表,在等待 SDK 响应时显示加载 gif 图像。所有这些功能都需要我们处理状态,这将在下一章中介绍。
你可以通过刷新页面、点击注销按钮,然后再次刷新页面来测试它。一旦你在 Facebook 上登录,用户详情应该显示在浏览器中,位于我们页面标题下方。
我们现在需要渲染用户的点赞列表,并将丑陋的 console.log(2, likesResponse); 命令替换为 ReactJS 渲染函数。首先,让我们在 index.jsx 文件的底部创建我们的 UserLikesList 组件:
var UserLikesList = React.createClass({
render: function() {
var items = this.props.list.map(function (likeObject) {
return <UserLikeItem data={likeObject} />;
});
return (
<ul id="user-likes-list">
{items}
</ul>
);
}
});
我们创建了一个名为 items 的 UserLikeItem 组件数组,并在列表 <ul> 元素内部渲染它们。UserLikeItem 组件应该看起来像这样:
var UserLikeItem = React.createClass({
render: function() {
var data = this.props.data;
return (
<li>
<img src={data.picture.data.url} title={data.name} />
<h1>{data.name} <small>{data.category}</small></h1>
</li>
);
}
});
我们将属性数据放在一个单独的变量中,以避免在组件标记中存在长名称。请注意,我们还在显示来自点赞的 Facebook 资源的照片;因此,我们还需要在我们的 API 调用中请求这一点,并通过传递这个点赞列表来渲染我们的组件:
function loadUserAndLikes () {
FB.api('/me', function (userResponse) {
React.render(<UserDetails userDetails={userResponse} />, document.getElementById('user'));
var fields = { fields: 'category,name,picture.type(normal)' };
FB.api('/me/likes', fields, function (likesResponse) {
React.render(<UserLikesList list={likesResponse.data} />, document.getElementById('main'));
});
});
}
我们已经将 loadUserAndLikes 函数更改为请求 API 也带来点赞的 Facebook 资源的照片。默认情况下,它被省略在响应中。
尝试一下,看看你是否能在页面上显示你的点赞列表。它可能看起来非常奇怪,因为我们还没有创建任何样式。在 index.html 页面中创建一个样式元素来为我们的列表添加样式:一个非常基本的样式,但只是为了使我们的示例看起来更令人愉悦:
<style>
#user-likes-list {
list-style: none;
padding: 5px;
margin: 0;
}
#user-likes-list li {
display: inline-block;
width: 270px;
margin: 5px;
background-color: rgb(122, 174, 233);
height: 100px;
overflow: hidden;
}
#user-likes-list img {
display: inline-block;
vertical-align: top;
width: 100px;
}
#user-likes-list h1 {
font-size: 1.4em;
display: inline-block;
width: 160px;
vertical-align: top;
margin-left: 5px;
color: rgb(20, 90, 169);
margin: 5px 0 0 5px;
}
#user-likes-list small {
font-size: 0.7em;
display: block;
color: rgb(145, 50, 0);
margin-top: 5px;
}
</style>

好吧,可能没有样式看起来不太好。无论如何,你总是可以向设计师寻求帮助!
ReactJS 属性使你的组件可配置和可更改。正如你所见,创建 ReactJS 组件并在你的页面上渲染它们非常简单,即使在现有的页面上也是如此。你不需要让整个应用支持框架或从头开始创建一个。你只需定义你的组件是什么,将它们视为一个集合,避免创建难以维护的大组件,并在页面的某个位置渲染它们。通过智能地处理有状态的组件,它的功能得到了增强,这是我们学习 ReactJS 的下一步。
摘要
在本章中,我们学习了如何向 ReactJS 组件传递属性,并根据这些组件渲染 UI。我们还学习了如何创建父组件与其子组件进行通信。我们看到了如何配置 Facebook Open-Graph API,如何使用登录功能将其与 ReactJS 集成,以及如何将 API 请求调用的响应渲染成一系列较小的 ReactJS 组件。
在下一章中,我们将深入探讨有状态组件,并了解如何根据用户的输入或任何其他需要状态改变以及 UI 自动表示该状态的原因来创建可变的 ReactJS 组件。
第四章. 状态组件和事件
在本章中,我们将详细探讨 React 状态和事件。本章将涵盖具有状态的组件、它们之间的通信实践以及如何响应用户输入/事件,以便使 UI 反映这种状态。我们将开发一个网页,我们可以从中更新我们喜欢的 Facebook 页面的名称。这也涵盖了状态如何通过使用虚拟 DOM 提高性能来改变你的 React UI。
本章将涵盖以下内容:
-
React 状态
-
事件生态系统
ReactJS 中属性与状态的区别
让我们简要地看一下 React 中 props 和 states 之间的区别。
属性是在创建 React 组件时声明的,而 状态是在组件定义内声明的。因此,在组件初始化阶段声明 props。
-
在大多数情况下,React 组件以 props 的形式接收数据。在其他情况下,当你需要接收用户输入以进行服务器请求时,使用状态。
-
(this.props)用于访问从父组件传递的参数,而(this.state)用于管理动态数据。状态应被视为私有数据。
探索状态属性
在上一章中,我们探讨了 React 属性(props)。正如组件可以有属性一样,它们也可以有 states。状态主要设置在那些需要改变的组件中,例如如果组件在未来需要更新或替换。因此,对于整个组件来说,拥有状态不是强制性的。
组件可以是无状态的或状态化的:
-
无状态组件是那些只有 props 的组件,但没有定义状态。因此,这些组件在其组件生命周期内这些 prop 值将不会发生变化。静态数据的唯一来源应通过 props 呈现给组件实例。因此,对于 React 组件来说,props 是不可变数据。
-
状态化组件:无状态组件是指那些被声明为没有状态且返回相同标记的函数的 React 组件。正如其名所示,状态化组件是那些声明了 props 和 states 的组件。通常,这种数据更改通信是通过状态更改
[setState(data, callback)]来完成的。然后更新后的状态将在 UI 中渲染。在交互式应用程序[表单提交等]中,数据不断变化时,需要这样的状态化组件。否则,对于非交互式应用程序,建议拥有较少的状态化组件,因为它们会增加应用程序的复杂性和冗余。
初始化状态
组件状态的初始化是通过 getInitialState() 方法完成的,它返回一个对象:
object getInitialState()
getInitialState() 方法在组件挂载之前被调用一次。返回值将用作 this.state 的初始值。
注意
对于所有以下示例,我们在 index.html 文件中有相同的内容。因此,我们可以使用相同的 index.html 文件,并且只需根据讨论的主题更改相应的 JavaScript 文件的内容。
我们可以创建一个如下所示的 React 有状态组件:
var FormComponent = React.createClass({
getInitialState:function(){
return {
name: 'Doel',
id: 1
};
},
render : function() {
return <div>
My name is {this.state.name}
and my id is {this.state.id}.
</div>;
}
});
React.renderComponent(
<FormComponent />,
document.body
);
注意
getInitialState() 方法在第一次渲染周期中用值(name: Doel,id: 1)初始化组件。这些值在状态值更改之前保持不变,并且可以通过运行 {this.state.<VALUE>} 来收集。
设置状态
在 React 中,数据更改通常是通过调用方法 setState(data, callback) 来实现的,该方法与 this.state 的数据一起重新渲染组件。如果你提供了一个可选的回调参数,React 将在执行此方法时调用它,尽管通常不需要,因为 React 会保持 UI 更新。
状态是在组件内部设置的。
以下代码展示了如何更新/设置状态:
var InterfaceComponent = React.createClass({
getInitialState : function() {
return {
name : "doel"
};
},
handleClick : function() {
this.setState({
name : "doel sengupta"
});
},
render : function() {
return <div onClick={this.handleClick}>
hello {this.state.name}, your name is successfully updated!
</div>;
}
});
React.renderComponent(
<InterfaceComponent />,
document.body
);
这是我们的操作:
-
状态中的更改值仅在组件挂载后才会反映出来。
-
组件的挂载发生在它被传递给
React.render(<Component />)时。 -
我们的事件处理器
onClick调用handleClick()函数,该函数内部调用this.state()。因此,当onClick事件在名称doel上初始化时,它将值从doel更改为doel sengupta。
在 React 文档(facebook.github.io/react/docs/interactivity-and-dynamic-uis.html)中,Facebook 建议:
-
有许多无状态组件用于渲染数据,以及一个作为父组件的有状态组件,它通过属性将状态传递给无状态子组件。
-
从本质上讲,有状态组件的功能是包含交互逻辑,而无状态组件渲染数据。
-
组件的状态包含由组件的事件处理器操作的数据。
-
你应该在
this.state中保持最小数据,并在render方法中执行所有计算。这减少了冗余或计算值的存储,并确保对 React 的计算能力有更高的可靠性。 -
React 组件应该在
render()方法中基于底层属性和状态构建 -
应该将属性基本上用作真相的来源。任何可以通过用户的输入或其他方式更改的数据,都应该存储在状态中。
替换状态
index.html:
<!DOCTYPE html>
<html>
<head>
<script src="img/react.min.js"></script>
<script src="img/JSXTransformer.js"></script>
<script src="img/jquery.min.js"></script>
<script src="img/react-dom.js"></script>
<meta charset="utf-8">
<title>My React App</title>
</head>
<body>
<div id="myApp"></div>
<script type="text/jsx", src="img/replace_state.js"></script>
</body>
</html>
这是 replace_state.js 的代码:
//calling replaceSet() method
var FormComponent = React.createClass({
getInitialState : function() {
return {
first_name : "michael",
last_name : "jackson"
};
},
handleClick : function() {
this.replaceState({
last_name : "jordan"
});
},
render : function() {
return <div onClick={this.handleClick}>
Hi {this.first_name + " " + this.state.last_name }
</div>;
}
});
注意
当需要清除现有值并添加新值时,使用 replaceState() 方法。
这是应用首次运行时的样子:

初始页面加载时的应用截图
页面最初加载后,first_name属性的值是michael,但当onClick函数被调用时,值变为undefined。当调用replaceState()时,具有first_name和last_name属性的组件状态被替换为只有last_name。以下截图说明了这一点:

在调用 replaceState()后重新渲染组件
使用交互表单的 React 状态示例
index.html:
<!DOCTYPE html>
<html>
<head>
<script src="img/react.min.js"></script>
<script src="img/JSXTransformer.js"></script>
<script src="img/jquery.min.js"></script>
<script src="img/react-dom.js"></script>
<meta charset="utf-8">
<title>My React App</title>
</head>
<body>
<div id="myApp"></div>
<script type="text/jsx", src="img/react_state.js"></script>
</body>
</html>
react_state.js file:
/* declaration of React component1 with initial values and the changed value to be set in the update function.
*/
var Component1 =
React.createClass({
getInitialState:function(){
return {
name: 'hi',
id: 1
};
},
update: function(e){
this.setState({name: e.target.value});
},
render:function(){
return (
<div>
/* The render method returns the Component2 with props name and the value to be called on update method*/
< Component2 name={this.state.name} update={this.update} />
</div>
);
}
});
/* Declaration of Component2 which calls the update function when onChange method is called. */
var Component2 = React.createClass({
render:function(){
return (
<div>
<input type="text" onChange={this.props.update} />
<br />
<b>{this.props.name}</b>
</div>
);
}
});
ReactDOM.render(< Component1 name="this is the text property" />, document.getElementById('myApp'));
当我们首次运行代码时,我们看到:

用户交互表单
在文本框中输入后,下面的值会自动更改,如下所示:

使用 this.setState()更新的表单数据
现在,让我们更深入地了解事件在 React 生态系统中的流动方式。
事件
React 使用SyntheticEvent,这是一个围绕浏览器原生事件的跨浏览器包装器。因此,react 应用程序中的所有事件处理程序都将传递SyntheticEvent的实例。React 的事件生态系统具有与浏览器原生事件相同的接口,其优势在于它在所有浏览器中工作方式相同,并提供stopPropagation()和preventDefault()。
如果 React 作为 NPM 模块安装,那么这些与SyntheticEvent相关的文件可以在您的应用程序中的以下位置找到:app/node_modules/react/lib。
所有这些事件都符合 W3C 标准。主要事件流程如下:
-
分发事件:
@param {object} dispatchConfig -
标识事件目标的标记:
@param {object} dispatchMarker -
原生事件:
@param {object} nativeEvent
React 使用此事件委托的方式是通过监听具有监听器的节点。根据特定节点上的事件处理程序,React 的合成事件系统实现了自己的冒泡。
合成事件系统中的一些事件名称如下。有关注册事件的完整列表,请参阅 Facebook 文档。
表单事件
-
表单事件的事件名称:
onChange,onInput,onSubmit
注意
有关
onChange事件的更多信息,请参阅表单(facebook.github.io/react/docs/forms.html)。
鼠标事件
-
鼠标事件的事件名称:
-
onClick,onContextMenu,onDoubleClick,onDrag,onDragEnd,onDragEnter,onDragExit -
onDragLeave,onDragOver,onDragStart,onDrop,onMouseDown,onMouseEnter,onMouseLeave -
onMouseMove,onMouseOut,onMouseOver,onMouseUp
-
让我们看看SyntheticEvent系统在 React 组件上调用的一些不同事件的示例。
在 JavaScript 文件中,我们有以下代码片段:
/* React component EventBox is decalred which shows the different functions it fires in response of diffenrent Synthetic events.*/
var EventBox = React.createClass({
getInitialState:function(){
return {e: 'On initial page load'}
},
update: function(e){
this.setState({e: e.type})
},
render:function(){
return (
<div>
<textarea
/*Following are the various events (on the left hand side ). In response of all these events then the update function is called. */
onKeyDown={this.update}
onKeyPress={this.update}
onCopy={this.update}
onFocus={this.update}
onBlur={this.update}
onDoubleClick={this.update}
/>
<h1>{this.state.e}</h1>
</div>
);});
以下代码在浏览器中显示一个文本框。当我们输入框中时,相应的事件类型会打印出来。由于我们使用 event.type 更新状态,因此当我们在框中输入时,相应的事件会显示在下面。
ReactDOM.render(<EventBox />, document.getElementById('myTextarea'));

用户交互表单
nativeEvent
index.js file:
/* React component MyBrowserDimension, with nativeEvent attribute which is needed for manipulating the underlying browser events(window.innerWidth, window.innerHeight). */
var MyBrowserDimension = React.createClass({
getInitialState:function(){
return {
width: window.innerWidth,
height: window.innerHeight
};
},
update: function(){
this.setState({
height: window.innerHeight,
width: window.innerWidth
});
},
//componentDidMount is called after the component is mounted and //has a DOM presentation. This is often a place where we will //attach generic DOM events.
componentDidMount:function(){
window.addEventListener('resize', this.update );
window.addEventListener('resize', this.update );
},
componentWillUnmount:function(){
window.removeEventListener('resize', this.update );
window.removeEventListener('resize', this.update );
},
render:function(){
return <div>
<p>My Browser Window current Inner Width is: {this.state.width} pixels</p>
<p>My Browser Window current height is {this.state.height} pixels</p>
</div>;
}
});
ReactDOM.render(<MyBrowserDimension />, document.getElementById('myApp'));
这里是相应 HTML 页面的源代码:
<!DOCTYPE html>
<html>
<head>
<script src="img/react.js"></script>
<script src="img/react-dom.js"></script>
<script src="img/JSXTransformer.js"></script>
<meta charset="utf-8">
<title>React DOM attributes</title>
</head>
<body>
<div id="myApp"></div>
<script type="text/jsx", src="img/index.js"></script>
</html>
从以下屏幕截图可以看出,当我们调整浏览器窗口大小时,宽度和高度值会改变。第一张图片显示了浏览器的完整大小 [1311/681]。

显示浏览器原生属性的 App
前一个应用程序代码的第二张图片显示,在调整浏览器窗口大小后,像素值变为 497/219。

原生 DOM 属性随浏览器窗口变化而变化
注意
根据 Facebook 开发者团队 (facebook.github.io/react/tips/dom-event-listeners.html):
"在 React 应用中声明的回调事件都绑定到 React 组件上。这些事件回调与原始元素无关。通过自动绑定,这些方法会自动绑定到当前元素。"
此外,每个 SyntheticEvent 对象都有以下属性:
-
boolean bubbles: 所有事件处理程序都在事件冒泡阶段触发;可以是true/false -
boolean cancelable: 合成事件对象是否可以被取消(true/false) -
DOMEventTarget currentTarget: 根据 W3C 建议,currentTarget事件属性返回触发事件的元素
这在捕获和冒泡期间特别有用。
currentTarget 属性始终指向触发事件的元素,而不是 target 属性,后者返回触发事件的元素。
-
boolean defaultPrevented: 是否可以默认阻止SyntheticEvent对象(true/false) -
number eventPhase:eventPhase事件属性返回一个数字,表示当前正在评估的事件流的哪个阶段(见:developer.mozilla.org/en-US/docs/Web/API/Event/eventPhase)数字由四个常量表示:
0–NONE。1–CAPTURING_PHASE: 事件流处于捕获阶段。2–AT_TARGET: 事件流处于目标阶段,即正在事件目标处评估。3–BUBBLING_PHASE: 事件流处于冒泡阶段。 -
boolean isTrusted: 根据 JS 建议,在 Chrome、Firefox 和 Opera 中,如果事件是由用户触发的,则认为是可信的(返回 true),如果是由脚本触发的,则不可信(返回 false)。 -
DOMEvent nativeEvent:nativeEvent是一种DOMEvent。 -
void preventDefault(): 如果事件可取消,preventDefault()方法将取消事件(取消方法的默认操作),但它不会防止事件通过 DOM 进一步传播。React 中preventDefault()的返回类型为 void。 -
void stopPropagation(): 调用stopPropagation以防止事件冒泡到其父元素,从而防止任何父事件处理器被调用。 -
boolean isDefaultPrevented():isDefaultPrevented用于检查是否调用了preventDefault()方法(true)或未调用(false)。 -
boolean isPropagationStopped():isPropagationStopped用于检查是否调用了stopPropagation()方法(true)或未调用(false)。 -
DOMEventTarget target: 它用于识别声明的SyntheticEvent对象的目标,返回触发事件的元素。返回类型为DOMEventTarget。 -
number timeStamp: 这用于识别声明的SyntheticEvent对象的时间戳。返回类型为数字。 -
string type: 这用于识别声明的SyntheticEvent对象类型。返回类型为字符串。
注意
注意:从 v0.14 版本开始,从事件处理器返回 false 将不再停止事件传播。相反,应根据需要手动触发e.stopPropagation()或e.preventDefault()。
事件池
池是一个存储事件/对象的地方,以便在稍后阶段,在垃圾回收后可以重用。在 React 生态系统中,回调函数中接收的事件对象(SyntheticEvent)被池化。如前所述,在事件回调函数被调用后,SyntheticEvent将被放回池中,属性为空,从而减轻垃圾回收器的压力。以下是在 Facebook 文档中提到的关于事件池的一些关键要点。
React 中的SyntheticEvent系统是池化的。
"这意味着
SyntheticEvent对象将被重用。"在事件回调函数被调用后,所有属性都将被置为 null。
这是出于性能考虑。
我们无法以异步方式访问事件。
为了以异步方式访问事件属性,我们应该在事件上调用
event.persist(),这将移除合成事件从池中,并允许用户代码保留对事件的引用。
支持的事件
React 标准化事件,以确保在不同浏览器中具有一致的属性。
根据 Facebook 文档(facebook.github.io/react/docs/events.html)
"React 生态系统中的合成事件的事件处理器在冒泡阶段的事件触发。"
现在我们已经了解了 React 组件中的状态以及事件处理是如何发生的,让我们看看我们如何在上一章中构建的应用中使用这些功能。
到目前为止,我们已经能够使用 Graph-API 和 Facebook 登录到我们的应用程序中显示用户的点赞。基于其 props,每个组件都渲染了自己一次。Props 是不可变的:它们是从父组件传递的,并且由父组件拥有。现在,我们将在 React 组件所在的特定div的任何部分更新喜欢组件的onClick名称。
以下示例的index.html代码片段是:
<html>
<head>
<title>Learning React State</title>
<script src="img/react-0.13.3.js"></script>
<script src="img/JSXTransformer-0.13.3.js"></script>
</head>
<body>
<h1>Facebook User's list of likes</h1>
<div id="user"></div>
<div id="main"></div>
<a onClick='logout()' href='#'>Logout</a>
<script>
window.fbAsyncInit = function() {
FB.init({
appId : '1512084142440038',
xfbml : true,
version : 'v2.2'
});
checkLoginStatusAndLoadUserLikes();
};
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = "//connect.facebook.net/en_US/sdk/debug.js";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
<script type="text/jsx" src="img/index.js"></script>
</body>
</html>
以下代码位于js文件中:
// The following code block explains in order to login to user's Facebook //account and call the function internally( loginAndLoadUserLikes) if //successfully connected.
function checkLoginStatusAndLoadUserLikes() {
FB.getLoginStatus(function(response) {
if (response.status === 'connected') {
loadUserAndLikes();
} else {
loginAndLoadUserLikes();
}
});
}
function loginAndLoadUserLikes() {
FB.login(function(response) {
loadUserAndLikes();
}, {scope: 'user_likes'});
}
//Once logged in, this method should load the details of the specific user.
var UserDetails = React.createClass({
render: function () {
return (
<section id="user-details">
<a href={this.props.userDetails.link} target="__blank">
{this.props.userDetails.name}
</a>
{' | '}
<a href="#" onClick={this.handleLogout}>Logout</a>
</section>
)
},
//Specified user should be able to logout from the respective account
handleLogout: function () {
FB.logout(function () {
alert("You're logged out, refresh the page in order to login again.");
});
}
});
//Once logged in, this method should load the likes pages of the specific user.
function loadUserAndLikes () {
FB.api('/me', function (userResponse) {
React.render(<UserDetails userDetails={userResponse} />, document.getElementById('user'));
var fields = { fields: 'category,name,picture.type(normal)' };
FB.api('/me/likes', fields, function (likesResponse) {
React.render(<UserLikesList list={likesResponse.data} />, document.getElementById('main'));
});
});
}
//Once logged in, this method should list the liked pages of the specific user.
var UserLikesList = React.createClass({
render: function() {
var items = this.props.list.map(function (likeObject) {
return <UserLikeItem data={likeObject} />;
});
return (
<ul id="user-likes-list">
{items}
</ul>
);
}
});
var UserLikeItem = React.createClass({
getInitialState: function() {
return {data_name: this.props.data.name};
},
handleClick: function(){
this.setState({
data_name: 'I liked it'})
},
render: function() {
var props_data = this.props.data;
return (
<div onClick={this.handleClick}>
<img src={props_data.picture.data.url} title={props_data.name} />
<h1> {this.state.data_name} <small>{props_data.category}</small></h1>
</div>
);
}
});
突出的部分显示了我们所做的更改,以保存状态。
getInitialState():通过从 props 数据初始化喜欢数据名称的值来声明。getInitialState()在组件的生命周期中恰好执行一次,并设置组件的初始状态。
可变状态(可以改变的状态)的原因如下:
-
在组件(
UserLikedItem)中引入了可变状态。为了实现交互,可以通过调用this.setState()来更改this.state,并且它是私有的。当状态更新时,组件(UserLikedItem)会重新渲染自己。 -
render()方法是由 Facebook 开发团队编写的,以声明性方式作为this.props和this.state的函数。它们确保框架保证 UI 始终与输入保持一致。 -
这是一个完美的例子,说明了在 React 生态系统中组件之间数据是如何流动的。属性(数据)从 React 组件
UserLikesList传递到另一个组件,UserLikedItem。

应用程序从 Facebook 获取用户的喜欢页面
React 使用驼峰命名约定将事件处理器附加到组件上。我们将onClick处理器附加到div元素上,这样当用户点击图像的任何部分、图像名称或类别时,它都会变为我喜欢它。

React this.setState()替换喜欢项目的名称
摘要
根据 Facebook 的 Reconciliation 文档(facebook.github.io/react/docs/reconciliation.html)
"React 的关键设计决策是使 API 看起来在每次更新时都会重新渲染整个应用程序。"
因此,每当在对象上调用setState()方法时,该特定节点就会被标记。在事件循环结束时,所有在调用setState()方法的节点都会重新渲染。
React 之所以快速,是因为它从不直接与 DOM 交谈。它维护实际 DOM 的内存表示。每当调用render()方法时,它返回实际 DOM 的映射。React 可以检测(使用差异算法)映射的 DOM 与内存表示之间的变化。然后重新渲染更改并相应地更新 UI。
React 中的事件生态系统是通过一个完整的合成事件系统(SyntheticEvent())实现的。由于所有事件都一致地冒泡,因此实现了跨浏览器的效率。
在本章中,我们探讨了 React 中的有状态组件以及合成事件系统在 React 应用程序中的处理方式。在 React 组件中,状态用于那些可变属性。在下一章中,我们将探讨组件的生命周期以及这些生命周期方法如何与各种事件以及整个 DOM 交互。
第五章:组件生命周期和 React 中的新 ECMAScript
到目前为止,我们已经探讨了 React 组件属性以及我们需要如何初始化、更新和更改组件的状态(s)以实现交互式应用程序。现在让我们在本章中探索此类 React 组件的生命周期。我们还将深入研究未来的 ECMAScript 语法以及 React 社区从 0.13.0 版本开始使用的一些更改。为此,我们将回顾 React 库中的一些 ES6 和 ES7 功能。
当通过调用 React.createClass() 创建任何 React 组件时,我们始终需要有一个渲染方法。此渲染方法返回 DOM 的描述。React 在我们的应用程序中具有性能优势,因为 React 维护一个快速的内存表示形式的 DOM,并且从不直接与实际的 DOM 交互。因此,当渲染方法返回 DOM 的描述时,React 可以比较实际 DOM 和内存表示之间的差异,并根据差异重新渲染视图。
在本章中,我们将涵盖以下主题:
-
React 组件生命周期
-
在 ECMAScript 中使用 React
React 组件生命周期
根据 Facebook 的 React 文档 facebook.github.io/react/docs/working-with-the-browser.html,React 组件生命周期可以大致分为以下三个类别:
"挂载:组件正在被插入到 DOM 中。"
更新:组件正在重新渲染以确定是否应该更新 DOM。*
卸载:组件正在从 DOM 中移除。"*"
React 提供了生命周期方法,您可以在其中指定以挂钩到这个过程。我们提供了 will 方法,这些方法在某个事件发生之前被调用,以及 did 方法,这些方法在某个事件发生后被调用。
挂载类别
挂载 是将组件的虚拟表示发布到最终 UI 表示(例如,DOM 或原生组件)的过程。在浏览器中,这意味着将 React 元素发布到 DOM 树中的实际 DOM 元素。
| 方法名称 | 方法功能 |
|---|---|
getInitialState() |
此方法在组件挂载之前被调用。对于有状态组件,此方法返回初始状态数据。 |
componentWillMount() |
此方法在 React 将组件挂载到 DOM 之前被调用。 |
componentDidMount() |
此方法在组件挂载后立即调用。DOM 节点所需的初始化过程应在此方法中进行。 |
与前几章一样,index.html 中的大部分代码都是相同的。我们只需替换 JavaScript 文件的全部内容。
index.html 的代码将变为以下内容:
<!DOCTYPE html>
<html>
<head>
<script src="img/react.min.js"></script>
<script src="img/JSXTransformer.js"></script>
<script src="img/react-dom.js"></script>
<meta charset="utf-8">
<title>My React App</title>
</head>
<body>
<div id="app"></div>
<script type="text/jsx", src="img/index.js"></script>
</body>
</html>
index.js file:
var MyButton = React.createClass({
getInitialState: function(){
return {value: 11}
},
addOnClick: function(){
this.setState({value: this.state.value + 2});
},
render: function(){
console.log("myButton React component is rendering");
return <button onClick={this.addOnClick}>{this.state.value}</button>
}
});
ReactDOM.render(<MyButton />, document.getElementById('myComponent'));
初始时,我们可以看到 myButton 的值设置为 11:

当onClick(addOnClick)事件发生时,myButton的值增加两个。因此,状态的值发生了变化。

当onClick事件发生时,myButton的值增加两个
如果我们将componentWillMount方法添加到前面的代码中,我们将能够看到 React 组件在 DOM 中只挂载一次,但每次我们点击按钮时都会进行渲染。
componentWillMount: function(){
console.log('MyButton component is mounting');
},
在控制台中显示的应用组件挂载到 DOM 的截图,MyButton 组件正在挂载。

应用组件挂载到 DOM 的截图
让我们实现最后一个挂载方法,componentDidMount,它在组件挂载后被调用。正如您在下一张截图中所见,控制台显示组件已挂载一次,但组件被渲染的次数与我们点击按钮的次数相同:11 + (2*4) =19。
componentDidMount: function(){
console.log('MyButton component is mounted');
},
该截图显示挂载和挂载到 DOM 只调用一次的方法,尽管发生了渲染。因此,在componentDidMount方法执行后,在控制台中我们可以看到输出MyButton 组件已挂载。

方法的截图显示挂载和挂载到 DOM 只调用一次,尽管发生了渲染
更新类别
React 组件的生命周期允许在运行时更新组件。这可以通过以下方法完成:
| 方法名称 | 方法功能 |
|---|---|
componentWillReceiveProps(object nextProps) |
当挂载的 React 组件接收到新属性(props)时,此方法会被调用。这意味着您可以使用它来比较this.props,当前属性集,和nextProps,新的属性值。没有类似componentWillReceiveState的方法。因此,传入的属性转换可能会引起状态变化,但传入的状态可能不会引起属性变化。如果我们想对状态变化执行某些操作,我们需要使用componentWillUpdate方法。因此,组件的属性变化将在更新的视图中渲染,而无需重新渲染视图。 |
shouldComponentUpdate(object nextProps, object nextState) |
当组件需要在 DOM 中更新时,此方法会被调用。返回类型是布尔值(true/false)。如果没有 props 和/或状态的变化,它将返回false,这将阻止componentWillUpdate和componentDidUpdate被调用。 |
componentWillUpdate(object nextProps, object nextState) |
如其名所示,此方法在更新发生之前立即被调用,但不在第一次渲染调用中。在此生命周期方法中不能调用this.setState()。要响应属性变化更新状态,请使用componentWillReceiveProps代替。 |
componentDidUpdate(object prevProps, object prevState) |
这是在 DOM 更新后立即调用的,而不是在初始 render() 调用期间。 |
让我们在代码中添加前面的方法:
//Updating lifecycle methods
shouldComponentUpdate: function() {
console.log('ShouldComponentUpdate');
return true;
},
componentWillReceiveProps: function(nextProps) {
console.log('ComponentWillRecieveProps invoked');
},
componentWillUpdate: function() {
console.log('ComponentWillUpdate invoked');
},
componentDidUpdate: function() {
console.log('ComponentDidUpdate invoked');
},
执行前面的代码以查看以下输出。我们可以看到 React 组件的各种生命周期事件以及它们在控制台输出的对应内容。

组件更新的截图
卸载分类
在组件卸载和销毁之前立即调用 componentWillUnmount()。你应该在这里执行任何必要的清理工作。
componentWillUnmount: function(){
console.log('Umounting MyButton component');
}
下面是一个包含 React 组件所有生命周期方法的完整示例。index.html 与前面相同。
index.html 的代码:
<!DOCTYPE html>
<html>
<head>
<script src="img/react.min.js"></script>
<script src="img/JSXTransformer.js"></script>
<script src="img/react-dom.js"></script>
<meta charset="utf-8">
<title>My React App</title>
</head>
<body>
<div id="app"></div>
<script type="text/jsx", src="img/index.js"></script>
</body>
</html>
下面是相应的 index.js 代码:
var MyButton = React.createClass({
getDefaultProps: function() {
console.log('GetDefaultProps is invoked');
return {id: 1};
},
getInitialState: function(){
return {value: 11}
},
addOnClick: function(){
this.setState({value: this.state.value + 2});
},
componentWillMount: function(){
console.log('MyButton component is mounting');
},
render: function(){
console.log("myButton React component is rendering");
return ( <div>
<button>{this.props.id}</button>
<button onClick={this.addOnClick}>{this.state.value}</button>
</div>);
},
componentDidMount: function(){
console.log('MyButton component is mounted');
},
//Updating lifecycle methods
shouldComponentUpdate: function() {
console.log('ShouldComponentUpdate');
return true;
},
componentWillReceiveProps: function(nextProps) {
console.log('ComponentWillRecieveProps invoked');
},
componentWillUpdate: function() {
console.log('ComponentWillUpdate invoked');
},
componentDidUpdate: function() {
console.log('ComponentDidUpdate invoked');
},
//Unmounting Lifecycle Methods
componentWillUnmount: function(){
console.log('Umounting MyButton component');
}
});
var ComponentApp = React.createClass({
mount: function(){
ReactDOM.render(<MyButton />, document.getElementById('myApp'));
},
unmount: function(){
ReactDOM.unmountComponentAtNode(document.getElementById('myApp'));
},
render: function(){
return (
<div>
<button onClick={this.mount}>Mount</button>
<button onClick={this.unmount}>Unmount</button>
<div id="myApp"></div>
</div>
);
}
});
ReactDOM.render(<ComponentApp />, document.getElementById('app'));
注意以下内容:
-
执行上述代码后,我们将能够看到两个按钮,分别是 挂载 和 卸载
-
组件的初始值设置为 11
-
React 组件上的
onClick;其值增加两个 -
当点击 挂载 时,React 组件的生命周期方法被调用
-
对于这些生命周期方法中的每一个,我们都可以在控制台中看到输出
![卸载分类]()
从 DOM 中卸载组件的截图
注意
注意:已挂载的复合组件支持 component.forceUpdate() 方法。如果组件的深层方面发生某些更改,可以在不使用 this.setState() 的情况下调用此方法。
我们将在下面展示我们的 React 组件的生命周期。生命周期在开发工具的右侧部分被突出显示:

展示 React 组件生命周期的截图,如图形开发工具右侧所示
React 中的其他 ES (ECMAScript) 版本
在本章的后半部分,我们将探讨 React 如何支持 ECMAScript 的新版本。到目前为止,我们已经探讨了 React 组件中的不同生命周期方法。在本章的这一部分,我们将深入了解不同内容:ECMAScript 新版本中的变化是如何被 React 采纳的。
ES6
ES6 是 ECMAScript 语言规范标准的当前版本。关于更改和新增内容的更多详细信息,可以在 Mozilla 开发者网络网站上找到:developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_6_support_in_Mozilla
本书的范围超出了 ES6 的完整文档。
根据 Facebook 文档:
从 React 0.13.0 版本开始,一个转换器允许我们使用 ES6 类。JavaScript 最初没有内置的类系统。开发团队希望使用惯用的 JavaScript 风格创建类。因此,开发团队引入了组件,而不是 React.createClass。您可以通过使用 react-tools 中的转换器并利用和谐选项将其设置为 true 来使用他们提供的转换器,如下所示:
jsx –harmony
通过查看 www.npmjs.com/package/react-tools,您可以找到有关可以传递给 JSX 转换器的不同选项的详细信息。--harmony 启用诸如 ES6 类等 JS 转换。
因此,ES6 语法将被转换为与 ES5 兼容的语法。
注意
转换 是一种将一种语言编写的源代码转换为具有相似抽象级别的另一种语言的方法。
当 TypeScript 编译并由编译器转换为 JavaScript 时,它具有非常相似的抽象级别。因此,它被称为转换。
在这里,React 类被定义为普通的 JavaScript 类。让我们通过以下代码(经过一些修改和解释)来了解他们的文档。
index.html 中的代码:
<!DOCTYPE html>
<html>
<head>
<script src="img/react.js"></script>
<script src="img/react-dom.js"></script>
<script src="img/JSXTransformer.js"></script>
<meta charset="utf-8">
<title>React ES6</title>
<h1>ok</h1>
</style>
</head>
<body>
<div id="react-content"></div>
<script type="text/jsx;harmony=true" src="img/index.js"></script>
</html>
带有 harmony=true 参数的突出显示行确保 JSX 语法与 ES6 代码一起使用时应使用 ES5 语法进行转换。
index.js 中的代码:
//line 1
class Es6Component extends React.Component {
//line 2
render() {
return <div onClick={this._handleClick}>Hi There, I am learning ES6 in React.</div>;
}
_handleClick() {
console.log("hi");
}
}
ReactDOM.render(<Es6Component />, document.getElementById('react-content '));
说明:
-
第 1 行:声明 React 组件
ES6Component,它扩展自React.Component而不是React.createClass -
第 2 行:渲染函数调用语法不同。之前是
render: function()
下面是一个演示截图:

使用 ES6 的 React 组件截图
在 React.createClass 中不再使用 getInitialState,使用 ES6 的新构造函数在 React.Component 中具有新的自有状态属性,该属性被导出。
export class Counter extends React.Component
/* The constructor of the newly created React class, Counter. There are the following things to be noted:call to super(props)And instead of calling getInitialState() ifecycle method, React team used the instance property called this.state() */
constructor(props) {super(props);
this.state = {count: props.initialCount};
}
tick() {
this.setState({count: this.state.count + 1});
}
render() {
return (
<div onClick={this.tick.bind(this)}>
Clicks: {this.state.count}
</div>
);
}
}
/* For validation and default values purposes propTypes and defaultProps are inbuilt within React's component. Here the propTypes and defaultProps are defined as properties on the constructor instead within the the class body. */
// Declares the React's class Counter property types as number
Counter.propTypes = { initialCount: React.PropTypes.number };
/* sets the defaultProps for the Counter React class as initialCount being 0\. These values are passed as super(props)*/
Counter.defaultProps = { initialCount: 0 };

使用 ES6 的 React 组件截图
React 使用 ES6 的另一个特性是自动绑定。
与 ES6 类一样,由于它们不会自动绑定到实例,我们需要使用 bind.(this) 或者 在 ES6 中显式使用箭头符号 (=>)。
按照这些 ES6 语法,我们可以重写第 XX 章的示例应用,该应用列出用户的 Facebook 喜欢的内容。就像之前一样,如果用户点击喜欢的页面名称,页面上的字符串 我喜欢它 将被更新。
根据新的 ES6 语法所做的更改将在下面突出显示:
use 'strict';
function checkLoginStatusAndLoadUserLikes() {
FB.getLoginStatus(function(response) {
if (response.status === 'connected') {
loadUserAndLikes();
} else {
loginAndLoadUserLikes();
}
});
}
function loginAndLoadUserLikes() {
FB.login(function(response) {
loadUserAndLikes();
}, {scope: 'user_likes'});
}
//var UserDetails = React.createClass({
class UserDetails extends React.component {
render() {
return (
<section id="user-details">
<a href={this.props.userDetails.link} target="__blank">
{this.props.userDetails.name}
</a>
{' | '}
<a href="#" onClick={this.handleLogout}>Logout</a>
</section>
)
},
handleLogout: function () {
FB.logout(function () {
alert("You're logged out, refresh the page in order to login again.");
});
}
});
function loadUserAndLikes () {
FB.api('/me', function (userResponse) {
ReactDOM.render(<UserDetails userDetails={userResponse} />, document.getElementById('user'));
var fields = { fields: 'category,name,picture.type(normal)' };
FB.api('/me/likes', fields, function (likesResponse) {
React.render(<UserLikesList list={likesResponse.data} />, document.getElementById('main'));
});
});
}
//var UserLikesList = React.createClass({
class UserLikesList extends React.Component {
render() {
let items = this.props.list.map(function (likeObject) {
return <UserLikeItem data={likeObject} />;
});
return (
<ul id="user-likes-list">
{items}
</ul>
);
}
//});
}
//var UserLikeItem = React.createClass({
class UserLikeItem extends React.createComponent {
//getInitialState: function() {
// return {data_name: this.props.data.name};
//},
handleClick(){
this.setState({
data_name: 'I liked it'})
},
render() {
let props_data = this.props.data;
return (
<div onClick={this.handleClick}>
<img src={props_data.picture.data.url} title={props_data.name} />
<h1> Name:{this.state.data_name} </h1>
<h2>Category <small>{props_data.category}</small></h2>
</div>
);
}
}
注意
使用 let 而不是 var 在局部作用域中声明变量。
输出与下一个截图相同:

使用 ES6 语法获取用户喜欢的页面的 React 应用截图
注意
注意:ES6 不支持混入。混入将在本书的 第七章 中更详细地介绍,使组件可重用。混入用于在 React 应用程序中编写可重用代码。
ES7
ECMAScript7 是 ES6 的下一步。甚至在 ES6 最终确定之前,新的功能已经开始被提出。请查看以下 URL 的实验性和稳定特性列表:
developer.mozilla.org/en-US/docs/Web/JavaScript/New_in_JavaScript/ECMAScript_7_support_in_Mozilla
考虑到 ES7,React 开发者社区为我们提供了一些现有的 React 类代码的语法糖。在 JavaScript 的未来版本(ES7)中,可能会有更多用于属性初始化的声明式语法,因为这会是一种更符合习惯的表达方式。以下是一个快速示例:
// Future Version
export class Counter extends React.Component {
static propTypes = { initialCount: React.PropTypes.number };
static defaultProps = { initialCount: 0 };
state = { count: this.props.initialCount };
//constructor
// render method
);
}
}
摘要
在本章中,我们探讨了典型 React 组件的生命周期,它经历的各个阶段,以及 React 如何根据 diff 算法(即计算虚拟 DOM 和实际 DOM 之间的差异)来渲染视图。
在本章的第二部分,我们探讨了 ECMAScript 的未来以及 React.js 如何支持它。为此,我们使用了 Facebook 文档中的示例代码。
在下一章中,我们将讨论 React 的可重用组件,即 mixins。我们还将探讨如何在基于 React 的应用程序中添加验证。对于接受用户输入的应用程序,验证是必需的。在将用户输入发送到服务器之前,应该对其进行验证,以防止恶意或无效内容被发送。
第六章. 使用 Flux 进行响应
到目前为止,在前面的章节中,我们已经深入探讨了 React 世界。现在让我们探索 React 世界的另一个维度,Flux,它实际上只是一个单向数据流架构。Flux 是由 Facebook 内部开发团队开发的,用于在 Facebook 构建客户端 Web 应用程序。
随着我们的进展,我们将涵盖以下主题:
-
Flux 与 MVC 架构的概述
-
动作
-
调度器
-
存储
-
控制器-视图和视图
Flux 概述
Flux不应与基于 ReactJS 的框架混淆。Flux 是一种架构,旨在减少使用模型-视图-控制器(MVC)架构构建的大型应用程序的复杂性,并且被设计为 MVC 的替代方案。
以下是一些不同的 Flux 组件:
-
视图—这就像任何 Web 应用程序一样,视图(基本上是 React 组件)接收事件并将其传递给动作。
-
动作—它们是辅助方法(
actionCreators),将来自外部 API/视图的数据(有效载荷)和actionType传递给调度器。 -
调度器—这些是所有注册回调的中心枢纽。它接收动作,并在将其传递给存储之前充当“交通控制器”。
-
Store—它是一个数据层,存储所有计算和业务逻辑。它还负责存储应用程序状态和应用程序状态的单一真相来源。它根据注册的回调从调度器接收动作。
-
控制器-视图—它根据
changeEvents从存储中接收状态,并通过属性将状态传递给 React 视图组件。
以下图表说明了这一点:

典型的 Flux 数据流架构
Flux 与 MVC 架构
在基于 MVC 架构的典型应用程序中,视图从数据更新,这些数据通常存储在模型中。随着应用程序的增长,模型和视图的数量也增加,各种模型之间的相互依赖性也随之增加。因此,视图也变得依赖于多个模型,从而增加了应用程序的复杂性。
视图和模型之间的相互依赖性可能导致真相来源的扩散,导致应用程序复杂性和不可预测性的增加。因此,需要有一种解决方案,通过将所有控制移入各个部分来内部化控制。

使用 MVC 构建的日益增长的应用程序的问题
Flux 优势
根据 Facebook Flux 开发团队的说法,Flux 应用程序中的对象高度解耦,并且非常严格地遵循德米特法则的第一部分:系统中的每个对象应尽可能少地了解系统中的其他对象。这导致软件更加。
-
可维护性
-
可适应性
-
可测试性
-
对于新工程团队成员来说更容易理解且更可预测
以下是我们 library_app 应用程序的 Flux 应用程序结构。

我们的图书馆应用程序结构
流量组件
让我们深入一个使用 React 视图构建的 Flux 架构的应用程序。在这里,我们将构建一个名为 library_app 的应用程序。这是一个基本的基于 Flux 的 ReactJS 应用程序,我们可以从 library_app 存储库借阅书籍到我们的阅读列表。一旦我们完成阅读书籍,我们可以将其从阅读列表中删除。
注意
在命令行执行:
sudo npm install flux
上述操作将安装 flux 包作为节点模块,并且你的 library_app 应用程序将有一个名为 node_modules 的目录,其中包含安装的 flux 库。
动作
动作通常是进入应用程序的数据,无论是直接来自视图还是来自外部 Web API。每个动作不过是一个 JavaScript 方法,它包含两部分:actionType 和实际数据。actionCreators 方法仅仅是离散的、语义化的辅助函数,它简化了将数据以 action 的形式传递给 dispatcher 的过程。不同类型的动作被声明为一个 JavaScript 对象,在一个名为 App-Constants.js 的文件中。根据 Flux 应用程序层次结构,App-Constants.js 文件位于 src/js/constants 目录下。此类文件的典型示例如下:
module.exports = {
ADD_BOOK: 'ADD_BOOK',
DELETE_BOOK: 'DELETE_BOOK',
INC_BOOK_COUNT: 'INC_BOOK_COUNT',
DEC_BOOK_COUNT: 'DEC_BOOK_COUNT'
}
在这里,ADD_BOOK、DELETE_BOOK 是动作。
注意
动作本身不包含任何功能。动作通常由 stores 执行,并且可用于触发视图。在 React 中,我们有少量名为 actionCreators 的辅助方法,理想情况下,这些方法创建动作对象并将动作传递给 Flux 分发器(AppDispatcher)。
在 AppConstants 中定义的所有动作都在 AppActions 中声明。
在 AppConstants 中使用常量来声明动作名称,有助于开发者理解应用程序的功能。正如我们的案例,它处理书籍。
在以下示例中,在向 library_app 存储库添加书籍时,我们处理了四个 actionTypes:
-
ADD_BOOK -
DELETE_BOOK -
INC_BOOK_COUNT -
DEC_BOOK_COUNT
动作(如 addBook、removeBook、incBookCount 和 decBookCount)根据它们的 actionType 属性是唯一的。因此,当这些动作由 dispatchers 分发到 stores 时,stores 会根据与 dispatchers 注册的特定回调进行自我更新。
典型的动作文件位于 library_app/src/js/actions/app-actions.js:
var AppConstants = require('../constants/app-constants');
var AppDispatcher = require('../dispatchers/app-dispatchers');
var AppActions = {
addBook:function(item){
AppDispatcher.handleViewAction({
actionType: AppConstants.ADD_BOOK,
item: item
})
},
removeBook:function(index){
AppDispatcher.handleViewAction({
actionType: AppConstants.REMOVE_BOOK,
index: index
})
},
incBookCount:function(index){
AppDispatcher.handleViewAction({
actionType: AppConstants.INC_BOOK_COUNT,
item: index
})
},
decBookCount:function(index){
AppDispatcher.handleViewAction({
actionType: AppConstants.DEC_BOOK_COUNT,
item: index
})
}
}
module.exports = AppActions;
分发器
正如其名所定义的,Flux 分发器将动作分发到后续的 stores。分发器可以被视为回调的注册表。所有 stores 都注册在分发器上。
分发器的一些关键点如下:
-
每个应用程序只有一个分发器。
-
Dispatchers 被用作所有注册回调的中心。
-
它充当所有动作到商店的广播器。调度器作为一个队列,依次广播动作。这与通用的 pub-sub 系统在以下两个方面不同:
-
回调没有订阅特定的事件。每个有效负载都会分发到每个已注册的回调。
-
回调可以全部或部分地延迟,直到其他回调已执行。
-
-
调度器具有按指定顺序调用回调的能力,并且它等待其他更新(
waitFor()方法就是这样做的)。 -
在 flux 库(
npm install flux)的node_module中,定义了register()和dispatch()方法,在 fluxlibrary_app中的调度器类中。
查看位于 library_app/node_modules/Flux/lib/Dispatcher.js 的文件:
// Registers a callback to be invoked with every dispatched payload. Returns
// a token that can be used with `waitFor()`.
Dispatcher.prototype.register = function register(callback) {
var id = _prefix + this._lastID++;
this._callbacks[id] = callback;
return id;
};
因此,当调度器从 Actions 接收触发(动作)时,它会逐个将所有动作分发到已注册的商店。这种分发流程是通过 dispatch() 方法启动的,该方法将有效负载(数据)传递到已注册的商店,并注册了回调。
以下代码是从 node_modules 中的 Flux.js 库的调度器摘录:
/**
* Dispatches a payload to all registered callbacks. The highlighted code below ensures the fact that dispatches cannot be triggered in the middle of another dispatch.
*/
Dispatcher.prototype.dispatch = function dispatch(payload) {
!!this._isDispatching ? process.env.NODE_ENV !== 'production' ? invariant(false, 'Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.') : invariant(false) : undefined;
this._startDispatching(payload);
try {
for (var id in this._callbacks) {
if (this._isPending[id]) {
continue;
}
this._invokeCallback(id);
}
} finally {
this._stopDispatching();
}
};
app-dispatcher class.
文件位置在 library_app/src/js/dispatchers/app-dispatchers.js:
var Dispatcher = require('flux').Dispatcher;
var assign = require('react/lib/Object.assign');
var AppDispatcher = assign(new Dispatcher(),{
handleViewAction: function(action){
console.log('action',action);
this.dispatch ({
source: 'VIEW_ACTION',
action: action
})
}
});
module.exports = AppDispatcher;
在实现 library_app 商店之前,让我们检查我们的有效负载(数据)是否在控制台中打印出来。为此,在 React component app.js 中创建了一个处理函数,当点击标题 My First Flux App 的任何部分时会被调用。
文件位置是 library_app/src/js/components/app.js:
var React = require('react');
var ReactDOM = require('react-dom');
//call the AppActions directly, before creation of the Store
var AppActions = require('../actions/app-actions');
//create a App component
var App = React.createClass({
handler: function(){
AppActions.addBook('This is the book..Sherlock Holmes')
},
render:function(){
return <h1 onClick={this.handler}>My First Flux App </h1>
}
});
module.exports = App;
注意
从应用程序的根目录运行 httpster:
doel@doel-Vostro:~/reactjs/ch6_flux_library$httpster
Starting HTTPster v1.0.1 on port3333 from /home/doel/reactjs/ch6_flux_library
打开浏览器并检查控制台,点击标题后:

library_app 的截图
为了快速回顾我们书店应用到目前为止的流程:
默认的 index.html 页面在 localhost:3333 上提供静态内容(示例应用)。
index.html 页面内部调用 main.js,然后内部创建 React 类并在 <App /> React 组件(来自 src/js/components/app.js)中渲染内容。React 组件在具有 ID main 的 div 标签中渲染。
一旦我们点击 <App /> 组件(My First Flux App)的任何部分,一个 onClick 事件处理程序会触发 handler() 函数,该函数调用 AppActions.addBook (This is the book..Sherlock Holmes),在这里,AppActions 在 AppConstant 中。AddBook 是要调用的特定动作,带有 payload / item/ data (This is the book..Sherlock Holmes)。
一旦调用 AppActions.addBook 方法,它就会被分配给调度器的回调 handleViewAction,如下所示:
-
actionType:AppConstants.ADD_BOOK -
item:This is the book..Sherlock Holmes -
调度器的
handleViewAction方法传递动作(带有action_type和item)并在控制台记录输出,然后分发它。 -
在点击我的第一个 Flux 应用程序后,我们在
console.log中看到以下输出:action Object { actionType: "ADD_BOOK", item: "This is the book..Sherlock Holmes" } -
这只是以统一和预期的方式将 JS 对象(
item: "This is the book..Sherlock Holmes")传递给商店处理的一种方式。它简化了应用程序的数据流,并使得跟踪和调试更容易。
商店
Flux 商店可以与 MVC 中的模型相比较,尽管本质上它们并不相同。从类似的角度来看,它们与所有业务逻辑和计算都在 Flux 商店中发生是一样的。根据 Facebook 团队的说法,“商店管理许多对象的状态——它们不表示 ORM 模型那样的单个数据记录。它们也不与 Backbone 的集合相同。商店不仅管理 ORM 风格的集合,还管理应用程序中特定领域的应用状态。”
来源 en.wikipedia.org/wiki/Object-relational_mapping.
在计算机科学中,对象关系映射(ORM)是一种编程技术,用于在面向对象编程语言中将数据在不可兼容的类型系统之间进行转换。实际上,它创建了一个“虚拟对象数据库”,可以在编程语言中使用。在面向对象编程中,数据管理任务作用于面向对象(OO)对象,这些对象几乎总是非标量值。例如,考虑一个代表一个人以及零个或多个电话号码和零个或多个地址的地址簿条目。这可以通过面向对象的实现来建模,通过“Person 对象”具有属性/字段来保存条目包含的每个数据项:人的名字、电话号码列表和地址列表。电话号码列表本身将包含“PhoneNumber 对象”,依此类推。编程语言将地址簿条目视为单个对象(例如,可以通过包含指向对象的指针的单个变量来引用它)。可以与对象关联各种方法,例如返回首选电话号码、家庭地址等方法。
商店从调度器接收动作。根据注册的回调(与调度器相关),商店决定是否应该响应调度器分发的动作。应用程序外部的任何对象都不负责更改商店或视图内的值。因此,任何由动作带来的更改,都是基于注册的回调导致的数据更改,而不是任何设置方法。
由于 Flux 存储可以在没有任何外部干预的情况下自行更新,因此它减少了在 MVC 应用中通常发现的复杂性。Flux 存储控制其内部发生的事情,只有输入是通过调度器进行的。在 MVC 应用中,各种模型与各种视图之间的相互依赖可能导致不稳定和复杂的测试用例。
一个应用可以根据其功能拥有多个存储,但每个存储只处理一个域。存储既表现出模型集合的特征,也表现出逻辑域的单例模型特征。
以下是对存储功能的快速回顾:
-
存储注册自身到调度器中,通过回调函数。
-
业务逻辑的计算位于存储中,作为 JS 函数。
-
在调度器将操作从调度器发送到存储后,它们通过已注册的回调函数被识别。
-
通过状态更新在存储中执行操作。
-
JS 数组:
_library和_readingItems存储可用的书籍和读者想要阅读的内容。 -
EventEmitter是事件模块的一个类,它是 Node.js 核心库的一部分。在这个例子中,事件发射器功能是通过eventEmitter.on()方法完成的,其中第一个参数是事件,第二个参数是要添加的函数。因此,eventEmitter.on()方法只是注册函数。当调用emit()方法时,它将执行通过 on 方法注册的所有函数。 -
公共方法
getReadingList()和getLibrary()允许我们从_readingItems和_readingListJS 数组中获取计算后的数据。 -
app-stores.js代码中的dispatcherIndex用于存储调度器注册方法的返回值。 -
在调度器广播的情况下,switch 语句是决定要执行哪些操作的确定因素。如果采取了相关操作,则发出一个变更事件,并更新监听此事件的视图的状态。
以下是我们library_app的app_stores.js代码的代码,它包含了我们应用的所有业务逻辑和计算:
var AppDispatcher = require('../dispatchers/app-dispatchers');
var AppConstants = require('../constants/app-constants');
var assign = require('react/lib/Object.assign');
//eventEmitter allows the Stores to listen/broadcast changes to the
//Controller-Views/React-Components
var EventEmitter = require('events').EventEmitter;
var CHANGE_EVENT = 'change';
var _library = [];
for(var i=1; i<6; i++){
_library.push({
'id': 'Book_' + i,
'title':'Sherlock Holmes Story ' + i,
'description': 'Sherlock Series by Sir Arthur Conan Doyle'
});
}
var _readingItems = [];
function _removeItem(index){
_readingItems[index].inReadingList = false;
_readingItems.splice(index, 1);
}
function _increaseItem(index){
_readingItems[index].qty++;
}
function _decreaseItem(index){
if(_readingItems[index].qty>1){
_readingItems[index].qty--;
}
else {
_removeItem(index);
}
}
function _addItem(item){
if(!item.inReadingList){
item['qty'] = 1;
item['inReadingList'] = true;
_readingItems.push(item);
}
else {
_readingItems.forEach(function(cartItem, i){
if(cartItem.id===item.id){
_increaseItem(i);
}
});
}
}
var AppStore = assign(EventEmitter.prototype, {
emitChange: function(){
this.emit(CHANGE_EVENT)
},
addChangeListener: function(callback){
this.on(CHANGE_EVENT, callback)
},
removeChangeListener: function(callback){
this.removeListener(CHANGE_EVENT, callback)
},
getReadingList: function(){
return _readingItems
},
getLibrary: function(){
return _library
}
注意
dispatcherIndex用于存储调度器注册方法的返回值。在waitFor()方法的情况下使用dispatcherIndex,即当应用的一部分需要等待应用的其他部分更新时。
以下代码展示了dispatcherIndex:
dispatcherIndex: AppDispatcher.register(function(payload){
var action = payload.action;
switch(action.actionType){
case AppConstants.ADD_BOOK:
_addItem(payload.action.item);
break;
case AppConstants.DELETE_BOOK:
_removeItem(payload.action.index);
break;
case AppConstants.INC_BOOK_COUNT:
_increaseItem(payload.action.index);
break;
case AppConstants.DEC_BOOK:
_decreaseItem(payload.action.index);
break;
}
AppStore.emitChange();
return true;
})
})
module.exports = AppStore;
控制器-视图和视图
视图主要是 React 视图,它们本质上生成操作。控制器-视图监听我们的存储,以获取任何已广播的changeEvent。emitChange事件让我们的控制器-视图知道是否需要在视图的状态中执行任何更改。它们本质上都是 React 组件。在我们的代码中,我们有五个这样的 React 组件,如下所示:
-
app-addbooktoreadinglist.js -
app-booklist.js -
app.js -
app-readinglist.js -
app-removefromreadinglist.js
以下为app-booklist.js的代码:
var React = require('react');
var AppStore = require('../stores/app-stores');
var AddBookToReadingList = require('./app-addbooktoreadinglist')
function getLibrary(){
return {items: AppStore.getLibrary()}
}
var BookList = React.createClass({
getInitialState:function(){
return getLibrary()
},
render:function(){
var items = this.state.items.map(function(item){
return (
<tr key={item.id}>
<td>{item.title}</td>
<td><AddBookToReadingList item={item} /></td>
</tr>
);
})
return (
<table className="table table-hover">
{items}
</table>
)
}
});
module.exports = BookList;
以下是在AddBookToReadingList React 组件内部调用的代码:
var React = require('react');
var AppActions = require('../actions/app-actions');
//create a AddBookToLibrary component
var AddBookToReadingList = React.createClass({
handleClick: function(){
AppActions.addBook(this.props.item)
},
render:function(){
return <button onClick={this.handleClick}>I want to borrow </button>
}
});
module.exports = AddBookToReadingList;
最后,在app.js中添加了以下组件<Booklist \>。这主要是为了用户可以在ReadingList列表部分看到他们拥有的书籍:
var React = require('react');
var AppStore = require('../stores/app-stores.js');
var RemoveFromReadingList = require('./app-removefromreadinglist');
function readingItems(){
return {items: AppStore.getReadingList()}
}
var ReadingList = React.createClass({
getInitialState:function(){
return readingItems()
},
componentWillMount:function(){
AppStore.addChangeListener(this._onChange)
},
_onChange: function(){
this.setState(readingItems())
},
render:function(){
var total = 0;
var items = this.state.items.map(function(item, i){
return (
<tr key={i}>
<td><RemoveFromReadingList index={i} /></td>
<td>{item.title}</td>
<td>{item.qty}</td>
</tr>
);
})
return (
<table className="table table-hover">
<thead>
<tr>
<th></th>
<th>Book Name</th>
<th>Qty</th>
<th></th>
</tr>
</thead>
<tbody>
</table>
)
}
});
module.exports = ReadingList
重新审视代码
在每个 React 组件(readingList和bookList)中,getInitialState()分别使用存储的公共方法getReadingList()和getLibrary()初始化。
在组件的生命周期中,各种方法在精确的点被执行。
-
componentWillMount()是 React 的生命周期方法。它在客户端和服务器上都会被调用一次,在初始渲染发生之前立即执行。如果你在这个方法中调用setState,render()将看到更新的状态,并且即使状态发生变化,也只会执行一次:componentWillMount:function(){ AppStore.addChangeListener(this._onChange) }, _onChange: function(){ this.setState(readingItems()) } -
因此,
componentWillMount()正在监听addChangeListener(在AppStore存储中定义)。如果传递了_onChange参数,则当前对象(_this)会更新(setState)为新/更新的数据/有效载荷(readingItems)。 -
为了从阅读列表中删除项目,事件监听器(
handleClick)被卸载。
以下为app-removebookfromreadinglist.js`的代码:
var React = require('react');
var AppActions = require('../actions/app-actions');
//create a DeleteBookFromLibrary component
var DeleteBookFromReadingList = React.createClass({
handleClicr: function(){
AppActions.deleteBook(this.props.index)
},
render:function(){
return <button onClick={this.handleClicr}>Book Completed</button>
}
});
module.exports = DeleteBookFromReadingList;
以下为app.js的代码:
var React = require('react');
var BookList = require('./app-booklist');
var ReadingList = require('./app-readinglist');
//create a App component
var App = React.createClass({
render:function(){
return <div><h1>Book List</h1><BookList /><h1>Reading List</h1><ReadingList /></div>
}
});
module.exports = App
我们library_app Flux 应用程序的最终视图
点击按钮我想借阅时,相应的书籍将出现在我的阅读列表中。一旦我完成这本书,点击按钮书籍完成,从阅读列表中删除这本书。
以下是我们library_app应用程序的截图。

如何运行这个 Flux 应用将在构建和部署结构中稍后介绍。
以下是基于 Flux 的应用程序组件的详细信息:
-
动作
-
派发器(回调注册的注册表)
-
存储(与派发器注册的回调)
-
视图
-
控制器 视图
![重新审视代码]()
Flux 应用中的数据流
摘要
通过我们的libary_app应用程序,我们探讨了简单 Flux 应用中单向数据流的工作方式。用户可以在视图中看到书单。他们可以在阅读列表中添加书籍,因此动作(添加书籍)被传递到派发器。内部,派发器有与存储注册的回调。然后存储根据用户的动作添加/删除书籍,并计算业务逻辑,相应地重新渲染视图。
在下一章中,我们将介绍 React 的良好实践和模式。这包括开发可重用组件的实践,如何以更好的数据流结构化组件层次结构,以及如何验证组件的行为。在我们的应用中,我们将改进到目前为止开发的组件。
第七章:使你的组件可重用
到目前为止,我们已经深入探讨了 React 组件的生命周期、属性、状态以及与 React 0.1.13 和未来版本相关的 ECMAScript。在本章中,我们还将看到如何在 React 应用程序中编写可重用的组件/代码。React 中的这些可重用组件被称为混合。此外,我们将探讨如何验证 React 组件的属性。
本章将涵盖以下主题:
-
理解混合
-
ECMA6 中的高阶组件(因为混合在 ECMA6 中不受支持)
-
React 应用程序中的不同类型的验证
-
React 组件和应用程序架构的结构
理解混合
混合(可重用组件)通常是那些在多个地方使用的 React 组件,因此可以重用。通常,设计元素,如按钮、布局组件、表单字段或任何使用超过一次的代码逻辑/计算,都被提取到名为混合的代码中。因此,混合通过作为助手来帮助我们向现有的 React 组件中添加一些额外的功能。
注意
与前几章一样,index.html 的内容保持不变。只有相应的 js(包含 React 组件)的内容发生变化。
通过示例探索混合
在此示例中,我们为 window 全局对象设置了每 100 毫秒的间隔:
index.html 的内容:
<!DOCTYPE html>
<html>
<head>
<script src="img/react.min.js"></script>
<script src="img/JSXTransformer.js"></script>
<script src="img/react-dom.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div id="myReactContainer">
<script type="text/jsx", src="img/index.js"></script>
</div>
</body>
</html>
index.js 的内容:
//Defining the Mixin
. var ReactMixin = {
. getInitialState:function(){
. return {count:0};
. },
// componentWillMount, a lifecycle method, is added as a part of the Mixin.
. componentWillMount:function(){
console.log('Component will mount!');
},
increaseCountBy10: function(){
this.setState({count: this.state.count+10})
}
}
//This method displays text to display
var App = React.createClass({
render:function(){
return (
<div>
<Label txt="SetInterval increase by 10 in every 100ms" />
</div>
)
}
});
// React component (<Label />), called from the <App /> component.
var Label = React.createClass({
// Mixins are called using the keyword Mixin, followed by the Mixin name within an array.
mixins:[ReactMixin],
componentWillMount:function(){
//setting the interval to 100ms
interval = setInterval(this.increaseCountBy10,100);
},
//The function is called for the second time to update the interval every 100ms
componentWillUnMount:function(){
clearInterval(this.interval);
},
render:function(){
return <label>{this.props.txt} : {this.state.count}</label>
}
});
ReactDOM.render(<App />, document.getElementById('myReactContainer'));
注意
从应用程序的根目录运行 httpserver:
doel@doel-Vostro-3500:~/reactjs/ch7_mixins_validationProps/app1_mixin$ httpster
Starting HTTPster v1.0.1 on port 3333 from /home/doel/reactjs/ch7_mixins_validationProps/app1_mixin
在打开localhost:3333时,以下是对此代码的输出:

使用生命周期方法的混合的应用程序截图
执行代码的解释:
混合(Mixin)不过是一个可以被 React 组件后来重用的 JavaScript 对象。我们首先定义混合(Mixin)。
componentWillMount是一个生命周期方法,它作为混合的一部分被添加。稍后,当混合从 React 组件中调用时,可以在网页的开发者工具部分底部看到console.log的日志,以展示Component Will Mount。
我们添加了一个典型的 React 组件(<App />),它调用了<Label />组件。它是一个渲染函数,用于显示标签上呈现的文本。App 组件可以有多个 React 组件,这些组件将内部调用不同的 React 组件。
在下一个示例中,我们将看到这样的例子。
React 组件(<Label />)是从<App />组件中调用的。它使用了 React 混合(ReactMixin)。
行内混合:[ReactMixin],React 中的混合(Mixins)是通过关键字 Mixin 后跟混合名称(在本例中为 ReactMixin),在数组内调用。我们可以定义多个混合,作为 JavaScript 对象。所有这些独立的混合都可以从单个 React 组件(每个混合代表数组中的一个单独元素)中调用。
我们将在本章后面探索这样一个例子,其中包含多个混合。
然后我们添加setInterval()函数
-
setInterval()方法是 JavaScript 中的 window 函数。 -
它被声明为
window.setInterval(function, milliseconds)。 -
尽管这是一个基于 window 对象的方法,但不需要在 window 对象上调用
setInterval()方法,例如在之前提到的代码中。它可以不带 window 前缀调用。 -
第一个参数是执行的功能(
this.increaseCountBy10)。 -
第二个参数是每个函数执行之间的时间间隔,
this.increaseCountBy10。在这种情况下,间隔设置为100ms。
在之前提到的代码中,生命周期方法(componentWillMount)被第二次调用。第一次调用是在 Mixins 体内,它记录了日志中的Component Will Mount。
第二次调用是在 React 组件(<Label />)内部。由于第二次调用,setInterval()方法将值从0(最初将计数设置为0)增加到10,每次增加间隔为100毫秒。
注意
查看 Facebook 文档facebook.github.io/react/docs/reusable-components.html:
"Mixins 的一个优点是,如果一个组件正在使用多个 Mixins,并且几个 Mixins 定义了相同的生命周期方法(即几个 Mixins 想在组件销毁时进行一些清理),所有生命周期方法都将得到保证被调用。Mixins 上定义的方法按照 Mixins 列出的顺序运行,然后是组件上的方法调用。"
在下一节中,我们将看到 Mixins 的另一个示例:
Calling Multiple Mixins from a single React Component
现在,我们将看到另一个示例,其中多个 Mixins 将从单个 React 组件中调用。以下代码被声明:
首先,我们将声明两个 react Mixins:
var ReactMixin1= {
getDefaultProps: function () {
return {text1: "I am from first Mixin"};
}
};
var ReactMixin2 = {
getDefaultProps: function () {
return {text2: "I am from second Mixin"};
}
};
在代码的第二部分,我们将从 react 组件<App />中调用两个 React Mixins:
var App = React.createClass({
Mixins:[ReactMixin, ReactMixin2],
render: function () {
return (
<div>
<p>Mixin1: {this.props.text1} </p>
<p>Mixin2: {this.props.text2}</p>
</div>
);
}
});
ReactDOM.render(<App />, document.getElementById('myReactContainer'));
\\
直接从应用程序根目录执行 httpster 命令,就像之前一样,以查看两个 Mixins 的输出:

使用多个 Mixins 的应用程序截图
注意以下内容:
-
在 Mixins 中相同的属性名,例如,text,在这种情况下,将引发错误
-
不同 Mixins 中的相同方法名将引发错误
-
相同的生命周期方法可以在 Mixins 和 React 组件内部调用。这些生命周期方法的执行顺序是 Mixins,然后是 React 组件。
-
如果在多个 Mixins 中调用相同生命周期方法,则执行顺序是 Mixins 在数组中调用的顺序(从低到高索引)。
Mixins 中的高阶组件
在 ReactJS 中使用 ES6,Mixins 不再被支持。取而代之的是,他们引入了高阶组件。
这些高阶组件在 Relay 框架中被广泛使用,Relay 是由 Facebook 发布的一个完整的基于 React 的框架。高阶组件封装了子 UI 组件。因此,当这些组件被调用时,它们会首先执行其查询,从而渲染子 UI 组件。当查询传递时,数据会从子组件以 props 的形式传递给高阶组件。
验证
验证 是任何处理用户输入的应用程序的一个基本组成部分。在 ReactJS 中,库提供了一些验证,使得开发者能够验证接收到的数据。
在 React 应用程序中,数据大多以属性(props)的形式接收。各种验证器从 React.PropTypes 中导出。如果发生验证错误,它将出现在 JavaScript 控制台中。由于性能原因,只有开发模式下才会因为验证检查而发生此类错误。
查看 Facebook ReactJS 开发团队文档 facebook.github.io/react/docs/reusable-components.html#prop-validation。以下是一些验证器的示例:
React.createClass({
propTypes: {
// You can declare that a prop is a specific JS primitive. By default, these
// are all optional.
optionalArray: React.PropTypes.array,
optionalBool: React.PropTypes.bool,
optionalFunc: React.PropTypes.func,
optionalNumber: React.PropTypes.number,
optionalObject: React.PropTypes.object,
optionalString: React.PropTypes.string,
// Anything that can be rendered: numbers, strings, elements or an array
// (or fragment) containing these types.
optionalNode: React.PropTypes.node,
// A React element.
optionalElement: React.PropTypes.element,
// You can also declare that a prop is an instance of a class. This uses
// JS's instanceof operator.
optionalMessage: React.PropTypes.instanceOf(Message),
// You can ensure that your prop is limited to specific values by treating
// it as an enum.
optionalEnum: React.PropTypes.oneOf(['News', 'Photos']),
// An object that could be one of many types
optionalUnion: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
React.PropTypes.instanceOf(Message)
]),
// An array of a certain type
optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
// An object with property values of a certain type
optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
// An object taking on a particular shape
optionalObjectWithShape: React.PropTypes.shape({
color: React.PropTypes.string,
fontSize: React.PropTypes.number
}),
// You can chain any of the above with `isRequired` to make sure a warning
// is shown if the prop isn't provided.
requiredFunc: React.PropTypes.func.isRequired,
// A value of any data type
requiredAny: React.PropTypes.any.isRequired,
// You can also specify a custom validator. It should return an Error
// object if the validation fails. Don't `console.warn` or throw, as this
// won't work inside `oneOfType`.
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error('Validation failed!');
}
}
},
/* ... */
});
使用 isRequired 验证器的示例
index.html 页面。使用不同的 JS 页面来检查使用的不同版本的验证:
<!DOCTYPE html>
<html>
<head>
<script src="img/react.js"></script>
<script src="img/JSXTransformer.js"></script>
<script src="img/react-dom.js"></script>
<script type="text/jsx", src="img/index4.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div id="myReactContainer">
<script type="text/jsx", src="img/index.js"></script>
</div>
</body>
</html>
如验证器名称所示,isRequired 验证器确保 React 组件的属性始终存在。否则,它将在 JS 控制台中抛出错误。React.PropTypes.{foo} 属性是 JavaScript 函数,它们内部检查一个 prop 是否有效。当 prop 有效时,它将返回 null,但当 prop 无效时,它将返回一个错误。在 第四章 中,我们深入探讨了 ES6。在下一个示例中,我们将使用 ES6 语法:
"use strict"
class App extends React.Component {
render () {
return (
<div className="app">
<h1 ref="title" className="app__title"></h1>
<div ref="content" className="widget__content">{this.props.content}</div>
</div>
)
}
}
App.propTypes = {
title: React.PropTypes.string.isRequired,
content: React.PropTypes.node.isRequired
}
ReactDOM.render(<App content="I am learning react"/>,document.getElementById('myReactContainer'));
注意
在你的应用根目录下运行 httpster,以便在浏览器中的 localhost:3333 看到输出。
输出将如下所示:

应用程序截图——React 组件 prop 中的 isRequired 验证
从 ES6 视角来看,关于之前提到的代码的一些要点:
use strict 已经被用来选择性地启用 JavaScript 的一个受限版本。我们使用它是因为我们用 let 代替了 var。use strict 允许将组件置于一个 strict 运行环境中,并阻止某些操作被执行,同时抛出更多的异常。
let 声明变量,其作用域限制在使用的块、语句或表达式中。
详细信息请查看 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/。
使用自定义验证器的示例
以下是在使用自定义验证时通常使用的模板:
error = propTypespropName;
让我们通过一个自己的例子来演示,使用自定义错误消息,并使用这些验证之一,看看它在 JavaScript 控制台中的验证情况:
var ValidationApp = React.createClass({
propTypes: {
name: function(props, propName,componentName){
if(!(propName in props)) {
throw new Error("Property Name Missing ")
}
},
render:function(){
return <h1>{this.props.name}</h1>
}
});
ReactDOM.render(<ValidationApp />, document.getElementById('myReactContainer')); //missing prop name
注意
从你的应用程序根目录运行 httpster,以便在你的浏览器中的 localhost:3333 看到输出
以下代码的输出显示在本截图:

应用程序截图——React 组件属性中的自定义验证
我们可以在属性(名称)中添加更多的验证,如下所示:
var ValidationApp = React.createClass({
propTypes: {
name: function(props, propName,componentName){
if(!(propName in props)) {
throw new Error("Property Name Missing ")
}
if(props[propName].length < 7) {
throw new Error("Can you add a longer Property Name, more than 7chars")
}
}
},
render:function(){
return <h1>{this.props.name}</h1>
}
});
// ReactDOM.render(<ValidationApp />, document.getElementById('myReactContainer')); //missing prop name
ReactDOM.render(<ValidationApp name="react" />, document.getElementById('myReactContainer')); //prop length should be more than 7 chars
注意
从你的应用程序根目录运行 httpster,以便在你的浏览器中的 localhost:3333 看到输出
以下代码的输出如下所示:

应用程序截图——React 组件属性中的验证
因此,如果我们传递的名称属性超过七个字符,那么在预期的 JS 控制台中将不会有错误。
组件的结构
现在我们已经对 ReactJS 进行了相当多的探索,你心中可能会有关于如何架构一个 React 组件或更广泛地说是一个 React 应用程序的问题。还没有设定任何基本规则,这在基于 ReactJS 编写应用程序时是理想的。然而,如果我们深入研究 Facebook 文档团队提供的教程,超链接 facebook.github.io/react/docs/tutorial.html,我们将能够理解他们在编写这样的应用程序时所使用的底层方式。
让我们先探索一下组件通常是如何结构的:
-
组件
declaredData是在需要时从服务器获取的 [如果需要]。 -
组件的
propTypes已声明 [用于验证]。 -
组件生命周期方法 [
componentWillMount、componentDidMount、componentDidUpdate、componentWillUnmount等等] 已定义。 -
在每个生命周期方法中,这些方法的功能要么是声明的,要么是从另一个为特定任务明确定义的 JS 函数内部调用的。需要记住的是,之前提到的生命周期方法不是在应用程序中同时或任何情况下都必须使用。
-
必须存在于任何 React 组件中的渲染方法。因此,任何基于 React 的应用程序的结构方式因应用而异。尽管没有最佳方式,但像任何其他应用程序一样,建议将代码分块以遵循关注点分离。我们应该将 React 视图、组件和数据分开。一个组件目录可以根据需要调用其他子组件,从而提高代码的可读性和可测试性。
由于 React 是一个开源的 JavaScript 库,因此有各种开源网站和开发者每天都在为此库工作,以增强和调整库,以满足需求。
对于一个使用 ReactJS 库的应用程序,通常根据其功能将视图(React 视图)分开(例如,主页、管理页面和产品目录)。在每个视图的子文件夹中,你可以添加 test.js 文件,或者你可以将所有与测试相关的文件放在同一个 tests 文件夹下。如果你需要一些应该跨其他组件共享的 react 视图,你可以将这些相关文件放在 shared/lib 文件夹下。
摘要
在本章中,我们探讨了如何在 ReactJS 中开发可重用组件(在 ES6 实现之前,使用 Mixins)。我们还了解了高阶组件,这些组件在 ReactJS 的后续版本(从 0.13 版开始)中被使用,支持 ES6 但不支持 Mixins。验证是任何应用程序的一个基本组成部分,尤其是那些使用用户输入(即表单输入)的应用程序。我们探讨了 ReactJS 如何处理验证,以及我们如何使用自定义验证。我们还概述了 react 组件的结构。在下一章中,我们将处理 React 应用程序的测试。
第八章. 测试 React 组件
到目前为止,我们已经探讨了 React 的组件生命周期、属性、状态、验证以及与 React 0.1.13 和未来版本相关的 ECMAScript。在本章中,我们将探讨 JavaScript 和 ReactJS 相关内容的测试。首先,我们将通过不同的 JavaScript 测试框架来整体了解测试,以及如何运行测试,然后测试使用 ReactJS 库构建的视图。
本章将涵盖以下内容:
-
使用 Chai 和 Mocha 在 JavaScript 中进行测试
-
使用 ReactTestUtils 测试 React 组件
-
探索 Jest
-
使用 Expect、Mocha 和浅渲染测试基于 React 的应用
在测试 JavaScript 时,你可以混合匹配各种方法。让我们简要概述一下各种事物,如框架、断言库和测试工具。这里给出的列表并不全面,详细涵盖所有这些内容超出了本书的范围。
Mocha和Jasmine是测试框架。它们可以与各种测试断言库一起使用,如下所示:
-
should.js是一个断言库。它是框架无关的,并且从 IE9 及以上版本工作。有关库的详细信息,请参阅www.npmjs.com/package/should。 -
chaijs也是一个断言库,其中我们添加插件。它也适用于测试框架。有关库的详细信息,请在线查阅chaijs.com/karma。它是一个 JavaScript 测试工具,可以测试浏览器中的 JavaScript 代码。它是框架无关的(可以用于运行 Mocha、Jasmine、Qunit 等)。详细信息请参阅www.npmjs.com/package/karma。
应当记住,karma 既不是像 Jasmine 或 Mocha 这样的 JavaScript 框架,也不是像chaijs或should.js这样的断言库。因此,我们应该根据需要使用断言库和框架,以及 karma 一起启动 HTTP 服务器,以便我们可以在浏览器中测试 JS 代码。
Jest也是 Jasmine 框架上的一个框架。Facebook 开发者团队建议使用 Jest 来测试基于 React 的应用。根据 Jest 网站(facebook.github.io/jest/),以下是使用 Jest 而不是 vanilla jasmine 进行测试的一些优点:
-
Jest 在 Jasmine 之上提供了多个层级
-
它会自动搜索并找到要执行的测试
-
在你运行测试时,它会为你模拟依赖
-
它并行运行测试,因此可以更快地完成执行
-
它允许你同步测试异步代码
-
它允许你通过 jsdom 的模拟 DOM 实现,在命令行上运行测试
使用 Chai 和 Mocha 在 JavaScript 中进行测试
如前所述,为了编写 React 代码的测试用例,我们将安装一些测试库来运行测试和编写断言。让我们了解 Chai 断言库和 Mocha 测试框架的设置。我们需要使用 npm 安装这些库。
在终端输入:
npm i -D mocha chai
注意
install 简称:i
devDependencies 简称:D(该包仅在开发环境中安装)
在通过之前提到的命令安装了 Chai 和 Mocha 库之后,它们可以在 node_modules 目录下找到。
我们需要在 package.json 文件中添加 Mocha 和 Chai 的条目。
package.json 代码
{
"name": "JSApp",
"version": "1.0.0",
"description": "Get random numbers",
"main": "index.js",
"scripts": {
"test": "mocha test.js"
},
"devDependencies": {
"chai": "3.2.0",
"mocha": "2.2.5"
}
}
根据 docs.nodejitsu.com/articles/getting-started/npm/what-is-the-file-package-json
所有 npm 包都包含一个名为 package.json 的文件。此文件通常位于项目根目录。此文件包含与项目相关的所有元数据。package.json 文件用于向 npm 提供信息,从而使其能够识别项目以及有效地处理项目的依赖项。
-
name:这表示应用程序的名称。 -
version:这是应用程序的版本。 -
description:这是应用程序的一般描述。 -
main:这是主 JavaScript 文件,它可能内部调用其他 JS 文件。在这个例子中,是index.js文件。 -
scripts:这是在调用npm start时要执行的脚本。它应该执行测试(mochatest.js文件)。 -
devDependencies:这些是在与package.json相同的目录中安装的包,除非传递了–production标志。除非传递了–dev选项,否则这些包不会安装在任何其他目录。
添加一个 test.js 文件。为了检查设置是否正常工作,我们添加了一个简单的单个测试断言。
Test.js file code
var expect = require('chai').expect
, name = 'my Name';
var random = require('./index');
describe('random', function() {
it('should work!', function() {
expect(false).to.be.false;
});
it ('return my Name', function() {
expect(name).to.be.a('string');
expect(name).to.equal('my Name');
expect(name).to.have.length(7);
})
});
注意
assertions 是从 Chai 调用的。
describe 是从 Mocha 框架中调用来描述测试的。
现在我们运行测试,从应用程序的根目录在终端中,如下所示:
npm test

使用 Mocha 和 Chai 设置的控制台截图
使用 ReactTestUtils 进行测试
ReactTestUtils 用于测试基于 React 的组件。它可以模拟 ReactJS 支持的所有基于 JavaScript 的事件。文档可以在 Facebook 开发者网站上找到(facebook.github.io/react/docs/test-utils.html)。
以下是对模拟函数的代码:
Simulate.{eventName}(
DOMElement element,
[object eventData]
)
安装 React 和 JSX
如前所述,在安装 Chai 和 mocha 时,我们在此安装 React 和 JSX 特定的测试工具(ReactTestUtils),以便简化我们的任务。让我们借助一些基于 React 的组件并刺激它们来测试行为和功能。
以下是一个此类代码的示例。
我们需要在终端使用以下代码通过npm安装jest包:
sudo npm install jest-cli –save-dev
在需要安装 node 包的机器/服务器上,需要 sudo/root 访问权限。这特别需要,因为 node 安装的目录。我们可以使用以下命令检查已安装的目录:
npm config get prefix
根据这里的截图,它安装在/usr目录中,权限设置为 root。因此,我们需要使用sudo选项安装npm包。

/usr目录文件所有者/权限的控制台截图。
另一种方法是设置/usr目录的权限为用户,该用户可以拥有和修改目录中的文件:
sudo chown -R $(whoami) $(npm config get prefix)/{lib/node_modules,bin,share}
让我们尝试采用测试驱动开发(TDD)的方法,即我们将创建一个失败的测试用例,然后根据实际代码来通过测试。
创建一个 JS 文件,该文件将用hi问候任何名字:
// greeting.js
module.exports = greetings;
现在,让我们在名为__test__的目录中创建一个test文件:
// __tests__/greeting-test.js
jest.dontMock('../greetings');
//executed when the test runs
describe('greetings', function() {
it('greets the name', function() {
var greet = require('../greetings');
expect(greet("react")).toBe("hi react");
});
});
让我们回顾一下之前提到的代码中的 jest 属性:
-
jest.dontMock在这里被明确提及,因为 jest 默认会模拟一切。因此,为了测试实际代码而不进行模拟,我们需要要求 jest 不要模拟需要测试的代码(greetings.js)。 -
describe('greetings', function())每个 describe 块是测试套件,当运行测试时(npm test/jest)将被执行。一个 describe 块可以有多个测试用例。 -
it('greets the name', function(), 在 describe 块内阻塞实际的测试规格/用例。
为了在_test__目录中执行测试,我们需要有一个包含以下条目的package.json文件:
注意
在下一章中,我们将更详细地介绍打包。
下面是package.json文件的代码:
{
"dependencies": {
"react": "~0.14.0",
"react-dom": "~0.14.0"
},
"devDependencies": {
"jest-cli": "⁰.8.2",
"react-addons-test-utils": "~0.14.0"
},
"scripts": {
"test": "jest"
},
"jest": {
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-addons-test-utils",
"<rootDir>/node_modules/fbjs"
]
}
}
让我们对package.json中的这段代码进行快速回顾。
一切准备就绪后,我们可以在终端使用以下命令运行测试:
npm test
输出如下所示:

使用 jest 的 TDD 控制台截图,显示失败测试。
现在,让我们添加代码,以便用名字问候名字,并且测试通过:
// greeting.js
function greetings(name) {
return "hi "+name;
}
module.exports = greetings;
现在,当我们执行测试时,我们将看到一个通过测试用例:

使用 npm test 的 TDD 控制台截图,显示通过测试。
执行测试的另一种方法是安装jest并通过终端调用 jest 来执行它们:
sudo npm install -g jest-cli
输出如下所示:

使用 jest 的 TDD 控制台截图,显示通过测试。
因此,我们可以看到使用npm test/jest中的任何一个命令,我们都会得到相同的输出。
下面是使用 Mocha、expect、ReactTestUtils 和 Babel 的 jest 测试套件的典型示例。
让我们看看package.json的典型示例,它使用以下内容:
-
Mocha 作为测试框架
-
Expect 作为一个断言库
-
ReactTestUtils 用于测试基于 React 的 JavaScript 组件
-
Babel 作为转编译器,将 ES6 代码转换为当前兼容(ES5)的 JavaScript 代码。
package.json文件的示例:
"scripts": {
"test": "mocha './src/**/*.test.js' --compilers js:babel-core/register",
},
"devDependencies": {
"babel-core": "6.1.4",
"babel-loader": "6.1.0",
"babel-preset-es2015": "6.1.4",
"babel-preset-react": "6.1.4",
"babel-preset-stage-2": "6.1.2",
"mocha": "2.3.3",
"react-addons-test-utils": "0.14.3",
}
}
如前例所示,在脚本对象中,我们保留测试文件,并且所有测试文件都遵循以.test.js扩展名结尾的约定。测试文件可以使用任何扩展名。对于从 ES6 代码编译到浏览器兼容的 JS 代码,脚本中添加了–compiler标签。
安装以下所有包,如package.json中所述:
npm install babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-2 react-addons-test-utilsBabel being the transpiler, we need to add the following entry to enable the import (reserver keyword) in the following .babelrc file:
{
"presets": ["es2015"]
}
这里是转译器的定义。来源 en.wikipedia.org/wiki/Source-to-source_compiler
"源到源编译器、转编译器或转译器是一种编译器,它接受用一种编程语言编写的程序源代码作为输入,并生成另一种编程语言的等效源代码。"
.babelrc文件包含所有 Babel API 选项。以下是与测试套件设置相关的应用程序文件结构截图。详细信息可以在 Babel 文档中找到,网址为babeljs.io/docs/usage/babelrc/。

展示典型 JS 应用程序目录结构的截图,包括 test、node_modules、package.json 和.babelrc
使用与之前相同的greetings.js文件,但在greetings.test.js和index.test.js文件中使用新的 ES6 语法进行测试,让我们测试测试套件。
代码 test/greetings.test.js(使用 ES6 语法)
import expect from 'expect';
describe('greetings', () => {
it('greets the name', () => {
var greet = require('../greetings');
expect(greet("react")).toBe("hi react");
});
});
代码 test/index.test.js(使用 ES6 语法)
import expect from 'expect';
describe('setup',() => {
it('testing the setup is working', () => {
expect(true).toEqual(true);
});
});

展示使用 ES6 语法、mocha 和 babel 进行测试的截图
使用 ES6 语法和 mocha 测试框架、expect 断言库执行此测试文件,并在经过 Babel 转译后产生了与之前相同的结果。
使用浅渲染进行测试
浅渲染是在测试 React 组件时使用的一种方法,其中组件是“单层深”。这样的浅渲染测试组件具有关于返回内容的render方法的事实。这些组件没有附加子组件,并且不需要 DOM。
因此,在测试使用浅渲染方法时,应记住,任何具有 DOM 更改的父组件或任何已更改的子组件的更改可能需要重写测试。
让我们通过一些代码来探索这个问题。在以下示例中,我们将创建一个 React 组件(GreetingComponent),其中render方法将返回一个包含两个子元素(h2和span元素)的div。
greeting.js的代码:
// greeting.js
import React from 'react';
const { div, h2, span} = React.DOM;
export default React.createClass({
displayName: 'GreetingComponent',
render(){
return(
div({classname: 'Greeting'},
h2({classname: "heading2"}, "Hi"),
span({classname: "like"},"ReactJs")
)
);
}
});
让我们使用浅渲染方法为这个 React 代码编写测试。
test/greeting.test.js 的代码
// Importing the necessary libraries and JavaScript code to be tested
import expect from 'expect';
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import GreetingComponent from '../greetings.js';
describe('GreetingComponent', () => {
it('should greet with the name', () => {
// Creating a shallow rendered object and stored within renderer
const renderer = TestUtils.createRenderer();
/*creating the react element (GreetingComponent, declared in the greeting.js code). This might be comparable to the "place" where the component to be tested is rendered. This component can respond to events and update itself
*/
renderer.render(React.createElement(GreetingComponent));
/* method is called on the renderer (TestUtils.createRenderer()) and stored within output. We can inspect this output in the console */
const output = renderer.getRenderOutput();
console.log(output);
expect(output.type).toBe('div');
输出值将在控制台中打印。基于此,我们可以看到相关 react 组件的不同层次和值。以下是从console.log输出的内容(输出)

展示在控制台中渲染输出()方法的截图。
让我们深入一层,检查以下值:const output = renderer.getRenderOutput().props.children。
因此,我们可以看到GreetingComponent React div元素的确切两个子元素及其类型和值:

展示在控制台中渲染输出()方法的子元素的截图。
根据输出,我们可以测试div元素的子元素(h2和span)如下:
Code of __test__/greeting.test.js
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import GreetingComponent from '../greetings.js';
describe('GreetingComponent', () => {
it('should greet with the greeting Hi', () => {
const renderer = TestUtils.createRenderer();
renderer.render(React.createElement(GreetingComponent));
const output = renderer.getRenderOutput();
console.log(output);
expect(output.type).toBe('div');
expect(output.props.children[0].type).toBe('h2');
expect(output.props.children[0].props.classname).toBe('heading2');
expect(output.props.children[0].props.children).toBe('Hi');
});
it('should return the like as ReactJs', () => {
const renderer = TestUtils.createRenderer();
renderer.render(React.createElement(GreetingComponent));
const output = renderer.getRenderOutput();
console.log(output);
expect(output.type).toBe('div');
expect(output.props.children[1].type).toBe('span');
expect(output.props.children[1].props.classname).toBe('like');
expect(output.props.children[1].props.children).toBe('ReactJs');
});
});
我们可以看到两个it块之间有若干行共同的代码。因此,我们可以将这些共同代码分离出来,并如以下所示进行重构:
// __tests__/sum-test.js
//jest.dontMock('../greetings.js');
import expect from 'expect';
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import GreetingComponent from '../greetings.js';
describe('GreetingComponent', () => {
describe('Common code', () => {
const renderer = TestUtils.createRenderer();
renderer.render(React.createElement(GreetingComponent));
const output = renderer.getRenderOutput();
// console.log(renderer);
console.log("From Common Code");
console.log(output);
it('should greet with the greeting Hi', () => {
// console.log(renderer);
console.log("h2 component");
console.log(output);
expect(output.props.children[0].type).toBe('h2');
expect(output.props.children[0].props.classname).toBe('heading2');
expect(output.props.children[0].props.children).toBe('Hi');
});
it('should return the like as ReactJs', () => {
// console.log(renderer);
console.log("span component");
console.log(output);
expect(output.props.children[1].type).toBe('span');
expect(output.props.children[1].props.classname).toBe('like');
expect(output.props.children[1].props.children).toBe('ReactJs');
});
});
});
在执行代码时,我们可以使用以下命令在文件中获取输出:
npm test > test_output.txt
以下是在test_output.txt文件中的输出。您可以播放并检查 React 元素的各个不同属性。每个属性的解释超出了本书的范围。但我们可以看到,所有 React 组件不过是 JavaScript 对象。

摘要
我们看到了如何在基于 React 的应用程序中测试不同的组件以及 JavaScript 本身。为了测试 JavaScript 代码,我们使用了 chai 和 expect 作为断言库,jasmine 和 jest 作为测试框架。为了测试 React 应用程序,我们使用了 ReactTestUtils 和浅渲染。在下一章中,你将学习关于 React 应用程序的部署过程。我们将更深入地探讨package.json,这是我们本章中提到的。
第九章.为部署准备代码
在学习 ReactJS 基础和 flux 之后,我们几乎接近了这本书的结尾。在开发任何应用程序后,我们面临的最关键部分是将应用程序提供给外部世界,即部署应用程序。将代码保存在源代码控制仓库(如 GitHub 或 Bitbucket)中,并使用 Git 进行代码版本控制是一个好习惯。这些在团队合作和必要时检索任何代码时都有帮助。如何设置前面提到的内容的解释超出了本书的范围,但有许多资源可供参考。
在本章中,我们将探讨以下主题:
-
Webpack 简介
-
使用 Webpack 和 Gulp 部署 React 应用程序的方法
-
用于 browserify 的配置选项
-
安装简单的 Web 服务器
Webpack 简介
Webpack是一个模块打包器,用于部署基于 JavaScript 的应用程序。它将输入作为具有依赖关系的模块,然后将其输出为静态资源。
从 Webpack 文档网站(webpack.github.io/docs/what-is-webpack.html#how-is-webpack-different),以下图像解释了相同的内容。

构建简单的 React 应用程序
如前几章所述,让我们构建一个简单的基于 React 的应用程序,我们将集成 Webpack 并在之后部署它。
从终端安装vis npm包,如下所示:
sudo npm install babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-2
npm -g install httpster
注意
httpster: 这是一个简单的 http 服务器,用于运行静态内容。在 Chrome 浏览器中,由于 X-origin 错误,index.html文件有时无法渲染。因此,从您的应用程序目录运行此 Web 服务器将更容易测试您的应用程序。只需运行命令httpster。
默认情况下,服务器在端口3333上运行,因此浏览器中的localhost:3333应该渲染您的应用程序的index.html页面。
我们创建了以下文件:
-
src/bundle.js: 这是在 Webpack 将代码转换为普通 JS 并执行任何其他文件转换后,Webpack 将其输出到的地方。该文件的详细信息将在后面的部分讨论。 -
index.html: 应用程序着陆页。 -
index.js: 基于 React 的组件。 -
.babelrc: 在这里声明了 babel 的预设和环境。 -
node_modules: 存储已安装的npm包。 -
Webpack.config.js: 在这里存在 Webpack 相关的配置。
以下是一个控制台截图,显示了使用 Webpack 的应用程序目录结构:

查看简单的 React 应用代码示例:
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="img/react.js"></script>
<script src="img/react-dom.js"></script>
<script src="img/JSXTransformer.js"></script>
<title>React App with Webpack</title>
</head>
<body>
<div id="app"></div>
<script type = "text/jsx" src="img/index.js"></script>
</body>
</html>
index.js:
"use strict";
class App extends React.Component {
render() {
return <div>Hello </div>;
}
}
ReactDOM.render(<App />, document.getElementById('app'));
.babelrc:
{
"presets": ["es2015", "react"]
}
设置 Webpack
现在我们已经了解了 Webpack 是什么,让我们安装和配置它,以便我们可以在 React 应用程序中使用它,如后面所述。
在你的终端中,输入以下内容:
sudo npm install -g webpack
注意
-g 选项将 Webpack 库全局安装到你的计算机上。
如你所见,在下面的截图中,有许多依赖包,这些包在安装 Webpack 时也会被安装:

显示 webpack 包安装及其所有依赖项的控制台截图
在安装 Webpack 之后,我们将创建一个 webpack-config.js 文件,其中包含以下条目:
// The declaration of the object having all the Webpack-related
configuration details.
module.exports = {
//entry point of the application
entry: "./app/components/index.js",
/* In this bundle.js file,Webpack will have the output after transpilation of the code from index.js (ES6 to ES5) & combining all the components' and it's children js files are present.
*/
output: {
filename: "src/bundle.js"
},
module: {
// Loading the test loader, it is used to transform any JSX code in the tests into plain JavaScript code.
loaders: [
{
// All the packages which are installed within the node_modules directories are to be excluded.
test: /\.jsx?$/,
exclude: /(node_modules)/,
//specifying which one to use
loader: 'babel',
query: {
presets: ['react', 'es2015']
}
}
]
}
}
让我们解释前面的代码。
我们从应用程序的入口点开始。由于基于 React 的应用程序通常有很多组件,为所有这些组件有一个共同的入口点将更容易管理,并且对于结构良好的模块化应用来说很重要。
然后我们将输出指向一个名为 bundle.js 的文件,并将所有组件及其子组件合并。
在加载测试加载器后,我们说明在 node_modules 目录中要排除哪些包。
然后我们使用加载器,指定使用哪一个。预设加载器在将 ES6 代码转换为当前浏览器兼容代码时执行 Babel 所做的所有转换。
现在我们将在我们的终端中运行 Webpack 命令,
sudo webpack -w -v
-
使用
sudo,因为我们需要 sudo/root 权限来执行 Webpack 命令,或者我们需要更改特定目录的所有权/权限。-w选项确保监视任何发生变化的文件。它将监视源文件的变化,当发生变化时,捆绑包将被重新编译。(来源:webpack.github.io/docs/webpack-dev-server.html)。-v选项给出详细输出。 -
webpack --help:此命令给出所有选项及其对应含义的输出,这些选项可以作为参数传递。

在终端上执行 Webpack 后的控制台截图
因此,所有代码的转换和转换都在 src/bundle.js 输出文件中。
从前面提到的应用中典型的 bundle.js 文件输出:
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId])
/******/ return installedModules[moduleId].exports;
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ exports: {},
/******/ id: moduleId,
/******/ loaded: false
/******/ };
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/ // Flag the module as loaded
/******/ module.loaded = true;
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/ // Load entry module and return exports
/******/ return __webpack_require__(0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports) {
"use strict";
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }
function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
var App = function (_React$Component) {
_inherits(App, _React$Component);
function App() {
_classCallCheck(this, App);
return _possibleConstructorReturn(this, Object.getPrototypeOf(App).apply(this, arguments));
}
_createClass(App, [{
key: "render",
value: function render() {
return React.createElement(
"div",
null,
"Hello "
);
}
}]);
return App;
}(React.Component);
ReactDOM.render(React.createElement(App, null), document.getElementById('app'));
/***/ }
/******/ ]);
请参阅 Webpack 文档webpack.github.io/docs/webpack-dev-server.html。
新生成的 bundle.js 存储在内存中,位置是 publicPath 中指定的相对路径。
例如,使用前面的配置,捆绑包将在 localhost:8080/assets/bundle.js 可用。
为了加载捆绑文件,我们在 build 文件夹中创建一个 html 文件(通常命名为 index.html 文件),从该文件夹提供静态文件:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script src="img/bundle.js"></script>
</body>
</html>
默认情况下,应用程序在 localhost:8080/ 运行以启动你的应用。例如,使用前面提到的配置(带有 publicPath),访问 localhost:8080/assets/。
Webpack 的优势
在使用 Webpack 作为另一个打包器时,以下是一些重要的优势:
-
代码拆分:基于代码大小,它有助于模块化代码块,并在需要时加载这些模块。你可以在你的代码中定义拆分点,根据这些拆分点,将使用代码块。因此,它有助于加快页面加载速度和性能提升。
-
加载器:如前所述的图片所示,在左侧,你可以看到除了 JavaScript 和
.less代替.css之外,还有各种其他格式,如coffescripts/jsx。因此,这些加载器(npm包)被用来将这些其他格式转换为接受的标准化格式,这使得开发者能够更容易地将代码编码成他们想要的任何格式。在我们之前看到的基于 React 的应用程序中,JSX 格式被广泛使用。因此,这些加载器将非常有用。 -
聪明的解析:它有助于解析大多数第三方库,并处理 CommonJS 和 AMD 中广泛使用的样式。
-
插件系统:如果你想扩展 Webpack 以在构建过程中创建一个步骤,你可以创建一个使用回调到 Webpack 引用点的插件,在那里你想调用它。
Gulp 简介
现在我们已经看到了一个模块打包器,让我们看看 Gulp 能为我们做什么。Gulp 是一个用于编译和压缩 JS/资源的构建工具,并且它可以在浏览器上执行实时重新加载。Gulp 文件基本上是一个包含 Gulp 应该执行的一组指令的文件。该文件可以有一个默认任务或几个其他任务,可以从一个任务调用另一个任务。
安装 Gulp 和创建 Gulp 文件
让我们安装gulp并配置它与我们的现有应用程序:
npm install -g gulp (for globally installing gulp)
npm install gulp –save-dev (as a developer dependancy)
接下来,在应用程序目录的根目录下创建一个简单的gulpfile.js文件:
var gulp = require('gulp');
gulp.task('default', function() {
// tasks goes here
});
让我们从终端执行命令:

执行 gulp 命令后的控制台截图
然后,我们为 Gulp 相关的任务安装了一些其他包。我们在package.json文件中添加这些包,并运行npm install来安装这些包:
Package.json
{
"name": "app1",
"version": "1.0.0",
"description": "ReactApp",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Doel Sengupta",
"license": "ISC",
"dependencies": {
"react": "⁰.14.3",
"react-dom": "⁰.14.3"
},
"devDependencies": {
"babel-core": "⁶.3.13",
"babel-loader": "⁶.2.0",
"babel-preset-es2015": "⁶.3.13",
"babel-preset-react": "⁶.3.13",
"browser-sync": "².9.6",
"gulp": "³.9.0",
"gulp-babel": "⁶.1.1",
"gulp-concat": "².6.0",
"gulp-eslint": "¹.1.1",
"gulp-filter": "³.0.1",
"gulp-notify": "².2.0",
}
}
注意
Gulp 中的一些关键事项:
-
初始时,我们需要引入所有在执行任务时所需的 gulp 和相关 gulp 插件/包。
-
使用
gulp.task声明 gulp 任务。 -
.pipe命令用于流式传输需要处理的数据。这个命令用于连接,结果是得到输出。
现在如果我们向 Gulp 文件添加一些任务,它将看起来像以下这样:
var gulp = require('gulp');
var babel = require('gulp-babel');
var browserSync = require('browser-sync');
var concat = require('gulp-concat');
var eslinting = require('gulp-eslint');
var notify = require('gulp-notify');
var reload = browserSync.reload;
var jsFiles = {
vendor: [
],
source: [
'*.js',
'*.jsx',
]
};
// Lint JS/JSX files
gulp.task('eslinting', function() {
return gulp.src(jsFiles.source)
.pipe(eslinting({
baseConfig: {
"ecmaFeatures": {
"jsx": true
}
}
}))
.pipe(eslinting.format())
.pipe(eslinting.failAfterError());
});
// Watch JS/JSX files
gulp.task('watch', function() {
gulp.watch('*.{js,jsx,html}').on("change",reload);
});
// BrowserSync
gulp.task('browsersync', function() {
browserSync({
server: {
baseDir: './'
},
open: false,
online: false,
notify: false,
});
});
gulp.task('default', ['eslinting', 'browsersync', 'watch']);
让我们分析 前面的代码:
-
之前声明了四个 gulp 任务,并已突出显示。默认强制任务在最后一行突出显示时内部调用三个任务。在 Gulp 术语中,调用其他任务的任何任务都被提及为父任务的数组元素。
-
gulp.task ('eslinting', function): 这个任务用于检查js&jsx文件中的任何代码问题。为了使用gulp-eslint插件检查jsx,设置了ecmaFeature: {"jsx": true}选项。 -
gulp.watch:正如其名所示,这个任务会监视 JS 文件中的任何变化,并在之后重新编译这些文件。如果不需要监视任何文件,我们需要将read: false传递给options对象。在js/jsx文件发生变化后,我们可以调用browserSync.reload或添加任务以重新加载您的 HTML 页面。 -
browsersync:这个插件不是官方为 gulp 设计的;尽管它可以与任何 gulp 任务一起工作。js/jsx文件中的任何变化都会同步到浏览器。
在终端中从应用程序的根目录执行 gulp 命令后,我们应该能够在终端中看到这样的输出。请参阅以下图片:

执行带有任务 gulp 命令后的控制台截图
让我们检查一下 gulp-eslint 的工作方式。在 index.js 文件的开始处添加一行,例如 require 'react'。
require "react";
var ReactApp1 = React.createClass({
render: function(){
return (
<div>
Hello World
</div>
)
}
});
ReactDOM.render(<ReactApp1 />, document.getElementById('app'));

执行带有包含错误 eslint 任务的 gulp 命令后的控制台截图
正如我们所知,var React = require("react") 应该是正确引入 React 包的方式。
对于 Gulp 插件来说,有很多,除了在前面提到的示例中提到的那些,它们在我们的日常应用开发中也非常有帮助。请随意查看 Gulp 文档以及他们网站上的相关插件 gulpjs.com/。
摘要
在本章中,我们了解到如何使用 Webpack 部署我们的 React 应用程序,以及 Gulp 如何通过自动化任务、压缩我们的资源(JS、JSX、CSS、SASS、图片等)、监视这些文件的变化以及内置在浏览器中的实时重新加载来简化我们的生活。在 第十章,接下来是什么,我们将探讨一些 ReactJS 的高级概念。
第十章。接下来是什么
到目前为止,我们已经从零开始构建基于 React 的 JavaScript 应用程序,将其与 Facebook Graph API 集成,深入探讨组件的各个阶段,生命周期,验证,测试和部署应用程序的所有主题。有了这些,我们已经到达了这本书的结尾,但让我们探索 React 世界的一些高级主题。
在本章中,我们将简要探讨以下主题,因为在一章中不可能详细涵盖所有内容:
-
React 中的 Ajax
-
React Router
-
服务器端渲染
-
同构应用程序
-
热重载
-
Redux React
-
Relay 和 GraphQL
-
React Native
React 中的 Ajax
就像在任何其他应用程序中一样,在基于 React 的应用程序中,可以使用 Ajax 异步获取数据。根据 Facebook 关于使用 Ajax 从服务器加载数据的文档(facebook.github.io/react/tips/initial-ajax.html),你需要记住以下提到的关键点:
-
在你的 HTML 中包含 jQuery 库:
<script src="img/"></script>由于没有单独的 Ajax 库可以从 jQuery 中引用,因此在 React 应用程序中使用 Ajax 时,必须使用整个 jQuery,这会导致下载 jQuery 的压缩版本,从而大大减少加载时间。
在
componentDidMount的生命周期阶段加载数据。此方法在客户端的生命周期中只发生一次,并且在这个阶段可以访问任何子组件。建议在此阶段执行任何外部 js 库或使用 Ajax 加载数据。 -
isMounted方法用于检查组件是否已挂载在 DOM 中。尽管这个方法在setState()之前与 Ajax 一起使用,但在使用 ES6 语法(使用React.component)时,这个方法将被弃用,并且可能在未来的 React 版本中完全删除。请参阅facebook.github.io/react/docs/component-api.html。
下面是index.html的代码:
<!DOCTYPE html>
<html>
<head>
<script src="img/react.min.js"></script>
<script src="img/JSXTransformer.js"></script>
<script src="img/react-dom.js"></script>
<script src="img/"></script>
<meta charset="utf-8">
<title>JS Bin</title>
</head>
<body>
<div id="app">
<script type="text/jsx", src="img/index.js"></script>
</div>
</body>
</html>
以下为index.js的代码:
var GithubUser = React.createClass({
getInitialState: function() {
return {
username: '',
user_url: ''
};
},
componentDidMount: function() {
$.get(this.props.source, function(result) {
console.log(result);
var user = result;
if (this.isMounted()) {
this.setState({
username: user.login,
user_url: user.html_url
});
}
}.bind(this));
},
render: function() {
return (
<div>
{this.state.username}'s last gist is
<a href={this.state.user_url}>here</a>.
</div>
);
}
});
ReactDOM.render(
<User source="https://api.github.com/users/doel" />,
document.getElementById('app')
);

使用 Ajax 的 React (facebook.github.io/react/tips/initial-ajax.html)
React Router
React Router 是基于 React 库的库,它有助于轻松快速地路由具有多个页面的应用程序。尽管在没有 React-router 的情况下可能构建这样的应用程序流程,但随着应用程序的增长,拥有许多页面,识别页面之间的父子关系变得繁琐。这就是 React-router 发挥作用的地方,它确定了如何构建嵌套 UI。
来源:
服务器端渲染
ReactJS 的服务器端渲染是通过 JavaScript(NodeJS 或 io.js)完成的。这种方法实际上在服务器端预先渲染 React 组件的初始状态。因此,它有助于快速渲染网页,因为用户可以在客户端的 JavaScript 完成加载之前看到网页。
然而,这种渲染方式不适用于那些需要从服务器向客户端传输大量数据的应用程序,这可能会减慢页面加载速度。在这种情况下,我们可以使用分页或分块批量加载数据,这样不会减慢页面加载速度,但可以在特定的时间间隔内从服务器端获取。
React API 的以下两种方法提供了服务器端渲染的骨干(facebook.github.io/react/docs/top-level-api.html)。
ReactDOMServer
react-dom/server 包允许您在服务器上渲染您的组件。
ReactDOMServer.renderToString 方法返回一个字符串。它生成两个额外的 DOM 属性—data-React-id 和 data-React-checksum—这些属性由 ReactJS 库内部使用。
此方法将 ReactElement 的元素渲染到视图的初始 HTML 中,并返回一个 HTML 字符串。
应仅在服务器端渲染和服务器端使用。
在初始页面加载期间,从服务器向客户端发送此方法会导致页面加载速度更快,并启用针对 搜索引擎优化(SEO)的网页抓取。
当 ReactDOM.render() 被调用到任何先前节点时,React 将在这些节点上附加事件处理器,从而加快页面加载速度。
语法是:
string renderToString(ReactElement element)
ReactDOMServer.renderToStaticMarkup 方法与 renderToString 类似。
它主要用于生成静态页面。
语法是:
string renderToStaticMarkup(ReactElement element)
为了说明 ReactJS 服务器端渲染的示例,我们可以在服务器端使用 express(NodeJS 应用程序的简约型网络框架)。
-
npm update -
npm install express -
npm init:这将生成一个package.json文件
将稍后提到的内容添加到 index.js 文件中,以使用 express 在端口 3000 上启动一个简单的网络应用程序。相同的示例可以在 node_modules/express 目录的 readme 文件中找到:
var express = require('express');
//....Initialise the app variable requiring the express.
var app = express();
/* Denotes the basic routing by the express server. */
app.get('/', function (request, response) {
response.send('Hello World');
})
// The following code will make the app listen in your localhost, i port 3000
app.listen(3000);
我们首先声明应用程序为 express 的一个实例。
我们使用 express 服务器来表示基本路由。在这个例子中,express 实例(app)正在使用 GET HTTP 方法。因此,当 app.get 调用默认路径(/)或服务器上的任何路径时,第三个参数作为 HANDLER,应该当路由匹配时向客户端(浏览器)发送响应 Hello World。
应用程序在端口 3000 上运行。您可以根据需要运行应用程序在任何端口上。
使用 node 命令在 express 文件上执行应用程序:`
node index.js
使用 express,我们现在可以看到 ReactJS 服务器端渲染的示例:
-
在您的应用程序目录中,执行以下命令来下载 express:
npm install react express -
从
express.js文件中,我们将调用 React 组件
这是创建 ReactComponent 的代码,不使用 JSX:
ReactComponent.js 文件:
var React = require('react')
var ReactComponent = React.createClass({
render:function(){
return React.createElement('li', null, this.props.argument)
}
});
module.exports = ReactComponent;
在终端中从您的应用程序根目录运行以下命令 node index.js 来启动 express 后,我们将在浏览器中的 localhost:3000 看到以下截图。

Express JS 简单应用程序
这里是对之前提到的代码的解释。
createElement 是 React 的主要类型,它有四个属性(types、properties、keys、ref)。之前提到的突出显示的代码意味着它将创建一个类型为列表(li)的 React 元素,它没有任何属性,但将从 React 渲染组件的属性(其键名为argument)传递值。
根据 Facebook 文档(facebook.github.io/react/docs/top-level-api.html)中的 React API,关于 renderToStaticMarkup 的突出显示代码。
string renderToStaticMarkup(ReactElement element),
"类似于
renderToString,但这个不会创建 React 内部使用的额外 DOM 属性,如data-react-id。如果您想将 React 用作简单的静态页面生成器,移除这些额外属性可以节省大量字节。"
renderToString 将 ReactElement 渲染为其初始 HTML。这应该在服务器上使用。React 将返回一个 HTML 字符串。您可以使用此方法在服务器上生成 HTML,并在初始请求中发送标记,以加快页面加载速度,并允许搜索引擎为了 SEO 目的抓取您的页面。
如果您在已经具有此服务器端渲染标记的节点上调用 ReactDOM.render(),React 将保留它,并且只附加事件处理器,这样您就可以拥有非常快速的首次加载体验。
express.js 文件的代码如下:
var express = require('express');
var React = require('react');
var ReactComponent = React.createFactory(require('./ReactComponent'));
var app = express();
function landingPage(request, response){
var argument = request.params.argument || 'This is the default Landing Page in absence of any parameter in url';
var reactComponent = ReactComponent({argument:argument});
response.send(React.renderToStaticMarkup(reactComponent));
}
app.get('', landingPage);
app.get('/:argument', landingPage)
app.listen(4000)
在终端中从您的应用程序根目录运行以下命令 node index.js 来启动 express 后,我们将在浏览器中的 localhost:4000 看到以下截图。

这是应用程序的截图,显示了 React 服务器端渲染的默认页面。正如我们所见,应用程序监听的端口是 4000。
在动态路由的情况下,这是 React 服务器端渲染的截图,显示了其他页面。

如前所述,如果我们使用 renderToString 而不是 renderToStaticMarkup,我们可以在 React 组件中看到两个属性,例如 data-react-id 和 data-react-checksum。
data-react-id:是 ReactJS 库用来在 DOM 中特定识别其组件的自定义数据属性。它可以在客户端或服务器端存在,而服务器端存在的 ID 以点开头,后面跟一些字母和数字,客户端侧的 ID 仅是数字。
以下示例显示了早期的方法rederToString():
function landingPage(request, response){
var argument = request.params.argument || 'This is the default Landing Page in absence of any parameter in url';
var reactComponent = ReactComponent({argument:argument});
response.send(React.renderToString(reactComponent));
}
重新运行 express 并应用上述更改,将在浏览器中的localhost:4000渲染以下内容,如以下截图所示。

应用程序的截图,使用服务器端渲染的 React,采用renderToString方法。
总结来说,我们可以看到 React-router 是一个能够在服务器端和客户端(浏览器)运行的库。为了使用服务器端渲染,我们使用renderToString()方法和路由。在请求-响应周期中,服务器端的 React-router 与请求的路由匹配,并使用 React 库的renderToString()方法将正确的路由从服务器渲染到客户端(浏览器)。
同构应用程序
同构 JavaScript 应用程序是指 JavaScript 同时在服务器和客户端使用的情况。因此,相同的 React 组件可以在客户端以及服务器端使用。构建此类应用程序的一些优点包括:
-
当需要时,根据应用程序状态在服务器端渲染视图。
- 服务器将以与客户端相同的方式渲染应用程序,以增加一致性。
-
如果浏览器中的 JavaScript 无法正常工作,应用程序仍然可以工作,因为服务器端也存在相同的 JavaScript。您需要将操作发送到服务器以获得相同的结果。
热重载
热重载是 JavaScript 世界中的一个术语,用来指代浏览器中的实时更改,而无需刷新浏览器。在 React 生态系统中,React Hot Loader 被广泛用于相同的目的。
React Hot Loader 是一个针对 Webpack 的插件,它可以在浏览器中实现即时和实时的更改,而不会丢失状态。在编辑 React 组件和函数时,这些更改可以可见,就像 React 的LiveReload一样。
作者(Dan Abramov)在这里讨论了 React Hot Loader 第一版本的局限性medium.com/@dan_abramov/the-death-of-react-hot-loader-765fa791d7c4#.fc78lady9。项目的详细信息可以在gaearon.github.io/react-hot-loader/找到。
Redux React
Redux 是由 Dan Abramov 设计的 JavaScript 库,它帮助 JavaScript 应用程序的状态容器化。随着应用程序的增长,由于模型和视图之间需要双向状态可更新性的要求,复杂性也随之增加。Redux 的出现是为了解决这种扭曲的复杂状态变更和异步问题。因此,它将自己定义为尝试使状态变更可预测。
它可以与 React 或任何其他视图库一起使用。在使用 Redux 时需要记住的一些关键点如下:
-
JavaScript 应用程序的状态完全存储在同一个对象树中的单个存储中。因此,即使应用程序增长,调试也更容易。由于整个应用程序状态都在一个地方,开发阶段也更快。状态是只读的;状态中只有获取器,没有设置器,因为你无法写入这个存储。
-
对状态的任何更改都只能通过发出一个动作来完成。动作不过是一个描述发生变化的对象。这些动作对象可以被记录、序列化、存储,并在以后重新播放以进行调试。除了这些动作之外,没有任何视图或网络回调可以更改状态。这种限制使得状态变更可预测,无需寻找任何瞬时的隐藏变化。
-
Redux 的第三个组件是reducers。Reducers 告诉动作如何改变状态树。Reducers 实际上就是具有前一个状态和动作的函数。因此,reducers 充当状态存储的设置器,因为它们正在设置新状态。任何要执行的改变都不是在实际的状态对象上,而是在状态对象的副本(新状态对象)上。在简单应用程序中可以使用单个根 reducer,而当任务数量增加时,可以委托给多个子 reducers(通过传递额外的数据)。
来源:
Relay 和 GraphQL
Relay 是 ReactJS 中的一个用于声明式数据获取的框架,它解决了在基于 React 的应用程序中更新数据以及确切更新位置的问题。使用 GraphQL,Relay 框架解耦了要获取的数据与如何获取数据之间的关系。
GraphQL 就像是一种查询语言,用于查询一个图,尽管它通常不是一个像饼图、x 轴、y 轴或维恩图所表示的图。
-
它用于从关系图中查询,其中每个节点以及它们之间的关系都表示为边。
-
为了从基于关系图的一个子集中获取数据,GraphQL 非常有用。
-
与在表示状态传输(REST)中不同,在 REST 中,数据是根据服务器端点使用资源从服务器获取的,而在 GraphQL 中,数据是根据客户端的要求从服务器获取的。
-
因此,数据被解耦,所有数据都在单个网络请求中一次性从服务器获取。
-
数据可以轻松地存储和检索缓存,这导致性能更快。
-
任何写操作都被称为变异。GraphQL 存储在磁盘上的数据变化与返回给开发者的数据之间不是一对一的关系。最好的方法是使用一个查询,它是缓存数据和可能发生变化的数据的交集。
为了深入了解 Relay 框架,请参阅facebook.github.io/relay/docs/thinking-in-relay.html#content。
React Native
如其名所示,React Native用于在 iOS 和 Android 平台使用 JavaScript 和 ReactJS 构建原生应用程序。以下是 React Native 的一些关键特性,这些特性受到 Facebook 开发团队的青睐(facebook.github.io/react-native/),用于原生平台:
-
它使用 React 组件的对应物具有外观和感觉的一致性
-
您可以使用 Chrome 开发者工具开发应用程序并在模拟器中运行
-
应用程序和原生平台之间的所有代码都有异步执行
-
React Native 无缝处理触摸事件、polyfills、StyleSheet 抽象、设计常见的 UI 布局
-
它被广泛用于扩展原生代码,创建 iOS 和 Android 模块和视图,并且可以轻松地稍后重用
-
React Native 的声明性、异步性和响应性特性对于 iOS 开发非常有用
参考文献
注意,这里的列表远非详尽无遗,有大量的优秀文章、博客文章和每天新涌现的更多内容。
这里有一些需要关注的网站:
以下是一些社交媒体上的社区:
摘要
ReactJS 是一个充满活力的 JS 社区。JavaScript 生态系统每天都在发生许多变化和进步。保持我们自己的更新是一个庞大而必要的任务。我们可以通过在社交平台上关注他们、问答论坛、他们的网站、参加会议以及最后但同样重要的是,始终保持我们的实践来密切跟踪 JS 世界的最新动态。
对于任何评论、建议或讨论,请随时通过@doelsengupta,@singhalmanu联系我们。




浙公网安备 33010602011771号