React-和-Bootstrap-Web-开发学习手册-全-
React 和 Bootstrap Web 开发学习手册(全)
原文:
zh.annas-archive.org/md5/59c715363f0dff298e7d1cff58a50a77
译者:飞龙
前言
我们都知道 JavaScript 应用程序是 Web 开发的未来,有许多不同的框架可用于构建同构 JavaScript Web 应用程序。然而,随着 Web 开发世界的变化,我们都需要作为开发人员现代化,学习新的框架并构建新的工具。重要的是要分析框架的代码方法,并采用相同的方法,而不是迷失在框架市场中。ReactJS 是一个开源的 JavaScript 库,类似于 Bootstrap,用于构建用户界面,被称为MVC中的V(视图)。当我们谈论M和C的定义时,我们可以使用其他框架,如 Redux 和 Flux,来处理远程数据。
Bootstrap 是一个用于开发响应式网站和 Web 应用程序的开源前端框架。它包括 HTML、CSS 和 JavaScript 代码来构建用户界面组件。这是一种更快、更简单的开发强大的移动优先响应式设计的方法。Bootstrap 库包括响应式的 12 列网格和预定义的类,用于简单的布局选项(固定宽度和全宽度)。Bootstrap 有数十个预定义的可重用组件和自定义的 jQuery 插件,如按钮、警报、下拉菜单、模态框、工具提示标签、分页、轮播、徽章和图标。
本书从对 ReactJS 和 Bootstrap 的详细研究开始。本书进一步介绍了如何使用 Twitter Bootstrap、React-Bootstrap 等来创建 ReactJS 的小组件。它还让我们了解了 JSX、Redux 和 Node.js 集成,以及高级概念,如 reducers、actions、store、live reload 和 webpack。目标是帮助读者使用 ReactJS 和 Bootstrap 构建响应式和可扩展的 Web 应用程序。
本书内容
第一章 ,使用 React 和 Bootstrap 入门,介绍了 ReactJS、它的生命周期和 Bootstrap,以及一个小型表单组件。
第二章 ,使用 React-Bootstrap 和 React 构建响应式主题,介绍了 React-Bootstrap 集成,它的好处以及 Bootstrap 响应式网格系统。
第三章 ,ReactJS-JSX,讲述了 JSX,它的优势,以及在 React 中的工作原理和示例。
第四章 ,使用 ReactJS 进行 DOM 交互,深入解释了 props 和 state 以及 React 如何与 DOM 交互,附有示例。
第五章 ,使用 React 的 jQuery Bootstrap 组件,探讨了我们如何将 Bootstrap 组件与 React 集成,附有示例。
第六章 ,Redux 架构,涵盖了使用 ReactJS 和 Node.js 的 Redux 架构,并附有示例,以及其优势和集成。
第七章 ,使用 React 进行路由,展示了 React 路由器与 ReactJS 和 Bootstrap 的导航组件的示例,以及其优势和集成。
第八章 ,ReactJS API,探讨了我们如何在 ReactJS 中集成 Facebook 等第三方 API 以获取个人资料。
第九章 ,React 与 Node.js,涵盖了为服务器端 React 应用程序设置的 Node.js,并涵盖了使用 Bootstrap 和 ReactJS npm 模块创建小型应用程序。
第十章,最佳实践,列出了创建 React 应用程序的最佳实践,并帮助我们理解 Redux 和 Flux 之间的区别,Bootstrap 自定义以及要关注的项目列表。
这本书需要什么
要运行本书中的示例,需要以下工具:
ReactJS | 15.1 及以上 | facebook.github.io/react/ |
---|---|---|
ReactJS DOM | 15.1 及以上 | facebook.github.io/react/ |
Babel | 5.8.23 | cdnjs.com/libraries/babel-core/5.8.23 |
Bootstrap | 3.3.5 | getbootstrap.com/ |
jQuery | 1.10.2 | jquery.com/download/ |
React-Bootstrap | 1.0.0 | react-bootstrap.github.io/ |
JSX 转换器 | 0.13.3 | cdnjs.com/libraries/react/0.13.0 |
React Router 库 | 3.0.0 | unpkg.com/react-router@3.0.0/umd/ReactRouter.min.js |
Node.js | 0.12.7 | nodejs.org/en/blog/release/v0.12.7/ |
MongoDB | 3.2 | www.mongodb.org/downloads#production |
这本书适合谁
如果您对 HTML、CSS 和 JavaScript 有中级知识,并且想要学习为什么 ReactJS 和 Bootstrap 是开发人员创建应用程序快速、响应式和可扩展用户界面的第一选择,那么这本书适合您。如果您对模型、视图、控制器(MVC)概念很清楚,那么理解 React 的架构就是一个额外的优势。
约定
在本书中,您将找到一些文本样式,用于区分不同类型的信息。以下是这些样式及其解释的一些示例。
文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名显示如下:“现在我们需要在chapter1
文件夹内创建几个文件夹,分别命名为images
、css
和js
(JavaScript),以便使您的应用程序更易管理。”
代码块设置如下:
<section>
<h2>Add your Ticket</h2>
</section>
<script>
var root = document.querySelector
('section').createShadowRoot();
root.innerHTML = '<style>h2{ color: red; }</style>' +
'<h2>Hello World!</h2>';
</script>
当我们希望引起您对代码块的特定部分的注意时,相关行或项将以粗体显示:
<div className="container">
<h1>Welcome to EIS</h1>
<hr/>
<div className="row">
<div className="col-md-12 col-lg-12">
**{this.props.children}**
</div>
</div>
</div>
任何命令行输入或输出都按照以下方式编写:
**npm install <package name> --save**
新术语和重要单词以粗体显示。您在屏幕上看到的单词,例如菜单或对话框中的单词,会在文本中以这种方式出现:“在仪表板页面上,您的左侧导航显示设置链接。请点击该链接设置应用程序的基本和高级设置。”
注意
警告或重要提示会以这样的方式出现在一个框中。
提示
提示和技巧如下所示。
第一章:开始使用 React 和 Bootstrap
使用 JavaScript 和 CSS 构建现代 Web 应用程序有许多不同的方法,包括许多不同的工具选择和许多新的理论需要学习。本书向您介绍了 ReactJS 和 Bootstrap,您在学习现代 Web 应用程序开发时可能会遇到它们。它们都用于构建快速和可扩展的用户界面。React 以(视图)而闻名于 MVC。当我们谈论定义M和C时,我们需要寻找其他地方,或者我们可以使用其他框架如 Redux 和 Flux 来处理远程数据。
学习代码的最佳方法是编写代码,所以我们将立即开始。为了向您展示使用 Bootstrap 和 ReactJS 轻松上手的方法,我们将涵盖理论,并制作一个超级简单的应用程序,可以让我们构建一个表单,并实时在页面上显示它。
您可以以任何您感觉舒适的方式编写代码。尝试创建小组件/代码示例,这将让您更清楚/了解任何技术。现在,让我们看看这本书将如何在涉及 Bootstrap 和 ReactJS 时让您的生活变得更轻松。我们将涵盖一些理论部分,并构建两个简单的实时示例:
-
Hello World!使用 ReactJS
-
使用 React 和 Bootstrap 的简单静态表单应用程序
Facebook 通过引入 React 真正改变了我们对前端 UI 开发的看法。这种基于组件的方法的主要优势之一是易于理解,因为视图只是属性和状态的函数。
我们将涵盖以下主题:
-
设置环境
-
ReactJS 设置
-
Bootstrap 设置
-
为什么要使用 Bootstrap
-
使用 React 和 Bootstrap 的静态表单示例
ReactJS
React(有时称为 React.js 或 ReactJS)是一个开源的 JavaScript 库,提供了一个将数据呈现为 HTML 的视图。组件通常用于呈现包含自定义 HTML 标记的其他组件的 React 视图。React 为您提供了一个微不足道的虚拟 DOM,强大的视图而无需模板,单向数据流和显式变异。当数据发生变化时,它在更新 HTML 文档方面非常有条理;并在现代单页应用程序上提供了组件的清晰分离。
观察以下示例,我们将清楚地了解普通 HTML 封装和 ReactJS 自定义 HTML 标记。
观察以下 JavaScript 代码片段:
<section>
<h2>Add your Ticket</h2>
</section>
<script>
var root = document.querySelector
('section').createShadowRoot();
root.innerHTML = '<style>h2{ color: red; }</style>' +
'<h2>Hello World!</h2>';
</script>
观察以下 ReactJS 代码片段:
var sectionStyle = {
color: 'red'
};
var AddTicket = React.createClass({
render: function() {
return (<section><h2 style={sectionStyle}>
Hello World!</h2></section>)}
})
ReactDOM.render(<AddTicket/>, mountNode);
随着应用程序的出现和进一步发展,确保组件以正确的方式使用是有利的。React 应用程序由可重用组件组成,这使得代码重用、测试和关注点分离变得容易。
React 不仅是 MVC 中的V,还具有有状态组件(有状态组件记住this.state
中的所有内容)。它处理输入到状态变化的映射,并渲染组件。在这个意义上,它做了 MVC 所做的一切。
让我们来看一下 React 的组件生命周期及其不同的级别。我们将在接下来的章节中更多地讨论这个问题。观察以下图表:
注意
React 不是一个 MVC 框架;它是一个用于构建可组合用户界面和可重用组件的库。React 在 Facebook 的生产阶段使用,并且instagram.com完全基于 React 构建。
设置环境
当我们开始使用 ReactJS 制作应用程序时,我们需要进行一些设置,这只涉及一个 HTML 页面和包含一些文件。首先,我们创建一个名为chapter1
的目录(文件夹)。在任何代码编辑器中打开它。直接在其中创建一个名为index.html
的新文件,并添加以下 HTML5 样板代码:
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<title>ReactJS Chapter 1</title>
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">You are using an
<strong>outdated</strong> browser.
Please <a href="http://browsehappy.com/">
upgrade your browser</a> to improve your
experience.</p>
<![endif]-->
<!-- Add your site or application content here -->
<p>Hello world! This is HTML5 Boilerplate.</p>
</body>
</html>
这是一个标准的 HTML 页面,一旦我们包含了 React 和 Bootstrap 库,就可以更新它。
现在我们需要在chapter1
文件夹内创建images
、css
和js
(JavaScript)等几个文件夹,以便使应用程序更易管理。完成文件夹结构后,它将如下所示:
安装 ReactJS 和 Bootstrap
创建文件夹结构完成后,我们需要安装 ReactJS 和 Bootstrap 两个框架。只需在页面中包含 JavaScript 和 CSS 文件即可。我们可以通过内容传送网络(CDN)来实现这一点,比如谷歌或微软,但我们将在应用程序中手动获取文件,这样就不必依赖互联网,可以离线工作。
安装 React
首先,我们需要转到此网址facebook.github.io/react/
,然后点击下载 React v15.1.0按钮:
这将为您提供最新版本的 ReactJS 的 ZIP 文件,其中包括 ReactJS 库文件和一些 ReactJS 的示例代码。
现在,我们在我们的应用程序中只需要两个文件:从提取的文件夹的build
目录中的react.min.js
和react-dom.min.js
。
以下是我们需要遵循的几个步骤:
-
将
react.min.js
和react-dom.min.js
复制到您的项目目录,chapter1/js
文件夹,并在编辑器中打开您的index.html
文件。 -
现在您只需要在页面的
head
标签部分添加以下脚本:
<script type="text/js" src="js/react.min.js"></script>
<script type="text/js" src="js/react-dom.min.js"></script>
-
现在我们需要在我们的项目中包含编译器来构建代码,因为现在我们正在使用诸如 npm 之类的工具。我们将从以下 CDN 路径下载文件,
cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js
,或者您可以直接给出 CDN 路径。 -
head
标签部分将如下所示:
<script type="text/js" src="js/react.min.js"></script>
<script type="text/js" src="js/react-dom.min.js"></script>
<script type="text/js" src="js/browser.min.js"></script>
这是您的js
文件夹的最终结构将是这样的:
Bootstrap
Bootstrap 是由 Twitter 维护的开源前端框架,用于开发响应式网站和 Web 应用程序。它包括 HTML、CSS 和 JavaScript 代码来构建用户界面组件。这是开发强大的移动优先用户界面的快速简便的方式。
Bootstrap 网格系统允许您创建响应式的 12 列网格、布局和组件。它包括预定义的类,用于简单的布局选项(固定宽度和全宽度)。Bootstrap 有数十个预定义的可重用组件和自定义 jQuery 插件,如按钮、警报、下拉菜单、模态框、工具提示标签、分页、轮播、徽章、图标等等。
安装 Bootstrap
现在,我们需要安装 Bootstrap。访问getbootstrap.com/getting-started/#download
,然后点击下载 Bootstrap按钮:
这包括我们应用程序的css
和js
的编译和压缩版本;我们只需要 CSSbootstrap.min.css
和fonts
文件夹。这个样式表将为您提供所有组件的外观和感觉,并为我们的应用程序提供响应式布局结构。Bootstrap 的早期版本包括图标作为图像,但在 3 版本中,图标已被替换为字体。我们还可以根据应用程序中使用的组件自定义 Bootstrap CSS 样式表:
-
解压缩 ZIP 文件夹,并将 Bootstrap CSS 从
css
文件夹复制到项目文件夹的 CSS 中。 -
现在将 Bootstrap 的
fonts
文件夹复制到您的项目根目录中。 -
在编辑器中打开你的
index.html
,并在head
部分添加这个link
标签:
<link rel="stylesheet" href="css/bootstrap.min.css">.
就是这样。现在我们可以再次在浏览器中打开index.html
,看看我们正在处理的内容。以下是我们迄今为止编写的代码:
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<title>ReactJS Chapter 1</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<script type="text/javascript" src="js/react.min.js">
</script>
<script type="text/javascript" src="js/react-dom.min.js">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-
core/5.8.23/browser.min.js"></script>
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">You are using an
<strong>outdated</strong> browser.
Please <a href="http://browsehappy.com/">upgrade
your browser</a> to improve your experience.</p>
<![endif]-->
<!-- Add your site or application content here -->
</body>
</html>
使用 React
现在我们已经从 ReactJS 和 Bootstrap 样式表中初始化了我们的应用程序。现在让我们开始编写我们的第一个 Hello World 应用程序,使用ReactDOM.render()
。
ReactDOM.render
方法的第一个参数是我们要渲染的组件,第二个参数是它应该挂载(附加)到的 DOM 节点。观察以下代码:
ReactDOM.render( ReactElement element, DOMElement container,
[function callback] )
为了将其转换为纯 JavaScript,我们在我们的 React 代码中使用包裹,<script type"text/babel">
,这个标签实际上在浏览器中执行转换。
让我们从在body
标签中放一个div
标签开始:
<div id="hello"></div>
现在,添加带有 React 代码的script
标签:
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('hello')
);
</script>
JavaScript 的 XML 语法称为 JSX。我们将在后续章节中探讨这一点。
让我们在浏览器中打开 HTML 页面。如果你在浏览器中看到Hello, world!,那么我们就走在了正确的轨道上。观察以下截图:
在上面的截图中,你可以看到它在你的浏览器中显示了Hello, world!。太棒了。我们已经成功完成了我们的设置,并构建了我们的第一个 Hello World 应用程序。以下是迄今为止我们编写的完整代码:
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<title>ReactJS Chapter 1</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<script type="text/javascript" src="js/react.min.js"></script>
<script type="text/javascript" src="js/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-
core/5.8.23/browser.min.js"></script>
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">You are using an
<strong>outdated</strong> browser.
Please <a href="http://browsehappy.com/">upgrade your
browser</a> to improve your experience.</p>
<![endif]-->
<!-- Add your site or application content here -->
<div id="hello"></div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('hello')
);
</script>
</body>
</html>
使用 React 和 Bootstrap 创建静态表单
我们已经完成了我们的第一个使用 React 和 Bootstrap 的 Hello World 应用程序,一切看起来都很好,符合预期。现在是时候做更多的事情,创建一个静态登录表单,并将 Bootstrap 的外观和感觉应用到它上面。Bootstrap 是一个很好的方式,可以使您的应用程序成为不同移动设备的响应式网格系统,并在 HTML 元素上应用基本样式,包括一些类和 divs。
注意
响应式网格系统是一种简单、灵活、快速的方式,可以使您的 Web 应用程序具有响应性和移动优先性,适当地按设备和视口大小扩展到 12 列。
首先,让我们开始制作一个 HTML 结构,以遵循 Bootstrap 网格系统。
创建一个div
,并添加一个className .container
(固定宽度)和.container-fluid
(全宽度)。使用className
属性而不是使用class
:
<div className="container-fluid"></div>
正如我们所知,class
和for
被不鼓励作为 XML 属性名称。此外,这些在许多 JavaScript 库中都是保留字,因此,为了有一个清晰的区别和相同的理解,我们可以使用className
和htmlFor
来代替使用class
和for
。创建一个div
并添加className="row"
。row
必须放在.container-fluid
中:
<div className="container-fluid">
<div className="row"></div>
</div>
现在创建必须是行的直接子元素的列:
<div className="container-fluid">
<div className="row">
<div className="col-lg-6"></div>
</div>
</div>
.row
和.col-xs-4
是预定义的类,可用于快速创建网格布局。
为页面的标题添加h1
标签:
<div className="container-fluid">
<div className="row">
<div className="col-sm-6">
<h1>Login Form</h1>
</div>
</div>
</div>
网格列是由给定的col-sm-*
中的指定数量的 12 个可用列创建的。例如,如果我们使用四列布局,我们需要指定col-sm-3
以获得相等的列:
类名 | 设备 |
---|---|
col-sm-* |
小设备 |
col-md-* |
中等设备 |
col-lg-* |
大设备 |
我们使用col-sm-*
前缀来调整我们的小设备的列。在列内,我们需要将我们的表单元素label
和input
标签包装在具有form-group
类的div
标签中:
<div className="form-group">
<label for="emailInput">Email address</label>
<input type="email" className="form-control" id="emailInput"
placeholder="Email"/>
</div>
忘记 Bootstrap 的样式;我们需要在输入元素中添加form-control
类。如果我们需要在label
标签中添加额外的填充,那么我们可以在label
上添加control-label
类。
让我们快速添加其余的元素。我将添加一个password
和submit
按钮。
在 Bootstrap 的早期版本中,表单元素通常包装在具有form-action
类的元素中。然而,在 Bootstrap 3 中,我们只需要使用相同的form-group
而不是form-action
。我们将在第二章中更详细地讨论 Bootstrap 类和响应性,使用 React-Bootstrap 和 React 构建响应式主题。
这是我们完整的 HTML 代码:
<div className="container-fluid">
<div className="row">
<div className="col-lg-6">
<form>
<h1>Login Form</h1>
<hr/>
<div className="form-group">
<label for="emailInput">Email address</label>
<input type="email" className="form-control"
id="emailInput" placeholder="Email"/>
</div>
<div className="form-group">
<label for="passwordInput">Password</label>
<input type="password" className=
"form-control" id="passwordInput"
placeholder="Password"/>
</div>
<button type="submit" className="btn btn-default
col-xs-offset-9 col-xs-3">Submit</button>
</form>
</div>
</div>
</div>
现在在var loginFormHTML
脚本标签内创建一个对象,并将此 HTML 分配给它:
Var loginFormHTML = <div className="container-fluid">
<div className="row">
<div className="col-lg-6">
<form>
<h1>Login Form</h1>
<hr/>
<div className="form-group">
<label for="emailInput">Email
address</label>
<input type="email" className="form-control"
id="emailInput" placeholder="Email"/>
</div>
<div className="form-group">
<label for="passwordInput">Password</label>
<input type="password" className="form-
control" id="passwordInput" placeholder="Password"/>
</div>
<button type="submit" className="btn btn-default col-xs-
offset-9 col-xs-3">Submit</button>
</form>
</div>
</div>
我们将在React.DOM()
方法中传递这个对象,而不是直接传递 HTML:
ReactDOM.render(LoginformHTML,document.getElementById('hello'));
我们的表单已经准备好了。现在让我们看看它在浏览器中的样子:
编译器无法解析我们的 HTML,因为我们没有正确地封闭其中一个div
标签。您可以在我们的 HTML 中看到,我们没有在最后关闭包装器container-fluid
。现在在最后关闭包装器标签,然后在浏览器中重新打开文件。
提示
每当您手工编写 HTML 代码时,请仔细检查您的起始标记和结束标记。它应该被正确地编写/关闭,否则它将破坏您的 UI/前端外观和感觉。
在关闭div
标签后的 HTML 如下:
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<title>ReactJS Chapter 1</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<script type="text/javascript" src="js/react.min.js"></script>
<script type="text/javascript" src="js/react-dom.min.js">
</script>
<script src="js/browser.min.js"></script>
</head>
<body>
<!-- Add your site or application content here -->
<div id="loginForm"></div>
<script type="text/babel">
var LoginformHTML =
<div className="container-fluid">
<div className="row">
<div className="col-lg-6">
<form>
<h1>Login Form</h1>
<hr/>
<div className="form-group">
<label for="emailInput">Email address</label>
<input type="email" className="form-control" id=
"emailInput" placeholder="Email"/>
</div>
<div className="form-group">
<label for="passwordInput">Password</label>
<input type="password" className="form-control"
id="passwordInput" placeholder="Password"/>
</div>
<button type="submit" className="btn btn-default
col-xs-offset-9 col-xs-3">Submit</button>
</form>
</div>
</div>
</div>
ReactDOM.render(LoginformHTML,document.getElementById
('loginForm');
</script>
</body>
</html>
现在,您可以在浏览器上检查您的页面,您将能够看到表单的外观和感觉如下屏幕截图所示:
现在它运行良好,看起来不错。Bootstrap 还提供了两个额外的类来使您的元素变小和变大:input-lg
和input-sm
。您还可以通过调整浏览器大小来检查响应式行为。观察以下屏幕截图:
看起来不错。我们的小型静态登录表单应用程序已经具备了响应式行为。
由于这是一个介绍性的章节,您可能会想知道 React 如何有益或有利?
这就是你的答案:
-
渲染您的组件非常容易
-
通过 JSX 的帮助,阅读组件的代码将会非常容易
-
JSX 还将帮助您检查布局以及检查组件之间的插件
-
您可以轻松测试您的代码,它还允许其他工具集成以进行增强
-
React 是一个视图层,您还可以将其与其他 JavaScript 框架一起使用。
上述观点是非常高层次的,我们将在接下来的章节中详细了解更多好处。
总结
我们简单的静态登录表单应用程序和 Hello World 示例看起来很棒,而且正好按照预期工作,所以让我们回顾一下我们在本章中学到的内容。
首先,我们看到了使用 JavaScript 文件和样式表轻松安装 ReactJS 和 Bootstrap 的方法。我们还看了 React 应用程序是如何初始化的,并开始构建我们的第一个表单应用程序。
我们创建的 Hello World 应用程序和表单应用程序演示了 React 和 Bootstrap 的一些基本功能,例如以下内容:
-
ReactDOM
-
渲染
-
Browserify
-
Bootstrap
使用 Bootstrap,我们努力为不同的移动设备实现响应式网格系统,并应用了一些类和 div 的基本 HTML 元素样式。
我们还看到了框架的新的移动优先响应式设计,而不会在我们的标记中添加不必要的类或元素。
在第二章中,让我们使用 React-Bootstrap 和 React 构建一个响应式主题,我们将深入了解 Bootstrap 的特性以及如何使用网格。我们将探索一些更多的 Bootstrap 基础知识,并介绍我们将在本书中构建的项目。
第二章:使用 React-Bootstrap 和 React 构建响应式主题
现在,您已经使用 ReactJS 和 Bootstrap 完成了您的第一个 Web 应用程序,我们将使用这两个框架构建您的应用程序的第一个响应式主题。我们还将涉及到两个框架的全部潜力。所以,让我们开始吧!
设置
首先,我们需要为我们在第一章中制作的 Hello World 应用创建一个类似的文件夹结构,使用 React 和 Bootstrap 入门。
以下屏幕截图描述了文件夹结构:
现在,您需要将 ReactJS 和 Bootstrap 文件从“第一章”复制到“第二章”的重要目录中,并在根目录中创建一个index.html
文件。以下代码片段只是一个包含 Bootstrap 和 React 的基本 HTML 页面。
这是我们 HTML 页面的标记:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>ReactJS theme with bootstrap</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<script type="text/javascript" src="js/react.min.js">
</script>
<script type="text/javascript" src="js/react-dom.min.js">
</script>
<script src="js/browser.min.js"></script>
</head>
<body>
</body>
</html>
脚手架
所以现在我们有了基本文件和文件夹结构。下一步是使用 Bootstrap CSS 开始搭建我们的应用程序。
我相信你有一个问题:什么是脚手架?简单地说,它提供了一个支撑结构,使您的基础更加牢固。
除此之外,我们还将使用 React-Bootstrap JS,其中包含了为 React 重新构建的 Bootstrap 组件集。我们可以在我们的员工信息系统(EIS)中使用这些组件。Bootstrap 还包括一个非常强大的响应式网格系统,帮助我们为应用程序创建响应式主题布局/模板/结构。
导航
导航是任何静态或动态页面的非常重要的元素。所以现在我们将构建一个导航栏(用于导航)来在我们的页面之间切换。它可以放在我们页面的顶部。
这是 Bootstrap 导航的基本 HTML 结构:
<nav className="navbar navbar-default navbar-static-top" role="navigation">
<div className="container">
<div className="navbar-header">
<button type="button" className="navbar-toggle"
data-toggle="collapse" data-target=".navbar-collapse">
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
<a className="navbar-brand" href="#">EIS</a>
</div>
<div className="navbar-collapse collapse">
<ul className="nav navbar-nav">
<li className="active"><a href="#">Home</a></li>
<li><a href="#">Edit Profile</a></li>
<li className="dropdown">
<a href="#" className="dropdown-toggle"
data-toggle="dropdown">Help Desk
<b className="caret"></b></a>
<ul className="dropdown-menu">
<li><a href="#">View Tickets</a></li>
<li><a href="#">New Ticket</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
用于容纳“导航栏”内的所有内容的<nav>
标签,而不是分成两个部分:navbar-header
和navbar-collapse
,如果您查看导航结构。导航栏是响应式组件,因此navbar-header
元素专门用于移动导航,并控制导航的展开和折叠,使用toggle
按钮。按钮上的data-target
属性直接对应于navbar-collapse
元素的id
属性,因此 Bootstrap 知道应该在移动设备中包装哪个元素以控制切换。
现在我们还需要在页面中包含 jQuery,因为 Bootstrap 的 JS 依赖于它。您可以从jquery.com/
获取最新的 jQuery 版本。现在您需要从 Bootstrap 提取的文件夹中复制bootstrap.min.js
,并将其添加到您的应用程序的js
目录中,然后在bootstrap.min.js
之前在页面中包含它。
请确保您的 JavaScript 文件按以下顺序包含:
<script type="text/javascript" src="js/react.min.js"></script>
<script type="text/javascript" src="js/react-dom.min.js"></script>
<script src="js/browser.min.js"></script>
<script src="js/jquery-1.10.2.min.js"></script>
<script src="js/bootstrap.min.js"></script>
在集成 React 后,让我们快速查看navbar
组件代码:
<div id="nav"></div>
<script type="text/babel">
var navbarHTML =
<nav className="navbar navbar-default navbar-static-top"
role="navigation">
<div className="container">
<div className="navbar-header">
<button type="button" className="navbar-toggle"
data-toggle="collapse" data-target=".navbar-collapse">
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
<a className="navbar-brand" href="#">EIS</a>
</div>
<div className="navbar-collapse collapse">
<ul className="nav navbar-nav">
<li className="active"><a href="#">Home</a></li>
<li><a href="#">Edit Profile</a></li>
<li className="dropdown">
<a href="#" className="dropdown-toggle"
data-toggle="dropdown">Help Desk <b className="caret">
</b></a>
<ul className="dropdown-menu">
<li><a href="#">View Tickets</a></li>
<li><a href="#">New Ticket</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
ReactDOM.render(navbarHTML,document.getElementById('nav'));
</script>
在浏览器中打开index.html
文件以查看navbar
组件。以下截图显示了我们的导航的外观:
我们直接在<body>
标签中包含了导航,以覆盖浏览器的整个宽度。现在我们将使用 React-Bootstrap JS 框架来做同样的事情,以了解 Bootstrap JS 和 React-Bootstrap JS 之间的区别。
React-Bootstrap
React-Bootstrap JavaScript 框架类似于为 React 重建的 Bootstrap。它是 Bootstrap 前端可重用组件在 React 中的完全重新实现。React-Bootstrap 不依赖于任何其他框架,如 Bootstrap JS 或 jQuery。这意味着,如果您使用 React-Bootstrap,则不需要将 jQuery 作为依赖项包含在项目中。使用 React-Bootstrap,我们可以确保不会有外部 JavaScript 调用来渲染组件,这可能与ReactDOM.render
不兼容。但是,您仍然可以实现与 Twitter Bootstrap 相同的功能、外观和感觉,但代码更清晰。
安装 React-Bootstrap
要获取这个 React-Bootstrap,我们可以直接使用 CDN,或者从以下 URL 获取:cdnjs.cloudflare.com/ajax/libs/react-bootstrap/0.29.5/react-bootstrap.min.js
。打开此 URL 并将其保存在本地目录以获得更快的性能。下载文件时,请确保同时下载源映射(react-bootstrap.min.js.map
)文件,以便更轻松地进行调试。下载完成后,将该库添加到应用程序的js
目录中,并在页面的head
部分包含它,如下面的代码片段所示。您的head
部分将如下所示:
<script type="text/javascript" src="js/react.min.js"></script>
<script type="text/javascript" src="js/react-dom.min.js"></script>
<script src="js/browser.min.js"></script>
<script src="js/react-bootstrap.min.js"></script>
使用 React-Bootstrap
现在,你可能会想,既然我们已经有了 Bootstrap 文件,还添加了 React-Bootstrap JS 文件,它们不会冲突吗?不,它们不会。React-Bootstrap 与现有的 Bootstrap 样式兼容,所以我们不需要担心任何冲突。
现在我们要在 React-Bootstrap 中创建相同的Navbar
组件。
这里是 React-Bootstrap 中Navbar
组件的结构:
var Nav= ReactBootstrap.Nav;
var Navbar= ReactBootstrap.Navbar;
var NavItem= ReactBootstrap.NavItem;
var NavDropdown = ReactBootstrap.NavDropdown;
var MenuItem= ReactBootstrap.MenuItem;
var navbarReact =(
<Navbar>
<Navbar.Header>
<Navbar.Brand>
<a href="#">EIS</a>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
<Navbar.Collapse>
<Nav>
<NavItem eventKey={1} href="#">Home</NavItem>
<NavItem eventKey={2} href="#">Edit Profile</NavItem>
<NavDropdown eventKey={3} id="basic-
nav-dropdown">
<MenuItem eventKey={3.1}>View Tickets</MenuItem>
<MenuItem eventKey={3.2}>New Ticket</MenuItem>
</NavDropdown>
</Nav>
</Navbar.Collapse>
</Navbar>
);
以下是前述代码的亮点(顺序已从好处部分下移至上方)。
<Navbar>
标签是组件的容器,分为两个部分:<Navbar.Header>
和<Nav>
。
为了响应式行为,我们添加了<Navbar.Toggle/>
标签,用于控制展开和折叠,并将<Nav>
包装到<Navbar.Collapse>
中以显示和隐藏导航项。
为了捕获事件,我们使用了eventKey={1}
;当我们选择任何菜单项时,会触发一个回调,它接受两个参数,(eventKey: any
, event: object
) => any
React-Bootstrap 的好处
让我们来看看使用 React-Bootstrap 的好处。
正如你在前述代码中所看到的,它看起来比 Twitter Bootstrap 组件更清晰,因为我们可以从 React-Bootstrap 中导入单个组件,而不是包含整个库。
例如,如果我想用 Twitter Bootstrap 构建一个navbar
,那么代码结构是:
<nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed"
data-toggle="collapse" data-target="#bs-example-navbar-
collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="#">EIS</a>
</div>
<div class="collapse navbar-collapse" id="bs-example-
navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="#">Home <span class=
"sr-only">(current)</span></a></li>
<li><a href="#">Edit Profile</a></li>
</ul>
<form class="navbar-form navbar-left" role="search">
<div class="form-group">
<input type="text" class="form-control"
placeholder="Search">
</div>
<button type="submit" class="btn
btn-default">Submit</button>
</form>
</div>
<!-- /.navbar-collapse -->
</div>
<!-- /.container-fluid -->
</nav>
现在你可以轻松比较代码,我相信你也会同意使用 React-Bootstrap,因为它非常具体化,而在 Twitter Bootstrap 中,我们需要维护多个元素的正确顺序才能获得类似的结果。
通过这样做,React-Bootstrap 只提取我们想要包含的特定组件,并帮助显著减少应用程序包大小。React-Bootstrap 提供以下一些好处:
-
React-Bootstrap 通过压缩 Bootstrap 代码节省了一些输入并减少了错误
-
它通过压缩 Bootstrap 代码减少了冲突
-
我们不需要考虑 Bootstrap 与 React 采用的不同方法
-
它很容易使用
-
它封装在元素中
-
它使用 JSX 语法
-
它避免了 React 渲染虚拟 DOM
-
很容易检测 DOM 的变化并更新 DOM 而不会发生冲突
-
它不依赖于其他库,比如 jQuery
这里是我们Navbar
组件的完整代码视图:
<div id="nav"></div>
<script type="text/babel">
var Nav= ReactBootstrap.Nav;
var Navbar= ReactBootstrap.Navbar;
var NavItem= ReactBootstrap.NavItem;
var NavDropdown = ReactBootstrap.NavDropdown;
var MenuItem= ReactBootstrap.MenuItem;
var navbarReact =(
<Navbar>
<Navbar.Header>
<Navbar.Brand>
<a href="#">EIS</a>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
<Navbar.Collapse>
<Nav>
<NavItem eventKey={1} href="#">Home</NavItem>
<NavItem eventKey={2} href="#">Edit Profile</NavItem>
<NavDropdown eventKey={3} id="basic-
nav-dropdown">
<MenuItem eventKey={3.1}>View Tickets</MenuItem>
<MenuItem eventKey={3.2}>New Ticket</MenuItem>
</NavDropdown>
</Nav>
</Navbar.Collapse>
</Navbar>
);
ReactDOM.render(navbarReact,document.getElementById('nav'));
哇哦!让我们在浏览器中看看我们的第一个 React-Bootstrap 组件。以下截图显示了组件的外观:
现在来检查Navbar
,如果你调整浏览器窗口大小,你会注意到 Bootstrap 在 768 像素以下的平板电脑纵向模式下显示移动头部和切换按钮。然而,如果你点击按钮切换导航,你会看到移动端的导航。
以下截图显示了移动导航的外观:
现在我们对 React-Bootstrap 和 Bootstrap 有了主要的了解。React-Bootstrap 正在进行积极的开发工作,以保持更新。
Bootstrap 网格系统
Bootstrap 基于一个 12 列网格系统,包括强大的响应式结构和移动优先的流体网格系统,允许我们用很少的元素来搭建我们的 Web 应用。在 Bootstrap 中,我们有一系列预定义的类来组成行和列,所以在开始之前,我们需要在我们的行和列周围包含带有container
类的<div>
标签。否则,框架不会如预期般响应,因为 Bootstrap 编写了依赖于它的 CSS,我们需要在我们的navbar
下面添加它:
<div class="container"><div>
这将使您的 Web 应用程序成为页面的中心,并控制行和列以响应预期地工作。
有四个类前缀,帮助定义列的行为。所有类都与不同的设备屏幕大小相关,并以熟悉的方式响应。来自getbootstrap.com/
的以下表格定义了所有四个类之间的差异:
额外小设备****手机(<768px) | 小设备****平板电脑(≥768px) | 中等设备****台式电脑(≥992px) | 大型设备****台式电脑(≥1200px) | |
---|---|---|---|---|
网格行为 | 始终水平 | 在断点以上折叠,水平 | ||
容器宽度 | 无(自动) | 750px | 970px | 1170px |
类前缀 | .col-xs- | .col-sm- | .col-md- | .col-lg- |
列数 | 12 | |||
列宽 | 自动 | ~62px | ~81px | ~97px |
间距宽度 | 30px(每列两侧各 15px) | |||
可嵌套 | 是 | |||
偏移 | 是 | |||
列排序 | 是 |
在我们的应用程序中,我们需要为主要内容区域和侧边栏创建一个两列布局。正如我们所知,Bootstrap 有一个 12 列网格布局,所以以一种覆盖整个区域的方式划分您的内容。
提示
请理解,Bootstrap 使用col-*-1
到col-*-12
类来划分 12 列网格。
我们将把 12 列分为两部分:一部分是主要内容的九列,另一部分是侧边栏的三列。听起来很完美。所以,这是我们如何实现的。
首先,我们需要在我们的container
内包含<div>
标签,并添加class
为"row"
。根据设计需求,我们可以有多个带有row
类的div
标签,每个标签最多可以容纳 12 列。
<div class="container">
<div class="row">
</div>
<div>
众所周知,如果我们希望我们的列在移动设备上堆叠,我们应该使用col-sm-
前缀。创建列就像简单地取所需的前缀并将要添加的列数附加到它一样简单。
让我们快速看一下我们如何创建一个两列布局:
<div class="container">
<div class="row">
<div class="col-sm-3">
Column Size 3 for smaller devices
</div>
<div class="col-sm-9">
Column Size 9 for smaller devices
</div>
</div>
</div>
如果我们希望我们的列不仅在较小的设备上堆叠,还可以通过向列添加col-md-*
和col-xs-*
来使用额外的小和中等网格类:
<div class="container">
<div class="row">
<div class="col-xs-12 col-md-4">
在手机视图中,这一列将是全宽,在平板视图中,它将是四个中等网格宽度。
</div>
<div class="col-xs-12 col-md-8">
In mobile view, this column will be full width and in tablet view, it will be eight medium grid width.</div>
</div>
</div>
因此,当它在比移动设备更大的屏幕上显示时,Bootstrap 将自动在每列之间添加 30 像素的间距(两个元素之间的空间为 15 像素)。如果我们想在列之间添加额外的空间,Bootstrap 将提供一种方法,只需将额外的类添加到列中即可:
<div class="container">
<div class="row">
<div class="col-xs-12 col-md-7 col-md-offset-1">
手机上的列是一个全宽和另一个半宽,离左边更远:
</div>
</div>
</div>
这次我们使用了offset
关键字。该类名末尾的数字用于控制要偏移的列数。
提示
offset
列数等于行中的总列数12
。
现在,让我们创建一些复杂的布局,嵌套额外的行和列:
<div class="row">
<div class="col-sm-9">
Level 1 - Lorem ipsum...
<div class="row">
<div class="col-xs-8 col-sm-4">
Level 2 - Lorem ipsum...
</div>
<div class="col-xs-4 col-sm-4">
Level 2 - Lorem ipsum...
</div>
</div>
</div>
</div>
如果您在浏览器中打开它,您将看到这将在我们之前创建的主要内容容器col-sm-9
中创建两列。然而,由于我们的网格是嵌套的,我们可以创建一个新行,并拥有一个单列或两列,无论您的布局需要什么。我已经添加了一些虚拟文本来演示嵌套列。
Bootstrap 还将通过使用col-md-push-*
和col-md-pull-*
类在网格系统中提供更改列的顺序的选项。
<div class="row">
<div class="col-sm-9">
Level 1 - Lorem ipsum...
<div class="row">
<div class="col-xs-8 col-sm-4 col-sm-push-4">
Level 2 - col-xs-8 col-sm-4 col-sm-push-4
</div>
<div class="col-xs-4 col-sm-4 col-sm-pull-4">
Level 2 - col-xs-8 col-sm-4 col-sm-pull4
</div>
</div>
</div>
</div>
观察以下屏幕截图:
Bootstrap 还包括一些预定义的类,以便在特定屏幕尺寸下显示或隐藏元素。这些类使用与 Bootstrap 网格相同的预定义尺寸。
例如,以下代码将在特定屏幕尺寸下隐藏一个元素:
<div class="hidden-md"></div>
这将在中等设备上隐藏元素,但在手机、平板电脑和大型台式机上仍然可见。要在多个设备上隐藏元素,我们需要使用多个类:
<div class="hidden-md hidden-lg"></div>
同样,与可见类一样,它们可以反向工作,在特定尺寸下显示元素。
但是,与隐藏类不同,它们还要求我们设置显示值。这可以是block
,inline
或inline-block
:
<div class="visible-md-block"></div>
<div class="visible-md-inline"></div>
<div class="visible-md-inline-block"></div>
当然,我们可以在一个元素中使用各种类。例如,如果我们想在较小的屏幕上使用block
级元素,但稍后将其变为inline-block
,我们将使用以下代码:
<div class="visible-sm-block visible-md-inline-block"></div>
如果您记不住各种类的大小,请务必再次查看了解 Bootstrap 网格部分,以了解屏幕尺寸。
辅助类
Bootstrap 还包括一些辅助类,我们可以用来调整布局。让我们看一些例子。
浮动
Bootstrap 的浮动类将帮助您在 Web 上创建一个体面的布局。以下是两个 Bootstrap 类,用于将您的元素向左和向右拉:
<div class="pull-left">...</div>
<div class="pull-right">...</div>
当我们在元素上使用浮动时,我们需要在我们的浮动元素中包装一个clearfix
类。这将清除元素,您将能够看到容器元素的实际高度:
<div class="helper-classes">
<div class="pull-left">...</div>
<div class="pull-right">...</div>
<div class="clearfix">
</div>
如果float
类直接位于具有row
类的元素内部,则我们的浮动将被 Bootstrap 自动清除,无需手动应用clearfix
类。
中心元素
要使其居中block-level
元素,Bootstrap 允许使用center-block
类:
<div class="center-block">...</div>
这将将您的元素属性margin-left
和margin-right
属性设置为auto
,这将使元素居中。
显示和隐藏
您可能希望使用 CSS 显示和隐藏元素,Bootstrap 为此提供了一些类:
<div class="show">...</div>
<div class="hidden">...</div>
注意
show
类将display
属性设置为block
,因此只将其应用于block-level
元素,而不是希望以inline
或inline-block
显示的元素。
React 组件
React 基于模块化构建,具有封装的组件,这些组件管理自己的状态,因此当数据发生变化时,它将有效地更新和渲染您的组件。在 React 中,组件的逻辑是用 JavaScript 编写的,而不是模板,因此您可以轻松地通过应用程序传递丰富的数据并管理 DOM 之外的状态。
使用render()
方法,我们正在在 React 中呈现一个组件,该组件接受输入数据并返回您想要显示的内容。它可以接受 HTML 标签(字符串)或 React 组件(类)。
让我们快速看一下两者的示例:
var myReactElement = <div className="hello" />;
ReactDOM.render(myReactElement, document.getElementById('example'));
在这个例子中,我们将 HTML 作为字符串传递给render
方法,之前我们已经在创建<Navbar>
之前使用过它:
var ReactComponent = React.createClass({/*...*/});
var myReactElement = <ReactComponent someProperty={true} />;
ReactDOM.render(myReactElement, document.getElementById('example'));
在上面的例子中,我们正在渲染组件,只是为了创建一个以大写约定开头的局部变量。在 React 的 JSX 中使用大写与小写的约定将区分本地组件类和 HTML 标签。
因此,我们可以以两种方式创建 React 元素或组件:一种是使用React.createElement
的纯 JavaScript,另一种是使用 React 的 JSX。
因此,让我们为应用程序创建侧边栏元素,以更好地理解React.createElement
。
React.createElement()
在 React 中使用 JSX 完全是可选的。正如我们所知,我们可以使用React.createElement
创建元素,它接受三个参数:标签名或组件、属性对象和可变数量的子元素(可选)。观察以下代码:
var profile = React.createElement('li',{className:'list-group-item'},
'Profile');
var profileImageLink = React.createElement('a',{className:'center-
block text-center',href:'#'},'Image');
var profileImageWrapper = React.createElement('li',
{className:'list-group-item'}, profileImageLink);
var sidebar = React.createElement('ul', { className: 'list-
group' }, profile, profileImageWrapper);
ReactDOM.render(sidebar, document.getElementById('sidebar'));
在上面的例子中,我们使用React.createElement
生成了一个ul
-li
结构。React 已经为常见的 DOM HTML 标签内置了工厂。
以下是一个示例:
var Sidebar = React.DOM.ul({ className: 'list-group' },
React.DOM.li({className:'list-group-item text-muted'},'Profile'),
React.DOM.li({className:'list-group-item'},
React.DOM.a({className:'center-block text-center',href:'#'},'Image')
),
React.DOM.li({className:'list-group-item text-right'},'2.13.2014',
React.DOM.span({className:'pull-left'},
React.DOM.strong({className:'pull-left'},'Joining Date')
),
React.DOM.div({className:'clearfix'})
));
ReactDOM.render(Sidebar, document.getElementById('sidebar'));
让我们快速在浏览器中查看我们的代码,它应该类似于以下截图:
到目前为止,这是我们编写的包含<Navbar>
组件的全部代码:
<script type="text/babel">
var Nav= ReactBootstrap.Nav;
var Navbar= ReactBootstrap.Navbar;
var NavItem= ReactBootstrap.NavItem;
var NavDropdown = ReactBootstrap.NavDropdown;
var MenuItem= ReactBootstrap.MenuItem;
var navbarReact =(
<Navbar>
<Navbar.Header>
<Navbar.Brand>
<a href="#">EIS</a>
</Navbar.Brand>
<Navbar.Toggle />
</Navbar.Header>
<Navbar.Collapse>
<Nav>
<NavItem eventKey={1} href="#">Home</NavItem>
<NavItem eventKey={2} href="#">Edit Profile</NavItem>
<NavDropdown eventKey={3} id="basic-
nav-dropdown">
<MenuItem eventKey={3.1}>View Tickets</MenuItem>
<MenuItem eventKey={3.2}>New Ticket</MenuItem>
</NavDropdown>
</Nav>
</Navbar.Collapse>
</Navbar>
);
ReactDOM.render(navbarReact,document.getElementById('nav'));
var Sidebar = React.DOM.ul({ className: 'list-group' },
React.DOM.li({className:'list-group-item text-muted'},'Profile'),
React.DOM.li({className:'list-group-item'},
React.DOM.a({className:'center-block
text-center',href:'#'},'Image')
),
React.DOM.li({className:'list-group-item text-right'},
'2.13.2014',
React.DOM.span({className:'pull-left'},
React.DOM.strong({className:'pull-left'},'Joining Date')
),
React.DOM.div({className:'clearfix'})
));
ReactDOM.render(Sidebar, document.getElementById('sidebar'));
</script>
<div id="nav"></div>
<div class="container">
<hr>
<div class="row">
<div class="col-sm-3" id="sidebar">
<!--left col-->
</div>
<!--/col-3-->
<div class="col-sm-9 profile-desc"></div>
<!--/col-9-->
</div>
</div>
<!--/row-->
我们的应用程序代码看起来非常混乱。现在是时候让我们的代码变得整洁和结构良好。
将navbar
代码复制到另一个文件中,并将其保存为navbar.js
。
现在将sidebar
代码复制到另一个文件中,并保存为sidebar.js
。
在根目录中创建一个名为 components 的文件夹,并将navbar.js
和sidebar.js
都复制到其中。
在head
部分包含两个js
文件。
head
部分将如下所示:
<script type="text/javascript" src="js/react.min.js"></script>
<script type="text/javascript" src="js/react-dom.min.js"></script>
<script src="js/browser.min.js"></script>
<script src="js/jquery-1.10.2.min.js"></script>
<script src="js/react-bootstrap.min.js"></script>
<script src="components/navbar.js" type="text/babel"></script>
<script src="components/sidebar.js" type="text/babel"></script>
以下是您的 HTML 代码:
<div id="nav"></div>
<div class="container">
<hr>
<div class="row">
<div class="col-sm-3" id="sidebar">
<!--left col-->
</div>
<!--/col-3-->
<div class="col-sm-9 profile-desc"></div>
<!--col-9-->
</div>
</div>
<!--/row-->
现在我们的代码看起来更加清晰。让我们快速查看一下您在浏览器中的代码输出:
提示
当我们从外部来源引用 ReactJS 文件时,我们需要一个 Web 服务器或者像 WAMP 或 XAMPP 这样的全栈应用,因为一些浏览器(例如 Chrome)在不通过 HTTP 提供文件的情况下会加载失败。
总结
我们已经从本章节中积累了相当多关于 Bootstrap 和 React-Bootstrap 的基础知识,所以让我们快速复习一下我们到目前为止学到的东西。
在了解 Bootstrap 和 React-Bootstrap 的定义和用法时,我们发现 React-Bootstrap 是一个非常有潜力、更灵活、更智能的解决方案。
我们已经看到了如何通过使用 Bootstrap 和 React-Bootstrap 的一些特性来创建移动导航,这些特性在所有预期的设备上以及桌面浏览器上都能很好地工作。
我们还研究了包括 Bootstrap 在内的强大响应式网格系统,并创建了一个简单的两列布局。在做这些的时候,我们学到了四种不同的列类前缀,以及如何嵌套我们的网格。
我们还看到了 Bootstrap 的一些非常好的特性,比如offset
,col-md-push-*
,col-md-pull-*
,hidden-md
,hidden-lg
,visible-sm-block
,visible-md-inline-block
和helper-classes
。
我们希望你也已经准备好了响应式布局和导航。现在让我们跳到下一章。
第三章:ReactJS-JSX
在上一章中,我们通过使用 React-Bootstrap 和 React 来构建响应式主题的过程。我们看到了它的示例以及 Twitter Bootstrap 和 React-Bootstrap 之间的区别。
我现在非常兴奋,因为我们将深入了解 ReactJS 的核心,即 JSX。那么,你们准备好了吗?让我们深入学习 ReactJS-JSX。
在 React 中的 JSX 是什么
JSX 是 JavaScript 语法的扩展,如果您观察 JSX 的语法或结构,您会发现它类似于 XML 编码。
使用 JSX,您可以执行预处理步骤,将 XML 语法添加到 JavaScript 中。虽然您当然可以在没有 JSX 的情况下使用 React,但 JSX 使 React 变得更加整洁和优雅。与 XML 类似,JSX 标记具有标记名称、属性和子级,如果属性值被引号括起来,该值就成为一个字符串。
XML 使用平衡的开放和关闭标记。JSX 类似地工作,它还有助于比 JavaScript 函数和对象更容易地阅读和理解大量的结构。
在 React 中使用 JSX 的优点
以下是一些优点的列表:
-
与 JavaScript 函数相比,JSX 非常容易理解和思考
-
JSX 的标记更容易让非程序员熟悉
-
通过使用 JSX,您的标记变得更有语义、有组织和有意义
如何使您的代码整洁和干净
正如我之前所说,这种结构/语法非常容易可视化/注意到,这意味着当我们将其与 JavaScript 语法进行比较时,JSX 格式的代码更加清晰和易于理解。
以下是简单的代码片段,将给你一个清晰的想法。让我们看看在渲染时 JavaScript 语法的以下示例中的代码片段:
render: function () {
return React.DOM.div({className:"divider"},
"Label Text",
React.DOM.hr()
);
}
观察以下 JSX 语法:
render: function () {
return <div className="divider">
Label Text<hr />
</div>;
}
我假设现在很清楚 JSX 对于通常不习惯处理编码的程序员来说是非常容易理解的,并且他们可以学习和执行它,就像执行 HTML 语言一样。
熟悉或理解
在开发领域,UI 开发人员、用户体验设计师和质量保证人员并不太熟悉任何编程语言,但 JSX 通过提供简单的语法结构使他们的生活变得更加轻松,这个结构在视觉上类似于 HTML 结构。
JSX 显示了一种路径,以一种坚实而简洁的方式指示和看到您的思维结构。
语义/结构化语法
到目前为止,我们已经看到了 JSX 语法是如何易于理解和可视化的,原因在于语义化的语法结构。
JSX 将您的 JavaScript 代码转换为更标准的解决方案,这样可以清晰地设置您的语义化语法和重要组件。借助于 JSX 语法,您可以声明自定义组件的结构和信息,就像在 HTML 语法中一样,这将为您的语法转换为 JavaScript 函数提供魔力。
React.DOM
命名空间帮助我们使用所有 HTML 元素,借助于 ReactJS:这不是一个令人惊讶的功能吗!而且,好处是您可以使用React.DOM
命名空间编写自己命名的组件。
请查看以下简单的 HTML 标记以及 JSX 组件如何帮助您创建语义化标记:
<div className="divider">
<h2>Questions</h2><hr />
</div>
正如您在前面的示例中所看到的,我们用<div>
标记包裹了<h2>Questions</h2><hr />
,并且<div>
标记具有className="divider"
。因此,在 React 复合组件中,您可以创建类似的结构,就像在使用语义化语法的 HTML 编码时一样简单:
<Divider> Questions </Divider>
让我们详细了解一下复合组件是什么,以及我们如何构建它。
复合组件
正如我们所知,您可以使用 JSX 标记和 JSX 语法创建自定义组件,并将您的组件转换为 JavaScript 语法组件。
让我们设置 JSX:
<script type="text/javascript" src="js/react.min.js"></script>
<script type="text/javascript" src="js/react-dom.min.js"></script>
<script src="js/browser.min.js"></script>
<script src="js/divider.js" type="text/babel"></script>
在您的 HTML 中包含以下文件:
<div>
<Divider>...</Divider>
<p>...</p>
</div>
将此 HTML 添加到您的<body>
部分。
现在,我们已经准备好使用 JSX 定义自定义组件了。
要创建自定义组件,我们必须将上述提到的 HTML 标记表达为 React 自定义组件。您只需按照给定的示例执行包装的语法/代码,然后在渲染后,它将给您预期的标记结果。Divider.js
文件将包含:
var Divider = React.createClass({
render: function () {
return (
<div className="divider">
<h2>Questions</h2><hr />
</div>
);
}
});
如果您想将子节点附加到您的组件中,那么在 React-JSX 中是可能的。在前面的代码中,您可以看到我们创建了一个名为divider
的变量,并且借助于 React-JSX,我们可以将其用作 HTML 标记,就像我们使用定义的 HTML 标记<div>
,<span>
等一样。您还记得我们在之前的示例中使用了以下标记吗?如果没有,请再次参考前面的主题,因为它将消除您的疑虑。
<Divider>Questions</Divider>
与 HTML 语法一样,在这里,子节点被捕获在开放和关闭标记之间的数组中,您可以将其设置在组件的props
(属性)中。
在这个例子中,我们将使用this.props.children
= ["Questions"]
,其中this.props.children
是 React 的方法:
var Divider = React.createClass({
render: function () {
return (
<div className="divider">
<h2>{this.props.children}</h2><hr />
</div>
);
}
});
正如我们在前面的示例中看到的,我们可以像在任何 HTML 编码中一样创建带有开放和关闭标记的组件:
<Divider>Questions</Divider>
我们将得到以下预期的输出:
<div className="divider">
<h2>Questions</h2><hr />
</div>
命名空间组件
命名空间组件是 React JSX 中可用的另一个功能请求。我知道你会有一个问题:什么是命名空间组件?好的,让我解释一下。
我们知道 JSX 只是 JavaScript 语法的扩展,它还提供了使用命名空间的能力,因此 React 使用 JSX 命名空间模式而不是 XML 命名空间。通过使用标准的 JavaScript 语法方法,即对象属性访问,这个功能对于直接分配组件作为<Namespace.Component/>
而不是分配变量来访问存储在对象中的组件非常有用。
让我们从以下的显示/隐藏示例开始,以便清楚地了解命名空间组件:
var MessagePanel = React.createClass({
render: function() {
return <div className='collapse in'> {this.props.children} </div>
}
});
var MessagePanelHeading = React.createClass({
render: function() {
return <h2>{this.props.text}</h2>}
});
var MessagePanelContent = React.createClass({
render: function() {
return <div className='well'> {this.props.children} </div>
}
});
从以下示例中,我们将看到如何组合MessagePanel
:
<MessagePanel>
<MessagePanelHeading text='Show/Hide' />
<MessagePanelContent>
Phasellus sed velit venenatis, suscipit eros a, laoreet dui.
</MessagePanelContent>
</MessagePanel>
MessagePanel
是一个组件,用于在用户界面中呈现消息。
它主要有两个部分:
-
MessagePanelHeading
:这显示消息的标题 -
MessagePanelContent
:这是消息的内容
有一种更健康的方式来组成MessagePanel
,即通过将子组件作为父组件的属性来实现。
让我们看看如何做到这一点:
var MessagePanel = React.createClass({
render: function() {
return <div className='collapse in'>
{this.props.children} </div>
}
});
MessagePanel.Heading = React.createClass({
render: function() {
return <h2>{this.props.text}</h2>
}
});
MessagePanel.Content = React.createClass({
render: function() {
return <div className='well'> {this.props.children} </div>
}
});
因此,在前面的代码片段中,您可以看到我们如何通过只添加新的 React 组件Heading
和Content
来扩展MessagePanel
。
现在,让我们看看当我们引入命名空间符号时,组合会发生什么变化:
<MessagePanel>
<MessagePanel.Heading text='Show/Hide' />
<MessagePanel.Content>
Phasellus sed velit venenatis, suscipit eros a, laoreet dui.
</MessagePanel.Content>
</MessagePanel>
现在,我们将在 React 中与 Bootstrap 集成后看到命名空间组件代码的实际示例:
<!doctype html>
<html>
<head>
<title>React JS – Namespacing component</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/custom.css">
<script type="text/javascript" src="js/react.min.js"></script>
<script type="text/javascript" src="js/JSXTransformer.js">
</script>
</head>
<script type="text/jsx">
/** @jsx React.DOM */
var MessagePanel = React.createClass({
render: function() {
return <div className='collapse in'> {this.props.children}
</div>
}
});
MessagePanel.Heading = React.createClass({
render: function() {
return <h2>{this.props.text}</h2>
}
});
MessagePanel.Content = React.createClass({
render: function() {
return <div className='well'> {this.props.children} </div>
}
});
var MyApp = React.createClass({
getInitialState: function() {
return {
collapse: false
};
},
handleToggle: function(evt){
var nextState = !this.state.collapse;
this.setState({collapse: nextState});
},
render: function() {
var showhideToggle = this.state.collapse ?
(<MessagePanel>
<MessagePanel.Heading text='Show/Hide' />
<MessagePanel.Content>
Phasellus sed velit venenatis, suscipit eros a,
laoreet dui.
</MessagePanel.Content>
</MessagePanel>)
: null;
return (<div>
<h1>Namespaced Components Demo</h1>
<p><button onClick={this.handleToggle} className="btn
btn-primary">Toggle</button></p>
{showhideToggle}
</div>)
}
});
React.render(<MyApp/>, document.getElementById('toggle-
example'));
</script>
</head>
<body>
<div class="container">
<div class="row">
<div id="toggle-example" class=”col-sm-12”>
</div>
</div>
</div>
</body>
</html>
让我解释一下前面的代码:
-
State
属性包含我们组件的setState
和getInitialState
设置的状态 -
setState(changes)
方法将给定的更改应用于此状态并重新呈现它 -
handleToggle
函数处理我们组件的状态并返回布尔值true
或false
我们还使用了一些 Bootstrap 类来赋予我们的组件外观和感觉:
-
.collapse
:这是用于隐藏内容的。 -
.collapse.in
:这是用于显示内容的。 -
.well
:这是用于内容周围的背景、边框和间距。 -
.btn .btn-primary
:这是按钮的外观。Bootstrap 还为您提供了一些不同颜色样式的不同类,帮助读者提供视觉指示: -
.btn-default
、.btn-success
、.btn-info
、.btn-warning
、.btn-danger
和.btn-link
。 -
我们可以使用
<a>
、<button>
或<input>
元素。 -
.col-sm-12
:这是为了使你的组件在小屏幕上响应。
现在,让我们在浏览器中打开你的 HTML 并查看输出:
现在调整屏幕大小,看看效果:
看起来很棒!
JSXTransformer
JSXTransformer是另一个在浏览器中编译 JSX 的工具。在阅读代码时,浏览器将读取你所提到的<script>
标签中的attribute type="text/jsx"
,它只会转换那些具有提到type
属性的脚本,然后执行你的脚本或文件中的函数。代码将以与react-tools
在服务器上执行的相同方式执行。访问facebook.github.io/react/blog/2015/06/12/deprecating-jstransform-and-react-tools.html
了解更多。
JSXTransformer 在当前版本的 React 中已经被弃用,但你可以在任何提供的 CDN 和 Bower 上找到当前版本。依我看来,使用Babel REPL(babeljs.io/repl/#?babili=false&evaluate=true&lineWrap=false&presets=es2015%2Creact%2Cstage-2&code=
)工具来编译 JavaScript 会很棒。它已经被 React 和更广泛的 JavaScript 社区所采用。
注意
这个例子在最新版本的 React 中不起作用。使用旧版本,比如 0.13,因为 JSXTransformer 已经被弃用,它被 Babel 所取代,用于在浏览器中转换和运行 JSX 代码。当浏览器具有type="text/babel"
类型属性时,它才能理解你的<script>
标签,我们之前在第一章和第二章的例子中使用过这种类型属性。
属性表达式
如果您看一下前面的显示/隐藏示例,您会发现我们使用属性表达式来显示消息面板并隐藏它。在 React 中,写属性值有一个小改变,在 JavaScript 表达式中,我们用引号(""
)来写属性,但在 React 中,我们必须提供一对花括号({}
):
var showhideToggle = this.state.collapse ? (<MessagePanel>):null/>;
布尔属性
布尔属性有两个值,它们可以是true
或false
,如果我们在 JSX 中声明属性时忽略了值,那么默认情况下它会取值为true
。如果我们想要一个false
属性值,那么我们必须使用属性表达式。当我们使用 HTML 表单元素时,这种情况经常发生,例如disabled
属性,required
属性,checked
属性和readOnly
属性。
在 Bootstrap 示例中aria-haspopup="true"aria-expanded="true"
:
// example of writing disabled attribute in JSX
<input type="button" disabled />;
<input type="button" disabled={true} />;
JavaScript 表达式
如前面的示例所示,您可以使用在任何句柄用户习惯的语法中在 JSX 中嵌入 JavaScript 表达式,例如,style = { displayStyle }
将displayStyle
JavaScript 变量的值分配给元素的style
属性。
样式
与表达式一样,您可以通过将普通的 JavaScript 对象分配给style
属性来设置样式。多么有趣。如果有人告诉你不要编写 CSS 语法,您仍然可以编写 JavaScript 代码来实现这一点,而不需要额外的努力。这不是很棒吗!是的,确实如此。
事件
有一组事件处理程序,您可以以一种熟悉 HTML 的方式绑定它们。
一些 React 事件处理程序的名称如下:
-
剪贴板事件
-
组合事件
-
键盘事件
-
焦点事件
-
表单事件
-
鼠标事件
-
选择事件
-
触摸事件
-
UI 事件
-
滚轮事件
-
媒体事件
-
图像事件
-
动画事件
-
过渡事件
属性
JSX 的一些定义的PropTypes
如下:
-
React.PropTypes.array
-
React.PropTypes.bool
-
React.PropTypes.func
-
React.PropTypes.number
-
React.PropTypes.object
-
React.PropTypes.string
-
React.PropTypes.symbol
如果您提前了解所有属性,那么在使用 JSX 创建组件时会很有帮助:
var component = <Component foo={x} bar={y} />;
改变props
是不好的做法,让我们看看为什么。
通常,根据我们的做法,我们将属性设置为非推荐的标准对象:
var component = <Component />;
component.props.foo = x; // bad
component.props.bar = y; // also bad
如前面的例子所示,您可以看到反模式,这不是最佳实践。如果您不了解 JSX 属性的属性,则propTypes
将不会被设置,并且将抛出难以跟踪的错误。
props
是属性的一个非常敏感的部分,所以您不应该更改它们,因为每个 prop 都有一个预定义的方法,您应该按照其预期的方式使用它,就像我们使用其他 JavaScript 方法或 HTML 标签时一样。这并不意味着不可能更改props
。这是可能的,但这违反了 React 定义的标准。即使在 React 中,它也会抛出错误。
扩展属性
让我们看看 JSX 的特性--扩展属性:
var props = {};
props.foo = x;
props.bar = y;
var component = <Component {...props} />;
在前面的例子中,您声明的属性也已成为组件的props
的一部分。
属性的可重用性在这里也是可能的,您还可以将其与其他属性进行映射。但是在声明属性时,您必须非常小心,因为它将覆盖先前声明的属性,最后声明的属性将覆盖之前的属性。
var props = { foo: 'default' };
var component = <Component {...props} foo={'override'} />;
console.log(component.props.foo); // 'override'
希望您现在对 JSX、JSX 表达式和属性有了清楚的了解。那么,让我们看看如何使用 JSX 动态构建简单的表单。
使用 JSX 构建动态表单的示例
在使用 JSX 构建动态表单之前,我们必须了解 JSX 表单库。
通常,HTML 表单元素输入将其值作为显示文本/值,但在 React JSX 中,它们将相应元素的属性值作为显示文本/值。由于我们已经直观地感知到我们不能直接更改props
的值,所以输入值不会具有转变后的值作为展示值。
让我们详细讨论一下。要更改表单输入的值,您将使用value
属性,然后您将看不到任何更改。这并不意味着我们不能更改表单输入的值,但是为此我们需要监听输入事件,然后您将看到值的变化。
以下异常是不言自明的,但非常重要:
-
在 React 中,
Textarea
内容将被视为value
属性。 -
由于
For
是 JavaScript 的保留关键字,HTML 的for
属性应该像htmlFor
prop 一样被绑定
现在是时候学习了,为了在输出中拥有表单元素,我们需要使用以下脚本,并且还需要用先前编写的代码替换它。
现在让我们开始为我们的应用程序构建一个添加工单表单。
在根目录中创建一个React-JSXform.html
文件。以下代码片段只是一个包含 Bootstrap 和 React 的基本 HTML 页面。
这是我们 HTML 页面的标记:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Dynamic form with JSX</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
</head>
<body>
<script type="text/javascript" src="js/react.min.js"></script>
<script type="text/javascript" src="js/react-dom.min.js">
</script>
<script src="js/browser.min.js"></script>
</body>
</html>
将所有脚本加载到页面底部,在<body>
标签关闭之前,总是一个良好的做法,这样可以成功加载组件到 DOM 中,因为当脚本在<head>
部分执行时,文档元素不可用,因为脚本本身在<head>
部分。解决此问题的最佳方法是将脚本保持在页面底部,在<body>
标签关闭之前执行,这样在加载所有 DOM 元素后执行,不会引发任何 JavaScript 错误。
现在让我们使用 Bootstrap 和 JSX 创建<form>
元素:
<form>
<div className="form-group">
<label htmlFor="email">Email <span style={style}>*</span>
</label>
<input type="text" id="email" className="form-control"
placeholder="Enter email" required/>
</div>
</form>
在上面的代码中,我们使用class
作为className
,for
作为htmlFor
,因为 JSX 类似于 JavaScript,而for
和class
是 JavaScript 中的标识符。我们应该在ReactDOM
组件中将className
和htmlFor
作为属性名称使用。
所有表单元素<input>
、<select>
和<textarea>
都将使用.form-control
类获得全局样式,并默认应用width:100%
。因此,当我们在输入框中使用标签时,我们需要使用.form-group
类进行包装,以获得最佳间距。
对于我们的添加工单表单,我们需要以下表单字段以及标签:
-
邮箱:<input>
-
问题类型:<select>
-
分配部门:<select>
-
评论:<textarea>
-
按钮:<button>
为了使其成为响应式表单,我们将使用*col-*
类。
让我们快速查看一下我们的表单组件代码:
var style = {color: "#ffaaaa"};
var AddTicket = React.createClass({
handleSubmitEvent: function (event) {
event.preventDefault();
},
render: function() {
return (
<form onSubmit={this.handleSubmitEvent}>
<div className="form-group">
<label htmlFor="email">Email <span style={style}>*
</span></label>
<input type="text" id="email" className="form-
control" placeholder="Enter email" required/>
</div>
<div className="form-group">
<label htmlFor="issueType">Issue Type <span style=
{style}>*</span></label>
<select className="form-control" id="issueType"
required>
<option value="">-----Select----</option>
<option value="Access Related Issue">Access
Related Issue</option>
<option value="Email Related Issues">Email
Related Issues</option>
<option value="Hardware Request">Hardware
Request</option>
<option value="Health & Safety">Health &
Safety</option>
<option value="Network">Network</option>
<option value="Intranet">Intranet</option>
<option value="Other">Other</option>
</select>
</div>
<div className="form-group">
<label htmlFor="department">Assign Department
<span style={style}>*</span></label>
<select className="form-control" id="department"
required>
<option value="">-----Select----</option>
<option value="Admin">Admin</option>
<option value="HR">HR</option>
<option value="IT">IT</option>
<option value="Development">Development
</option>
</select>
</div>
<div className="form-group">
<label htmlFor="comments">Comments <span style=
{style}>*</span></label>(<span id="maxlength">
200</span> characters left)
<textarea className="form-control" rows="3"
id="comments" required></textarea>
</div>
<div className="btn-group">
<button type="submit" className="btn btn-primary">
Submit</button>
<button type="reset" className="btn btn-link">
cancel</button>
</div>
</form>
);
}
});
ReactDOM.render(
<AddTicket />
,
document.getElementById('form')
);
在属性值中应用样式或调用onSubmit
函数,而不是使用引号(""
),我们必须在 JavaScript 表达式中使用一对花括号({}
)。现在,创建一个component
文件夹,并将此文件保存为form.js
,然后将其包含在您的 HTML 页面中。这是我们页面的样子:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Dynamic form with JSX</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-12 col-md-6">
<h2>Add Ticket</h2>
<hr/>
<div id="form">
</div>
</div>
</div>
</div>
<script type="text/javascript" src="js/react.min.js"></script>
<script type="text/javascript" src="js/react-dom.min.js">
</script>
<script src="js/browser.min.js"></script>
<script src="component/form.js" type="text/babel"></script>
</body>
</html>
让我们快速查看一下我们组件在浏览器中的输出:
哦,太酷了!看起来很棒。
在调整浏览器大小时,让我们检查一下表单组件的响应行为:
提示
在创建 React 组件时,第一个字符应始终是大写。例如,AddTicket
。
总结
在本章中,我们已经看到了 JSX 在制作自定义组件方面起着重要作用,使它们非常简单易于可视化、理解和编写。
本章中展示的关键示例将帮助您了解 JSX 语法及其实现。
本章的最后一个示例涵盖了响应式的使用 JSX 和 Bootstrap 创建添加工单表单,这给了您关于 JSX 语法执行以及如何创建自定义组件的想法。您可以在与 HTML 交互时轻松使用它并进行调整。
如果您仍然不确定 JSX 及其行为,我建议您再次阅读本章,因为这也将帮助您在查看未来章节时。
如果您完全理解了本章,那么让我们继续阅读第四章,ReactJS 中的 DOM 交互,这一章将讨论 DOM 与 React 的交互,我们将看到 DOM 与 ReactJS 的交互。这是一个有趣的章节,因为当我们谈论输入和输出之间的交互时,我们必须考虑后端代码和 DOM 元素。您将看到一些非常有趣的主题,如 props 和 state,受控组件,不受控组件,非 DOM 属性键和引用,以及许多示例。
第四章. 使用 ReactJS 与 DOM 交互
在上一章中,我们学习了什么是 JSX,以及如何在 JSX 中创建组件。与许多其他框架一样,React 还有其他原型可以帮助我们构建我们的 Web 应用程序。每个框架都有不同的方式与 DOM 元素交互。React 使用快速的内部合成 DOM 来执行差异并为您计算最有效的 DOM 突变,其中您的组件实际上存在。
React 组件类似于接受 props 和 state 的函数(这将在后面的部分中解释)。React 组件只呈现单个根节点。如果我们想呈现多个节点,那么它们必须被包装到单个根节点中。
在我们开始使用表单组件之前,我们应该先看一下 props 和 state。
Props 和 state
React 组件将您的原始数据转换为丰富的 HTML,props 和 state 一起构建该原始数据以保持您的 UI 一致。
好的,让我们确定它到底是什么:
-
Props 和 state 都是普通的 JS 对象。
-
它们通过
render
更新触发。 -
React 通过调用
setState(data,callback)
来管理组件状态。这种方法将数据合并到此状态中,并重新呈现组件以保持我们的 UI 最新。例如,下拉菜单的状态(可见或隐藏)。 -
React 组件 props(属性)随时间不会改变。例如,下拉菜单项。有时组件只使用这种
props
方法获取一些数据并呈现它,这使得您的组件无状态。 -
同时使用
props
和state
有助于您创建一个交互式应用程序。
参考第三章中的实时示例,ReactJS-JSX。您将更好地理解状态和属性的工作原理。
在这个例子中,我们正在管理切换的状态(显示或隐藏)和切换按钮的文本作为属性。
表单组件
在 React 中,表单组件与其他本机组件不同,因为它们可以通过用户交互进行修改,例如<input>
、<textarea>
和<option>
。
以下是支持的事件列表:
-
onChange
、onInput
和onSubmit
-
onClick
、onContextMenu
、onDoubleClick
、onDrag
、onDragEnd
、onDragEnter
和onDragExit
-
onDragLeave
、onDragOver
、onDragStart
、onDrop
、onMouseDown
、onMouseEnter
和onMouseLeave
-
onMouseMove
、onMouseOut
、onMouseOver
和onMouseUp
可以在官方文档中找到支持的事件的完整列表:facebook.github.io/react/docs/events.html#supported-events
。
表单组件中的 Props
正如我们所知,ReactJS 组件有自己的 props 和类似状态的形式,支持通过用户交互受影响的一些 props:
<input>
和<textarea>
组件 | 支持的 Props |
---|---|
<input> 和<textarea> |
Value, defaultValue |
<input> 类型的复选框或单选按钮 |
Checked, defaultChecked |
<select> |
Selected, defaultValue |
注意
在 HTML <textarea>
组件中,值是通过子元素设置的,但在 React 中可以通过value
设置。onChange
属性由所有原生组件支持,例如其他 DOM 事件,并且可以监听所有冒泡变化事件。
onChange
属性在用户交互和更改时在浏览器中起作用:
-
<input>
和<textarea>
的value
-
<input>
类型的radio
和checkbox
的checked
状态 -
<option>
组件的selected
状态
在本章中,我们将演示如何使用我们刚刚查看的属性(prop)和状态来控制组件。然后,我们将看看如何从组件中应用它们来控制行为。
受控组件
我们要看的第一个组件是控制用户输入到textarea
中的组件,当字符达到最大长度时阻止用户输入;它还会在用户输入时更新剩余字符:
render: function() {
return <textarea className="form-control" value="fdgdfgd" />;
}
在上述代码中,我们声明了textarea
的值,因此当用户输入时,它不会对textarea
的值进行更改。要控制这一点,我们需要使用onChange
事件:
var style = {color: "#ffaaaa"};
var max_Char='140';
var Teaxtarea = React.createClass({
getInitialState: function() {
return {value: 'Controlled!!!', char_Left: max_Char};
},
handleChange: function(event) {
var input = event.target.value;
this.setState({value: input});
},
render: function() {
return (
<form>
<div className="form-group">
<label htmlFor="comments">Comments <span style=
{style}>*</span></label>(<span>
{this.state.char_Left}</span> characters left)
<textarea className="form-control" value=
{this.state.value} maxLength={max_Char} onChange=
{this.handleChange} />
</div>
</form>
);
}
})
观察以下截图:
在上述截图中,我们接受并控制用户提供的值,并更新<textarea>
组件的prop
值。
注意
this.state()
应该只包含表示 UI 状态所需的最小数据量。
但现在我们还想在<span>
中更新textarea
的剩余字符:
this.setState({
value: input.substr(0, max_Char),char_Left: max_Char -
input.length
});
在上述代码中,this
将控制textarea
的剩余值,并在用户输入时更新剩余字符。
不受控组件
正如我们在 ReactJS 中所见,使用value
属性时,我们可以控制用户输入,所以没有value
属性的<textarea>
是一个非受控组件:
render: function() {
return <textarea className="form-control"/>
}
这将渲染一个带有空值的textarea
,用户可以输入值,这些值会立即反映在渲染的元素上,因为非受控组件有自己的内部状态。如果要初始化默认值,我们需要使用defaultValue
属性:
render:function() {
return <textarea className="form-control" defaultValue="Lorem
lipsum"/>
}
看起来像是受控组件,我们之前见过的。
在提交时获取表单值
正如我们所见,state
和prop
将让您控制组件的值并处理该组件的状态。
好的,现在让我们在我们的添加票务表单中添加一些高级功能,它可以验证用户输入并在 UI 上显示票务。
Ref 属性
React 提供了ref
非 DOM 属性来访问组件。ref
属性可以是一个回调函数,在组件挂载后立即执行。
所以我们将在我们的表单元素中附加ref
属性来获取值:
var AddTicket = React.createClass({
handleSubmitEvent: function (event) {
event.preventDefault();
console.log("Email--"+this.refs.email.value.trim());
console.log("Issue Type--"+this.refs.issueType.value.trim());
console.log("Department--"+this.refs.department.value.trim());
console.log("Comments--"+this.refs.comment.value.trim());
},
render: function() {
return (
);
}
});
现在,我们将在return
方法中添加表单元素的 JSX:
<form onSubmit={this.handleSubmitEvent}>
<div className="form-group">
<label htmlFor="email">Email <span style={style}>*</span>
</label>
<input type="text" id="email" className="form-control"
placeholder="Enter email" required ref="email"/>
</div>
<div className="form-group">
<label htmlFor="issueType">Issue Type <span style={style}>*
</span></label>
<select className="form-control" id="issueType" required
ref="issueType">
<option value="">-----Select----</option>
<option value="Access Related Issue">Access Related
Issue</option>
<option value="Email Related Issues">Email Related
Issues</option>
<option value="Hardware Request">Hardware Request</option>
<option value="Health & Safety">Health & Safety</option>
<option value="Network">Network</option>
<option value="Intranet">Intranet</option>
<option value="Other">Other</option>
</select>
</div>
<div className="form-group">
<label htmlFor="department">Assign Department <span style=
{style}>*</span></label>
<select className="form-control" id="department" required
ref="department">
<option value="">-----Select----</option>
<option value="Admin">Admin</option>
<option value="HR">HR</option>
<option value="IT">IT</option>
<option value="Development">Development</option>
</select>
</div>
<div className="form-group">
<label htmlFor="comments">Comments <span style={style}>*</span>
</label>(<span id="maxlength">200</span> characters left)
<textarea className="form-control" rows="3" id="comments"
required ref="comment"></textarea>
</div>
<div className="btn-group">
<button type="submit" className="btn
btn-primary">Submit</button>
<button type="reset" className="btn btn-link">cancel</button>
</div>
</form>
在前面的代码中,我在我们的表单元素上添加了ref
属性和onSubmit
,调用了函数名handleSubmitEvent
。在这个函数内部,我们使用this.refs
来获取值。
现在,打开你的浏览器,让我们看看我们代码的输出:
我们成功地获取了组件的值。很清楚数据是如何在我们的组件中流动的。在控制台中,我们可以看到用户单击提交按钮时表单的值。
现在,让我们在 UI 中显示这张票的信息。
首先,我们需要获取表单的值并管理表单的状态:
var AddTicket = React.createClass({
handleSubmitEvent: function (event) {
event.preventDefault();
var values = {
date: new Date(),
email: this.refs.email.value.trim(),
issueType: this.refs.issueType.value.trim(),
department: this.refs.department.value.trim(),
comment: this.refs.comment.value.trim()
};
this.props.addTicketList(values);
},
)};
现在我们将创建 AddTicketsForm 组件,它将负责管理和保存 addTicketList(值)的状态:
var AddTicketsForm = React.createClass({
getInitialState: function () {
return {
list: {}
};
},
updateList: function (newList) {
this.setState({
list: newList
});
},
addTicketList: function (item) {
var list = this.state.list;
list[item] = item;
//pass the item.id in array if we are using key attribute.
this.updateList(list);
},
render: function () {
var items = this.state.list;
return (
<div className="container">
<div className="row">
<div className="col-sm-6">
<List items={items} />
<AddTicket addTicketList={this.addTicketList} />
</div>
</div>
</div>
);
}
});
让我们看一下前面的代码:
-
getInitialState
:这个属性初始化了<List />
组件的默认状态 -
addTicketList
:这个属性保存数值并传递到updateList
中的状态 -
updateList
:这是用于更新票务列表以使我们的 UI 同步
现在我们需要创建<List items={items} />
组件,当我们提交表单时,它会迭代列表:
var List = React.createClass({
getListOfIds: function (items) {
return Object.keys(items);
},
createListElements: function (items) {
var item;
return (
this
.getListOfIds(items)
.map(function createListItemElement(itemId) {
item = items[itemId];
return (<ListPanel item={item} />);//key={item.id}
}.bind(this))
.reverse()
);
},
render: function () {
var items = this.props.items;
var listItemElements = this.createListElements(items);
return (
<div className="bg-info">
{listItemElements}
</div>
);
}
});
让我们了解一下前面的代码:
-
getListOfIds
:这将遍历项目中的所有键,并返回我们与<ListPanel item={item}/>
组件映射的列表 -
.bind(this)
:this
关键字将作为第二个参数传递,当调用函数时会给出适当的值
在render
方法中,我们只是渲染元素的列表。此外,我们还可以根据render
方法内部的长度添加条件:
<p className={listItemElements.length > 0 ? "":"bg-info"}>
{listItemElements.length > 0 ? listItemElements : "You have not
raised any ticket yet. Fill this form to submit the ticket"}
</p>
它将验证长度,并根据返回值 TRUE 或 FALSE 显示消息或应用 Bootstrap 类.bg-info
。
现在我们需要创建一个<ListPanel />
组件,以在 UI 中显示票务列表:
var ListPanel = React.createClass({
render: function () {
var item = this.props.item;
return (
<div className="panel panel-default">
<div className="panel-body">
{item.issueType}<br/>
{item.email}<br/>
{item.comment}
</div>
<div className="panel-footer">
{item.date.toString()}
</div>
</div>
);
}
});
现在,让我们结合我们的代码,看看在浏览器中的结果:
var style = {color: "#ffaaaa"};
var AddTicketsForm = React.createClass({
getInitialState: function () {
return {
list: {}
};
},
updateList: function (newList) {
this.setState({
list: newList
});
},
addTicketList: function (item) {
var list = this.state.list;
list[item] = item;
this.updateList(list);
},
render: function () {
var items = this.state.list;
return (
<div className="container">
<div className="row">
<div className="col-sm-12">
<List items={items} />
<AddTicket addTicketList={this.addTicketList} />
</div>
</div>
</div>
);
}
});
//AddTicketsForm components code ends here
var ListPanel = React.createClass({
render: function () {
var item = this.props.item;
return (
<div className="panel panel-default">
<div className="panel-body">
{item.issueType}<br/>
{item.email}<br/>
{item.comment}
</div>
<div className="panel-footer">
{item.date.toString()}
</div>
</div>
);
}
});
// We'll wrap ListPanel component in List
var List = React.createClass({
getListOfIds: function (items) {
return Object.keys(items);
},
createListElements: function (items) {
var item;
return (
this
.getListOfIds(items)
.map(function createListItemElement(itemId) {
item = items[itemId];
return (
<ListPanel item={item} />
);//key={item.id}
}.bind(this))
.reverse()
);
},
render: function () {
var items = this.props.items;
var listItemElements = this.createListElements(items);
return (
<p className={listItemElements.length > 0 ? "":"bg-info"}>
{listItemElements.length > 0 ? listItemElements : "You
have not raised any ticket yet. Fill this form to submit
the ticket"}
</p>
);
}
});
在上述代码中,我们正在迭代项目并将其作为 props 传递给<Listpanel/>
组件:
var AddTicket = React.createClass({
handleSubmitEvent: function (event) {
event.preventDefault();
var values = {
date: new Date(),
email: this.refs.email.value.trim(),
issueType: this.refs.issueType.value.trim(),
department: this.refs.department.value.trim(),
comment: this.refs.comment.value.trim()
};
this.props.addTicketList(values);
},
render: function() {
return (
// Form template
ReactDOM.render(
<AddTicketsForm />,
document.getElementById('form')
);
以下是我们 HTML 页面的标记。
<link rel="stylesheet" href="css/bootstrap.min.css">
<style type="text/css">
div.bg-info {
padding: 15px;
}
</style>
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-6">
<h2>Add Ticket</h2>
<hr/>
</div>
</div>
</div>
<div id="form">
</div>
<script type="text/javascript" src="js/react.js"></script>
<script type="text/javascript" src="js/react-dom.js"></script>
<script src="js/browser.min.js"></script>
<script src="component/advance-form.js" type="text/babel"></script>
</body>
打开您的浏览器,让我们在提交之前看看我们表单的输出:
以下截图显示了提交表单后的外观:
看起来不错。我们的第一个完全功能的 React 组件已经准备好了。
注意
永远不要在任何组件内部访问refs
,也不要将它们附加到无状态函数。
观察以下截图:
我们收到此警告消息是因为 React 的key
(可选)属性,它接受一个唯一的 ID。每当我们提交表单时,它将迭代List
组件以更新 UI。例如:
createListElements: function (items) {
var item;
return (
this
.getListOfIds(items)
.map(function createListItemElement(itemId,id) {
item = items[itemId];
return (<ListPanel key={id} item={item} />);
}.bind(this))
.reverse()
);
},
React 提供了 add-ons 模块来解决这种类型的警告并生成唯一的 ID,但它只在 npm 中可用。在后续章节中,我们将展示如何使用 React npm 模块。以下是一些流行的 add-ons 列表:
-
TransitionGroup
和CSSTransitionGroup
:用于处理动画和过渡 -
LinkedStateMixin
:使用户的表单输入数据和组件状态之间的交互变得容易 -
cloneWithProps
:更改组件的 props 并进行浅拷贝 -
createFragment
:用于创建一组外部键控的子元素 -
Update
:一个帮助函数,使在 JavaScript 中处理数据变得容易 -
PureRenderMixin:
性能增强器 -
shallowCompare:
一个帮助函数,用于对 props 和 state 进行浅比较
Bootstrap 辅助类
Bootstrap 提供了一些辅助类,以提供更好的用户体验。在AddTicketsForm
表单组件中,我们使用了 Bootstrap 辅助类*-info
,它可以帮助屏幕阅读器以颜色传达消息的含义。其中一些是*-muted
,*-primary
,*-success
,*-info
,*-warning
和*-danger
。
要更改文本的颜色,我们可以使用.text*
:
<p class="text-info">...</p>
要更改背景颜色,我们可以使用.bg*
:
<p class="bg-info">...</p>
插入符
要显示指示下拉方向的插入符,我们可以使用:
<span class="caret"></span>
清除浮动
通过在父元素上使用clearfix
,我们可以清除子元素的浮动:
<div class="clearfix">...
<div class="pull-left"></div>
<div class="pull-right"></div>
</div>
总结
在本章中,我们已经看到 props 和 state 在使组件交互以及在 DOM 交互中发挥重要作用。Refs 是与 DOM 元素交互的好方法。通过流式响应 props 和 state 来做这件事将会很不方便。借助 refs,我们可以调用任何公共方法并向特定的子实例发送消息。
本章中展示的关键示例将帮助您理解和澄清有关 props、state 和 DOM 交互的概念。
最后一个示例涵盖了使用多个 JSX 组件和 Bootstrap 创建高级添加票据表单,这将为您提供更多关于创建 React 组件以及如何使用 refs 与它们交互的想法。您可以像操作 HTML 一样轻松地使用它和调整它。
如果您仍然不确定 state 和 props 的工作原理以及 React 如何与 DOM 交互,我建议您再次阅读本章,这也将在您查看未来章节时帮助您。
如果您已经完成了,那么让我们继续阅读第五章,“React 中的 jQuery Bootstrap 组件”,这一章主要讲述了 React 中的 Redux 架构。
第五章:React 中的 jQuery Bootstrap 组件
到目前为止,我们已经介绍了如何创建 DOM 元素以及 DOM 如何与 React 组件交互。正如我们所见,每个框架都有不同的方式与 DOM 元素交互,而 React 使用快速的内部合成 DOM 来执行差异并为您计算最有效的 DOM 变化,这是您的组件实际存在的地方。
在本章中,我们将看看 jQuery Bootstrap 组件在 React 虚拟 DOM 中是如何工作的。我们还将涵盖以下主题:
-
组件生命周期方法
-
组件集成
-
Bootstrap 模态框
-
具体示例
这将让您更好地理解如何处理 React 中的 jQuery Bootstrap 组件。
在 Bootstrap 中,我们有很多可重用的组件,使开发人员的生活更轻松。在第一章和第二章中,我们解释了 Bootstrap 的集成。所以让我们从一个小组件开始,将其集成到 React 中。
警报
在 Bootstrap 中,我们有alert
组件来根据用户操作在 UI 中显示消息,使您的组件更具交互性。
首先,我们需要将文本包裹在包含close
按钮的.alert
类中。
Bootstrap 还提供了表示不同颜色的上下文类,根据消息的不同而不同:
-
.alert-success
-
.alert-info
-
.alert-warning
-
.alert-error
用法
Bootstrap 为我们提供了alert
组件的预定义结构,这使得将其包含在我们的项目中变得容易:
<div class="alert alert-info alert-dismissible fade in" role="alert">
<button type="button" class="close" data-dismiss="alert"
aria-label="Close">
<span aria-hidden="true">×</span>
</button>
</div>
当我们将close
按钮用作我们声明了alert
类的包装标记的子元素时,我们需要向该元素添加.alert-dismissible
类,就像前面的示例代码中所示的那样。
添加自定义属性data-dismiss="alert"
将为我们在alert
中提供close
功能。
在 React 中的 Bootstrap 警报组件
现在我们将把 Bootstrap alert
组件与我们在第四章中开发的 React 受控组件(textarea
)集成起来,ReactJS 中的 DOM 交互,在那里我们开发了一个带有受控组件的表单。我们通过一个示例防止用户在textarea
中写入超过140
个字符的文本。
在以下示例中,我们将看到如何将警报/警告消息与相同的组件绑定。在这里,我们只是扩展了开发的受控组件。
您可能还在第四章中看到了以下截图,显示了带有textarea
中注释的受控组件。在括号中,您可以看到定义的字符限制:
添加alert
组件后,当用户达到最大字符限制时,它将显示在 UI 中。
为此,首先,我们需要将 Bootstrap 组件包装到 React 结构中。让我们通过实际示例来了解一下:
var BootstrapAlert = React.createClass({
render: function() {
return (
<div className={(this.props.className) + ' alert'}
role="alert" ref="alertMsg">
<button type="button" className="close"
data-dismiss="alert" aria-label="Close" onClick=
{this.handleClose}>
<span aria-hidden="true">×</span></button>
<strong>Ooops!</strong> You reached the max limit
</div>
);
}
});
我们创建了一个名为BootstrapAlert
的组件,并将 HTML 包装在render
方法中。
onClick
调用handleClose
函数,该函数将处理close
事件。这是 React 的默认函数,因为我们在 JavaScript 中有.show()
和.hide()
默认函数。
在我们集成 jQuery Bootstrap 组件之前,我们必须了解组件中的 React 生命周期方法。
组件生命周期方法
在 React 中,每个组件都有自己特定的回调函数。当我们考虑 DOM 操作或在 React(jQuery)中集成其他插件时,这些回调函数起着重要作用。让我们看一些组件生命周期中常用的方法:
-
getInitialState()
: 此方法将帮助您获取组件的初始状态。 -
componentDidMount
:此方法在组件在 DOM 中首次渲染或挂载时自动调用。在集成 JavaScript 框架时,我们将使用此方法执行操作,例如setTimeout
或setInterval
,或发送 AJAX 请求。 -
componentWillReceiveProps
:此方法将用于接收新的props
。
注意
没有替代方法,如componentWillReceiveState
。如果我们需要在state
更改时执行操作,那么我们使用componentWillUpdate
。
-
componentWillUnmount
:此方法在组件从 DOM 中卸载之前调用。清理在componentDidMount
方法中挂载的 DOM 内存元素。 -
componentWillUpdate
:此方法在更新新的props
和state
之前调用。 -
componentDidUpdate
:在组件在 DOM 中更新后立即调用此方法。
组件集成
我们现在了解了组件的生命周期方法。现在让我们使用这些方法在 React 中集成我们的组件。请观察以下代码:
componentDidMount: function() {
// When the component is mount into the DOM
$(this.refs.alertMsg).hide();
// Bootstrap's alert events
// functionality. Lets hook into one of them:
$(this.refs.alertMsg).on('closed.bs.alert', this.handleClose);
},
componentWillUnmount: function() {
$(this.refs.alertMsg).off('closed.bs.alert', this.handleClose);
},
show: function() {
$(this.refs.alertMsg).show();
},
close: function() {
$(this.refs.alertMsg).alert('close');
},
hide: function() {
$(this.refs.alertMsg).hide();
},
render: function() {
return (
<div className={(this.props.className) + ' alert'} role="alert"
ref="alertMsg">
<button type="button" className="close" data-dismiss="alert"
aria-label="Close" onClick={this.handleClose}>
<span aria-hidden="true">x</span></button>
<strong>Oh snap!</strong> You reached the max limit
</div>
);
},
});
让我们看一下上述代码的解释:
-
componentDidMount()
默认情况下使用refs
关键字在组件挂载到 DOM 时隐藏alert
组件 -
alert
组件为我们提供了一些在调用close
方法时被调用的事件 -
当调用
close
方法时,将调用close.bs.alert
当我们使用componentWillUnmount
组件时,也使用 jQuery 的.off
来移除事件处理程序。当我们点击关闭(x)按钮时,它会调用 Closehandler 并调用 close
我们还创建了一些控制我们组件的自定义事件:
-
.hide()
: 用于隐藏组件 -
.show()
: 用于显示组件 -
.close()
: 用于关闭警报
请观察以下代码:
var Teaxtarea = React.createClass({
getInitialState: function() {
return {value: 'Controlled!!!', char_Left: max_Char};
},
handleChange: function(event) {
var input = event.target.value;
this.setState({value: input.substr(0, max_Char),char_Left:
max_Char - input.length});
if (input.length == max_Char){
this.refs.alertBox.show();
}
else{
this.refs.alertBox.hide();
}
},
handleClose: function() {
this.refs.alertBox.close();
},
render: function() {
var alertBox = null;
alertBox = (
<BootstrapAlert className="alert-warning fade in"
ref="alertBox" onClose={this.handleClose}/>
);
return (
<div className="example">
{alertBox}
<div className="form-group">
<label htmlFor="comments">Comments <span style=
{style}>*</span></label(<span{this.state.char_Left}
</span> characters left)
<textarea className="form-control" value=
{this.state.value} maxLength={max_Char} onChange=
{this.handleChange} />
</div>
</div>
);
}
});
ReactDOM.render(
<Teaxtarea />,
document.getElementById('alert')
);
使用if
条件,根据字符长度隐藏和显示警报。handleClose()
方法将调用我们之前创建的close
方法来关闭警报。
在render
方法中,我们使用className
属性、ref
键和onClose
属性来渲染我们的组件以处理close
方法。
类中的.fade
给我们在关闭警报时提供了淡出效果。
现在让我们结合我们的代码,快速在浏览器中查看一下:
'use strict';
var max_Char='140';
var style = {color: "#ffaaaa"};
var BootstrapAlert = React.createClass({
componentDidMount: function() {
// When the component is added
$(this.refs.alertMsg).hide();
// Bootstrap's alert class exposes a few events for hooking
into modal
// functionality. Lets hook into one of them:
$(this.refs.alertMsg).on('closed.bs.alert', this.handleClose);
},
componentWillUnmount: function() {
$(this.refs.alertMsg).off('closed.bs.alert', this.
handleClose);
},
show: function() {
$(this.refs.alertMsg).show();
},
close: function() {
$(this.refs.alertMsg).alert('close');
},
hide: function() {
$(this.refs.alertMsg).hide();
},
render: function() {
return (
<div className={(this.props.className) + ' alert'}
role="alert" ref="alertMsg">
<button type="button" className="close"
data-dismiss="alert" aria-label="Close" onClick=
{this.handleClose}>
<span aria-hidden="true">×</span></button>
<strong>oops!</strong> You reached the max limit
</div>
);
}
});
var Teaxtarea = React.createClass({
getInitialState: function() {
return {value: '', char_Left: max_Char};
},
handleChange: function(event) {
var input = event.target.value;
this.setState({value: input.substr(0, max_Char),char_Left:
max_Char - input.length});
if (input.length == max_Char){
this.refs.alertBox.show();
}
else{
this.refs.alertBox.hide();
}
},
handleClose: function() {
this.refs.alertBox.close();
},
render: function() {
var alertBox = null;
alertBox = (
<BootstrapAlert className="alert-warning fade in"
ref="alertBox"/>
);
return (
<div className="example">
{alertBox}
<div className="form-group">
<label htmlFor="comments">Comments <span style=
{style}>*</span></label>(<span
{this.state.char_Left}</span> characters left)
<textarea className="form-control" value=
{this.state.value} maxLength={max_Char} onChange=
{this.handleChange} />
</div>
</div>
);
}
});
ReactDOM.render(
<Teaxtarea />,
document.getElementById('alert')
);
请观察以下截图:
当我们点击关闭(x)按钮时,它会调用Closehandler
并调用close
事件来关闭警报消息。一旦关闭,您将需要刷新页面才能重新打开它。请观察以下截图:
注意
使用console.log()
,我们可以验证我们的组件是否已挂载或卸载。
现在让我们看一下 Bootstrap 组件的另一个示例。
Bootstrap 模态框
Bootstrap 模态组件向用户显示少量信息,而不会将您带到新页面。
来自 Bootstrap 网站(getbootstrap.com/javascript
)的下表显示了模态框可用的全部选项:
名称 | 类型 | 默认 | 描述 |
---|---|---|---|
backdrop |
布尔值 | true |
backdrop 允许我们在用户点击外部时关闭模态框。它为backdrop 提供了一个静态值,不会在点击时关闭模态框。 |
keyboard |
布尔值 | true |
按下Esc键关闭模态框。 |
show |
布尔值 | true |
初始化模态框。 |
remote |
路径 | false |
自 3.3.0 版本起,此选项已被弃用,并在 4 版本中已删除。建议使用数据绑定框架进行客户端模板化,或者自己调用jQuery.load 。 |
Bootstrap 网站(getbootstrap.com/javascript
)的以下表格显示了 Bootstrap 模态组件可用的完整事件列表:
事件类型 | 描述 |
---|---|
show.bs.modal |
当调用show ($('.modal').show(); )实例方法时,立即触发此事件。 |
shown.bs.modal |
当模态框对用户可见时触发此事件(我们需要等待 CSS 过渡完成)。 |
hide.bs.modal |
当调用hide ($('.modal').hide(); )实例方法时,立即触发此事件。 |
hidden.bs.modal |
当模态框从用户那里隐藏完成时触发此事件(我们需要等待 CSS 过渡完成)。 |
loaded.bs.modal |
当模态框使用remote 选项加载内容时触发此事件。 |
每当我们集成任何其他组件时,我们必须了解库或插件提供的组件选项和事件。
首先,我们需要创建一个button
组件来打开一个modal
弹出窗口:
// Bootstrap's button to open the modal
var BootstrapButton = React.createClass({
render: function() {
return (
<button {...this.props}
role="button"
type="button"
className={(this.props.className || '') + ' btn'} />
);
}
});
现在,我们需要创建一个modal-dialog
组件,并将button
和dialog
组件挂载到 DOM 中。
我们还将创建一些处理show
和hide
模态框事件的事件:
var BootstrapModal = React.createClass({
componentDidMount: function() {
// When the component is mount into the DOM
$(this.refs.root).modal({keyboard: true, show: false});
// capture the Bootstrap's modal events
$(this.refs.root).on('hidden.bs.modal', this.handleHidden);
},
componentWillUnmount: function() {
$(this.refs.root).off('hidden.bs.modal', this.handleHidden);
},
close: function() {
$(this.refs.root).modal('hide');
},
open: function() {
$(this.refs.root).modal('show');
},
render: function() {
var confirmButton = null;
var cancelButton = null;
if (this.props.confirm) {
confirmButton = (
<BootstrapButton
onClick={this.handleConfirm}
className="btn-primary">
{this.props.confirm}
</BootstrapButton>
);
}
if (this.props.cancel) {
cancelButton = (
<BootstrapButton onClick={this.handleCancel} className=
"btn-default">
{this.props.cancel}
</BootstrapButton>
);
}
return (
<div className="modal fade" ref="root">
<div className="modal-dialog">
<div className="modal-content">
<div className="modal-header">
<button
type="button"
className="close"
onClick={this.handleCancel}>
×
</button>
<h3>{this.props.title}</h3>
</div>
<div className="modal-body">
{this.props.children}
</div>
<div className="modal-footer">
{cancelButton}
{confirmButton}
</div>
</div>
</div>
</div>
);
},
handleCancel: function() {
if (this.props.onCancel) {
this.props.onCancel();
}
},
handleConfirm: function() {
if (this.props.onConfirm) {
this.props.onConfirm();
}
},
handleHidden: function() {
if (this.props.onHidden) {
this.props.onHidden();
}
}
});
在componentDidMount()
中,我们正在使用一些选项初始化modal
组件,并将hidden.bs.modal
事件注入modal
中。
close()
和show()
函数触发模态框的hide
/show
事件。
在render()
方法中,我们包含了带有props
和ref
键的模态框 HTML 模板来操作模板。
handleCancel()
,handleConfirm()
和handleHidden()
处理我们组件的每个状态。
.modal-*
类为我们提供了 Bootstrap 的样式,使我们的应用更加用户友好。
现在我们需要使用render
函数来渲染我们的组件:
var ReactBootstrapModalDialog = React.createClass({
handleCancel: function() {
if (confirm('Are you sure you want to cancel the dialog
info?')) {
this.refs.modal.close();
}
},
render: function() {
var modal = null;
modal = (
<BootstrapModal
ref="modal"
confirm="OK"
cancel="Cancel"
onCancel={this.handleCancel}
onConfirm={this.closeModal}
onHidden={this.handleModalDidClose}
>
This is a React component powered by jQuery and Bootstrap!
</BootstrapModal>
);
return (
{modal}
<BootstrapButton onClick={this.openModal} className="btn-
default">
Open modal
</BootstrapButton>
);
},
openModal: function() {
this.refs.modal.open();
},
closeModal: function() {
this.refs.modal.close();
},
handleModalDidClose: function() {
alert("The modal has been dismissed!");
}
});
我们在<BootstrapModal>
中传递props
并渲染<BootstrapButton>
。
使用this
关键字,我们调用一个函数来调用modal
事件并在每次事件触发时显示警报:
ReactDOM.render(<ReactBootstrapModalDialog />,
document.getElementById('modal'));
让我们快速在浏览器中查看一下我们的组件:
哎呀!我们出现了一个错误。我认为这可能是因为我们没有将组件包裹在render
方法内。它应该始终与一个父元素一起包装:
return (
<div className="modalbtn">
{modal}
<BootstrapButton onClick={this.openModal} className="btn-default">
Open modal
</BootstrapButton>
</div>
);
这是我们在做了一些小改动后ReactBootstrapModalDialog
组件的样子:
var ReactBootstrapModalDialog = React.createClass({
handleCancel: function() {
if (confirm('Are you sure you want to cancel?')) {
this.refs.modal.close();
}
},
render: function() {
var modal = null;
modal = (
<BootstrapModal
ref="modal"
confirm="OK"
cancel="Cancel"
onCancel={this.handleCancel}
onConfirm={this.closeModal}
onHidden={this.handleModalDidClose}
>
This is a React component powered by jQuery and
Bootstrap!
</BootstrapModal>
);
return (
<div className="modalbtn">
{modal}
<BootstrapButton onClick={this.openModal}
className="btn-default">
Open modal
</BootstrapButton>
</div>
);
},
openModal: function() {
this.refs.modal.open();
},
closeModal: function() {
this.refs.modal.close();
},
handleModalDidClose: function() {
alert("The modal has been dismissed!");
}
});
ReactDOM.render(<ReactBootstrapModalDialog />, document.getElementById('modal'));
让我们再次在浏览器中快速查看我们的组件:
现在点击打开模态按钮查看模态对话框:
如果我们点击取消或确定按钮,它将显示警报框,如下截图所示:
如果我们点击X图标,它将显示警报框,如下截图所示:
所以,现在我们知道我们可以通过点击(X)图标来关闭模态对话框。
当模态对话框关闭时,它会显示警报,模态已被解除! 请参见下面的截图:
这是我们的 HTML 文件的样子:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>J-Query Bootstrap Component with React</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
</head>
<body>
<div class="container">
<div class="row">
<div class="col-sm-6">
<h2>jQuery Bootstrap Modal with React</h2>
<hr/>
<div id="modal">
</div>
</div>
</div>
</div>
<script type="text/javascript" src="js/jquery-1.10.2.min.js">
</script>
<script type="text/javascript" src="js/bootstrap.min.js">
</script>
<script type="text/javascript" src="js/react.min.js"></script>
<script type="text/javascript" src="js/react-dom.min.js">
</script>
<script src="js/browser.min.js"></script>
<script src="component/modal.js" type="text/babel"></script>
</body>
</html>
摘要
我们已经看到了如何在 React 中集成 jQuery Bootstrap 组件以及在集成任何第三方插件(如 jQuery)时生命周期方法的工作方式。
我们能够通过处理事件的 props 来检查组件状态,并显示具有适当内容的对话框。我们还看了生命周期方法如何帮助我们集成其他第三方插件。
我们现在了解了组件的生命周期方法以及它们在 React 中的工作方式。我们已经学会了如何在 React 中集成 jQuery 组件。我们已经看到了事件处理机制以及警报和模态组件的示例。
本章中展示的关键示例将帮助您了解或澄清在 React 中集成 jQuery Bootstrap 组件的概念。
让我们继续阅读第六章,Redux 架构,这一章主要讲述在 React 中使用 Redux 架构。
第六章:Redux 架构
在之前的章节中,我们学习了如何创建自定义组件,与 React 进行 DOM 交互,以及如何在 React 中使用 JSX,这些都足以让你对 React 及其在不同平台上的变化有足够的了解,例如添加工单表单应用程序的实际示例。现在我们将进入一个高级水平,这将让你进一步了解 JavaScript 应用程序中的状态管理。
Redux 是什么?
正如我们所知,在单页面应用程序(SPAs)中,当我们需要处理状态和时间时,很难掌握随时间变化的状态。在这里,Redux 非常有帮助。为什么?因为在 JavaScript 应用程序中,Redux 处理两种状态:一种是数据状态,另一种是 UI 状态,这是 SPAs 的标准选项。此外,请记住,Redux 可以与 AngularJS、jQuery 或 React JS 库或框架一起使用。
Redux 是什么意思?简而言之,Redux 是在开发 JavaScript 应用程序时处理状态的助手。
我们在之前的例子中看到,数据只能从父级流向子级,这被称为单向数据流。React 也有相同的数据流方向,从数据到组件,因此在这种情况下,React 中的两个组件之间要进行正确的通信会非常困难。
我们可以在下面的图表中清楚地看到:
正如我们在前面的图表中所看到的,React 并不遵循两个组件之间的直接通信,尽管它具有提供该策略的功能。然而,这被认为是不良实践,因为它可能导致不准确性,而且这是一种很难理解的古老写法。
但这并不意味着在 React 中无法实现,因为它提供了另一种替代方法,但根据你的逻辑和 React 的标准,你必须加以处理。
要实现两个没有父子关系的组件之间的相同效果,你必须定义一个全局事件系统,让它们进行通信;Flux 可能是最好的例子。
这就是 Redux 的作用,它提供了一种将所有状态存储到一个组件可以访问的地方的方法,这个地方被称为STORE。简而言之,每当任何组件发现任何更改时,它必须首先分派到存储中,如果其他组件需要访问,它必须从存储中订阅。它不能直接授权与该组件的通信,如下图所示:
在前面的图表中,我们可以看到STORE假装成为应用程序中所有状态修改的中介,Redux 通过STORE控制两个组件之间的直接通信,只有一个通信点。
你可能认为组件之间的通信可以通过其他策略实现,但不建议这样做,因为这样做要么会导致错误的代码,要么会难以跟踪:
现在很清楚 Redux 是如何通过将所有状态更改分派到STORE而不是在组件内部进行通信来简化生活的。现在组件只需要考虑分派状态更改;所有其他责任将属于STORE。
Flux 模式也是这样做的。你可能听说过 Redux 受 Flux 启发,所以让我们看看它们有多相似:
比较 Redux 和 Flux,Redux 是一个工具,而 Flux 只是一个模式,你不能用来即插即用,也不能下载。我不否认 Redux 与 Flux 模式有一些相似之处,但它并不完全与 Flux 相同。
让我们看一些区别。
Redux 遵循三个指导原则,如下面的描述所示,这也将涵盖 Redux 和 Flux 之间的区别。
单个存储方法
我们在之前的图表中看到,存储假装成为应用程序中所有状态修改的中介,Redux 通过存储控制两个组件之间的直接通信,充当单一的通信点。
这里 Redux 和 Flux 的区别在于:Flux 有多个存储方法,而 Redux 有单个存储方法。
只读状态
在 React 应用程序中,组件不能直接更改状态,而必须通过actions将更改分派到存储中。
在这里,store
是一个对象,它有四种方法,如下所示:
-
store.dispatch
(action) -
store.subscribe
(监听器) -
store.getState()
-
replaceReducer
(下一个 Reducer)
您可能已经了解 JavaScript 中的get
和set
属性:set
属性设置对象,get
属性获取对象。但是使用store
方法时,只有get
方法,因此只有一种方法可以通过动作分派更改来设置状态。
以下代码显示了 JavaScript Redux 的示例:
var action = {
type: 'ADD_USER',
user: {name: 'Dan'}
};
// Assuming a store object has been created already
store.dispatch(action);
在这里,动作意味着dispatch()
,其中store
方法将发送一个对象来更新状态。在上述代码片段中,action
采用type
数据来更新状态。您可以根据组件的需要设计不同的方式来设置您的动作。
Reducer 函数用于更改状态
Reducer 函数将处理dispatch
动作以改变状态,因为 Redux 工具不允许两个组件之间的直接通信,因此它也不会改变状态,而是将dispatch
动作描述为状态更改。
在下面的代码片段中,您将看到Reducer
如何通过允许当前状态作为参数并返回新状态来改变state
:
Javscript:
// Reducer Function
varsomeReducer = function(state, action) {
...
return state;
}
这里的 Reducer 可以被视为纯函数。以下是编写Reducer
函数的一些特征:
-
没有外部数据库或网络调用
-
根据其参数返回值
-
参数是不可变的
-
相同的参数返回相同的值
Reducer 函数被称为纯函数,因为它们除了根据其设置的参数纯粹返回值之外,什么都不做;它们没有其他后果。
Redux 的架构
正如我们所讨论的,Redux 受 Flux 模式的启发,因此也遵循其架构。这意味着状态变化将被发送到存储库,并且存储库将处理动作以在组件之间进行通信。
让我们看看数据和逻辑是如何通过以下图表工作的:
观察以下要点以了解 Redux 架构:
-
您可以在上图中看到,在右下方,组件触发动作。
-
状态突变将以与 Flux 请求中相同的方式发生,并且可能会有另一个效果作为API请求。
-
中间件在这里扮演着重要角色,比如处理监听承诺状态的操作以及采取新的行动。
-
Reducers负责作为中间件处理动作。
-
Reducer作为中间件获取所有动作请求,它还与数据相关联。它有权通过定义新状态全局更改应用程序存储中的状态。
-
当我们说状态改变时,这涉及重新选择其选择器并转换数据并通过组件传递。
-
当组件获得更改请求时,相应地,它会将 HTML 呈现给 DOM 元素。
在我们继续之前,我们必须了解流程,以确保结构顺畅。
Redux 的架构优势
与其他框架相比,Redux 具有更多的优势:
-
它可能没有其他副作用
-
正如我们所知,不需要绑定,因为组件不能直接交互
-
状态是全局管理的,因此出现管理不善的可能性较小
-
有时,对于中间件来说,管理其他副作用可能会很困难
从上述观点来看,Redux 的架构非常强大,而且具有可重用性。让我们看一个实际的例子,看看 Redux 如何与 React 一起工作。
我们将在 Redux 中创建我们的 Add Ticket 表单应用程序。
Redux 设置
让我们从 Redux 中的UserList
示例开始。首先,创建一个带有应用程序的目录。我们在这个示例中使用 Node.js 服务器和 npm 包,因为 Redux 模块不能独立使用。
安装 Node.js
首先,如果我们尚未在系统中安装 Node.js,我们必须下载并安装 Node.js。我们可以从nodejs.org
下载 Node.js。它包括 npm 包管理器。
设置完成后,我们可以检查 Node.js 是否设置正确。打开命令提示窗口并运行以下命令:
**node --version**
您应该能够看到版本信息,这可以确保安装成功。
设置应用程序
首先,我们需要为我们的项目创建一个package.json
文件,其中包括项目信息和依赖项。现在,打开命令提示符/控制台,并导航到您创建的目录。运行以下命令:
**Npm init**
这个命令将初始化我们的应用程序,并询问一些问题,以创建一个名为package.json
的 JSON 文件。该实用程序将询问有关项目名称、描述、入口点、版本、作者名称、依赖项、许可信息等的问题。一旦执行了该命令,它将在项目的根目录中生成一个package.json
文件:
{
"name": "react-redux add ticket form example",
"version": "1.0.0",
"description": "",
"scripts": {
"start": "node server.js",
"lint": "eslintsrc"
},
"keywords": [
"react",
"redux",
"redux form",
"reactjs",
"hot",
"reload",
"live",
"webpack"
],
"author": "Harmeet Singh <harmeet.singh090@gmail.com>",
"license": "MiIT",
"devDependencies": {
"babel-core": "⁵.8.3",
"babel-eslint": "⁴.0.5",
"babel-loader": "⁵.3.2",
"css-loader": "⁰.15.6",
"cssnext-loader": "¹.0.1",
"eslint": "⁰.24.1",
"eslint-plugin-react": "³.1.0",
"extract-text-webpack-plugin": "⁰.8.2",
"html-webpack-plugin": "¹.6.1",
"react-hot-loader": "¹.2.7",
"redux-devtools": "¹.0.2",
"style-loader": "⁰.12.3",
"webpack": "¹.9.6",
"webpack-dev-server": "¹.8.2"
},
"dependencies": {
"classnames": "².1.3",
"lodash": "³.10.1",
"react": "⁰.13.0",
"react-redux": "⁰.2.2",
"redux": "¹.0.0-rc"
}
}
好的,让我在开始之前向您解释一些主要工具:
-
webpack-dev-server
:这是用于应用程序实时重新加载的服务器。 -
babel-loader
:这是我们 JavaScript 的编译器。 -
redux-devtools
:这是 Redux 开发的强大工具。在开发中使用此工具将帮助我们监视 DOM UI 中的更新。 -
classnames
:这是一个模块,将帮助我们根据条件应用类。 -
eslint
:这是类似于 JSHint 和 JSLint 用于解析 JavaScript 的工具。
开发工具设置
首先,我们需要创建webpack.config.js
并添加以下代码以启用redux-devtools
:
var path = require('path');
varwebpack = require('webpack');
varExtractTextPlugin = require('extract-text-webpack-plugin');
vardevFlagPlugin = new webpack.DefinePlugin({
__DEV__: JSON.stringify(JSON.parse(process.env.DEBUG || 'true'))
});
module.exports = {
devtool: 'eval',
entry: [
'webpack-dev-server/client?http://localhost:3000',
'webpack/hot/only-dev-server',
'./src/index'
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/static/'
},
plugins: [
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
devFlagPlugin,
new ExtractTextPlugin('app.css')
],
module: {
loaders: [
{
test: /\.jsx?$/,
loaders: ['react-hot', 'babel'],
include: path.join(__dirname, 'src')
},
{ test: /\.css$/, loader: ExtractTextPlugin.extract
('css-loader?module!cssnext-loader') }
]
},
resolve: {
extensions: ['', '.js', '.json']
}
};
现在,创建一个名为src
的目录。在其中,我们需要创建一些文件夹,如下面的截图所示:
Redux 应用程序设置
在每个 Redux 应用程序中,我们都有 actions、reducers、stores 和 components。让我们从为我们的应用程序创建一些 actions 开始。
Actions
Actions 是从我们的应用程序发送数据到我们的 store 的信息的一部分。
首先,我们需要在 actions 文件夹内创建UsersActions.js
文件,并将以下代码放入其中:
import * as types from '../constants/ActionTypes';
export function addUser(name) {
return {
type: types.ADD_USER,
name
};
}
export function deleteUser(id) {
return {
type: types.DELETE_USER,
id
};
}
在上面的代码中,我们创建了两个动作:addUser
和deleteUser
。现在我们需要在constants
文件夹内创建ActionTypes.js
,定义type
:
export constADD_USER = 'ADD_USER';
export constDELETE_USER = 'DELETE_USER';
Reducers
Reducers 处理描述发生了什么事情的 actions,但管理应用程序状态是 reducers 的责任。它们存储先前的state
和action
,并return
下一个state
:
export default function users(state = initialState, action) {
switch (action.type) {
case types.ADD_USER:
constnewId = state.users[state.users.length-1] + 1;
return {
...state,
users: state.users.concat(newId),
usersById: {
...state.usersById,
[newId]: {
id: newId,
name: action.name
}
},
}
case types.DELETE_USER:
return {
...state,
users: state.users.filter(id => id !== action.id),
usersById: omit(state.usersById, action.id)
}
default:
return state;
}
}
Store
我们已经定义了 actions 和 reducers,它们代表了关于发生了什么的事实,以及何时需要根据这些 actions 更新状态。
store
是将 actions 和 reducers 结合在一起的对象。store 有以下职责:
-
保存应用程序状态
-
通过
getState()
和dispatch
(action)允许访问和更新状态。 -
通过
subscribe
(监听器)注册和取消注册监听器
以下是 container 文件夹中UserListApp.js
的代码:
constinitialState = {
users: [1, 2, 3],
usersById: {
1: {
id: 1,
name: 'Harmeet Singh'
},
2: {
id: 2,
name: 'Mehul Bhatt'
},
3: {
id: 3,
name: 'NayanJyotiTalukdar'
}
}
};
import React, { Component, PropTypes } from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as UsersActions from '../actions/UsersActions';
import { UserList, AddUserInput } from '../components';
@connect(state => ({
userlist: state.userlist
}))
export default class UserListApp extends Component {
static propTypes = {
usersById: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired
}
render () {
const { userlist: { usersById }, dispatch } = this.props;
const actions = bindActionCreators(UsersActions, dispatch);
return (
<div>
<h1>UserList</h1>
<AddUserInputaddUser={actions.addUser} />
<UserList users={usersById} actions={actions} />
</div>
);
}
}
在上面的代码中,我们使用UserList
的静态 JSON 数据初始化组件的状态,并使用getstate
、dispatch
(action),然后更新 store 信息。
提示
在 Redux 应用程序中,我们只会有一个单一的 store。当我们需要拆分我们的数据处理逻辑时,我们将使用 reducer 组合而不是多个 store。
Components
这些都是普通的 React JSX 组件,所以我们不需要详细介绍它们。我们已经添加了一些功能状态组件,除非我们需要使用本地状态或生命周期方法,否则我们将使用它们:
在这个(AddUserInput.js
)文件中,我们正在创建一个 JSX 输入组件,从中获取用户输入:
export default class AddUserInput extends Component {
static propTypes = {
addUser: PropTypes.func.isRequired
}
render () {
return (
<input
type="text"
autoFocus="true"
className={classnames('form-control')}
placeholder="Type the name of the user to add"
value={this.state.name}
onChange={this.handleChange.bind(this)}
onKeyDown={this.handleSubmit.bind(this)} />
);
}
constructor (props, context) {
super(props, context);
this.state = {
name: this.props.name || '',
};
}
}
在UserList.js
中,我们正在创建一个列表组件,其中我们迭代Input
组件的值:
export default class UserList extends Component {
static propTypes = {
users: PropTypes.object.isRequired,
actions: PropTypes.object.isRequired
}
render () {
return (
<div className="media">
{
mapValues(this.props.users, (users) => {
return (<UsersListItem
key={users.id}
id={users.id}
name={users.name}
src={users.src}
{...this.props.actions} />);
})
}
</div>
);
}
}
在UserList
组件中迭代值后,我们将在 Bootstrap 的media
布局中显示该列表:
export default class UserListItem extends Component {
static propTypes = {
id: PropTypes.number.isRequired,
name: PropTypes.string.isRequired,
onTrashClick: PropTypes.func.isRequired
}
render () {
return (
<div>
<div className="clearfix">
<a href="#" className="pull-left">
<img className="media-object img-thumbnail"
src={"http://placehold.it/64x64"}/>
</a>
<div className={`media-body ${styles.paddng10}`}>
<h3className="media-heading">
<strong><a href="#">{this.props.name}</a></strong>
</h3>
<p>
Loremipsum dolor sit amet, consecteturadipiscingelit.
Praesentgravidaeuismod ligula,
vel semper nuncblandit sit amet.
</p>
<div className={`pull-right ${styles.userActions}`}>
<button className={`btnbtn-default ${styles.btnAction}`}
onClick={()=>this.props.deleteUser(this.props.id)}
>
Delete the user <iclassName="fafa-trash" />
</button>
</div>
</div>
</div>
</div>
);
}
}
现在,我们需要将我们的组件包装在容器文件夹中的UserListApp.js
中:
import { UserList, AddUserInput } from '../components';
@connect(state => ({
userlist: state.userlist
}))
export default class UserListApp extends Component {
static propTypes = {
usersById: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired
}
render () {
const { userlist: { usersById }, dispatch } = this.props;
const actions = bindActionCreators(UsersActions, dispatch);
return (
<div>
<h1>UserList</h1>
<AddUserInput addUser={actions.addUser} />
<UserList users={usersById} actions={actions} />
</div>
);
}
}
现在,让我们将UserListApp
组件包装到容器文件夹中的App.js
中的 Redux 存储中:
import UserListApp from './UserListApp';
import * as reducers from '../reducers';
const reducer = combineReducers(reducers);
const store = createStore(reducer);
export default class App extends Component {
render() {
return (
<div>
<Provider store={store}>
{() => <UserListApp /> }
</Provider>
{renderDevTools(store)}
</div>
);
}
}
现在转到根目录,打开 CMD,并运行以下命令:
要安装此应用程序所需的软件包,请运行以下命令:
**Npm install**
完成后,运行以下命令:
**Npm start**
观察以下屏幕截图:
看起来很棒。右侧面板是 Redux DevTool,它提供了 UI 的更新。我们可以轻松地看到在此列表中删除或添加用户的更新。
以下屏幕截图显示了从UserList
中删除用户:
以下屏幕截图显示了添加用户的过程:
注意
请参阅第六章的源代码,Redux 架构,以便更好地理解应用程序的流程。
总结
我们现在可以看到 Redux 架构的重要性及其在 React 应用程序中的作用。我们还在本章中学习了状态管理,看看存储如何全局处理状态更改请求,Redux 有助于避免组件之间的直接交互。
本章主要讨论 Redux 架构及其细节。为了澄清,我们已经看到了提供对 Redux 架构中数据和逻辑流程理解的图表。Redux 架构受 Flux 的启发,但它有自己的特点和优势。我们希望图表和实际示例有助于让您了解 Redux 架构。
现在,我们将继续进行下一章,讨论如何在 React 中进行路由。
第七章:使用 React 进行路由
在之前的章节中,我们已经了解了 Redux 架构以及如何处理两种状态,即数据状态和 UI 状态,以创建单页面应用程序或组件。目前,如果需要,我们的应用程序 UI 将与 URL 同步,我们需要使用 React 路由器使我们的应用程序 UI 同步。
React 路由器的优势
让我们看一下 React 路由器的一些优势:
-
以标准化结构查看声明有助于我们立即了解我们的应用视图
-
延迟加载代码
-
使用 React 路由器,我们可以轻松处理嵌套视图和渐进式视图分辨率
-
使用浏览历史功能,用户可以向后/向前导航并恢复视图的状态
-
动态路由匹配
-
导航时视图上的 CSS 过渡
-
标准化的应用程序结构和行为,在团队合作时非常有用
注意
React 路由器不提供任何处理数据获取的方式;我们需要使用asyncProps
或另一种 React 数据获取机制。
在本章中,我们将看看如何创建路由,以及包含参数的路由。在开始之前,让我们计划一下我们的员工信息系统(EIS)需要哪些路由。请查看以下屏幕截图:
上述屏幕截图来自第二章 使用 React-Bootstrap 和 React 构建响应式主题供您参考。
在第二章 使用 React-Bootstrap 和 React 构建响应式主题中,我们为我们的应用程序创建了响应式主题布局。现在我们将在其中添加路由以导航到每个页面。
-
主页:这将是我们的主页,将显示员工的个人资料信息
-
编辑个人资料:在这里,我们将能够编辑员工的信息
-
查看工单:在这个页面,员工将能够查看他提交的工单
-
新工单:在这里,员工可以提交工单
这些都是我们必要的路由;让我们看看如何创建它们。
安装路由器
React 路由器已经作为 React 库之外的不同模块打包。我们可以在 React 路由器 CDN 上使用 React 路由器 CDN:cdnjs.cloudflare.com/ajax/libs/react-router/4.0.0-0/react-router.min.js
。
我们可以像这样将其包含在我们的项目中:
var { Router, Route, IndexRoute, Link, browserHistory } = ReactRouter
或者我们可以使用 React 的npm
包:
**$ npm install --save react-router**
使用 ES6 转译器,比如 Babel:
import { Router, Route, Link } from 'react-router'
不使用 ES6 转译器:
var Router = require('react-router').Router
var Route = require('react-router').Route
var Link = require('react-router').Link
好的,现在让我们设置我们的项目并包括 React 路由器。
应用程序设置
React 路由器看起来与其他 JS 路由器不同。它使用 JSX 语法,这使得它与其他路由器不同。首先,我们将创建一个示例应用程序,而不使用npm
包,以更好地理解路由器的概念。
按照以下说明进行设置:
-
将
第二章
目录结构和文件复制到第七章
中。 -
删除现有的 HTML 文件并创建一个新的
index.html
。 -
在您的 HTML 中复制此样板代码:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React Router - Sample application with
bootstrap</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/font-awesome.min.css">
<link rel="stylesheet" href="css/custom.css">
<script type="text/javascript" src="js/react.js"></script>
<script type="text/javascript" src="js/react-dom.min.js">
</script>
<script src="js/browser.min.js"></script>
<script src="js/jquery-1.10.2.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="https://unpkg.com/react-router/umd/
ReactRouter.min.js"></script>
<script src="components/bootstrap-navbar.js" type=
"text/babel"></script>
<script src="components/sidebar.js" type="text/babel">
</script>
<script src="components/sidebar.js" type="text/babel">
</script>
</head>
<body>
<div id="nav"></div>
<div class="container">
<h1>Welcome to EIS</h1>
<hr>
<div class="row">
<div class="col-sm-3" id="sidebar">
<!--left col-->
</div>
<!--/col-3-->
<div class="col-sm-9 profile-desc" id="main">
</div>
<!--/col-9-->
</div>
</div>
<!--/row-->
</body>
</html>
- 在浏览器中打开
index.html
。确保输出不显示控制台中的任何错误。
创建路由
由于我们已经创建了 HTML,现在我们需要在之前创建的bootstrap-navbar.js
中添加一个 Bootstrap navbar
组件。
为了配置路由,让我们在routing.js
中创建一个组件,它将与 URL 同步:
var homePage = React.createClass({
render: function() {
return (<h1>Home Page</h1>);
}
});
ReactDOM.render((
<homePage />
), document.getElementById('main'));
在浏览器中打开它,看起来是这样的:
让我们添加Router
来渲染我们的homePage
组件与 URL:
ReactDOM.render((
<Router>
<Route path="/" component={homePage} />
</Router>
), document.getElementById('main'));
在前面的例子中,使用<Route>
标签定义了一个规则,访问首页将把homePage
组件渲染到'main'
中。正如我们已经知道的那样,React 路由器使用 JSX 来配置路由。<Router>
和<Route>
是不同的东西。<Router>
标签应该始终是包裹多个 URL 的主要父标签,而<Route>
标签。我们可以声明多个带有属性组件的<Route>
标签,使您的 UI 同步。当历史记录发生变化时,<Router>
将使用匹配的 URL 渲染组件:
ReactDOM.render((
<Router>
<Route path="/" component={homePage} />
<Route path="/edit" component={Edit} />
<Route path="/alltickets" component={allTickets} />
<Route path="/newticket" component={addNewTicket} />
</Router>
), document.getElementById('main'));
看起来非常简单和清晰,路由器将在视图之间切换路由,而不会向服务器发出请求并将它们渲染到 DOM 中。
页面布局
让我们假设如果我们需要为每个组件都需要不同的布局,比如首页应该有两列,其他页面应该有一列,但它们都共享头部和页脚等公共资产。
这是我们应用程序的布局草图:
好的,现在让我们创建我们的主要布局:
var PageLayout = React.createClass({
render: function() {
return (
<div className="container">
<h1>Welcome to EIS</h1>
<hr/>
<div className="row">
<div className="col-md-12 col-lg-12">
{this.props.children}
</div>
</div>
</div>
)
}
})
在上面的代码中,我们已经为我们的应用程序创建了主要布局,它使用this.props.children
来处理子布局组件,而不是硬编码的组件。现在我们将创建在我们的主要布局组件中呈现的子组件:
var RightSection = React.createClass({
render: function() {
return (
<div className="col-sm-9 profile-desc" id="main">
<div className="results">
<PageTitle/>
<HomePageContent/>
</div>
</div>
)
}
})
var ColumnLeft = React.createClass({
render: function() {
return (
<div className="col-sm-3" id="sidebar">
<div className="results">
<LeftSection/>
</div>
</div>
)
}
})
在上面的代码中,我们创建了两个组件,RightSection
和ColumnLeft
,来包装和分割我们的组件在不同的部分。
所以在响应式设计中,我们应该很容易管理布局:
var LeftSection = React.createClass({
render: function() {
return (
React.DOM.ul({ className: 'list-group' },
React.DOM.li({className:'list-group-item
text-muted'},'Profile'),
React.DOM.li({className:'list-group-item'},
React.DOM.a({className:'center-block
text-center',href:'#'},'Image')
),
React.DOM.li({className:'list-group-item text-right'},'2.13.2014',
React.DOM.span({className:'pull-left'},
React.DOM.strong({className:'pull-left'},'Joining Date')
),
React.DOM.div({className:'clearfix'})
))
)
}
})
var TwoColumnLayout = React.createClass({
render: function() {
return (
<div>
<ColumnLeft/>
<RightSection/>
</div>
)
}
})
var PageTitle = React.createClass({
render: function() {
return (
<h2>Home</h2>
);
}
});
在上面的代码中,我们将组件分成了两个部分:<ColumnLeft/>
和<RightSection/>
。我们在<TwoColumnLayout/>
组件中给出了这两个组件的引用。在父组件中,我们有this.props.children
作为一个 prop,但只有当组件是嵌套的时候才起作用,React 会自动负责填充这个 prop。如果组件不是父组件,this.props.children
将为 null。
嵌套路由
好的,我们已经创建了特定布局组件,但我们仍然需要看看如何为它们创建嵌套路由,以便将组件传递给具有 props 的父组件。这很重要,以便在我们的 EIS 应用程序中实现一定程度的动态性。这是我们的 HTML,显示当前的样子:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>React Router - Sample application with bootstrap</title>
<link rel="stylesheet" href="css/bootstrap.min.css">
<link rel="stylesheet" href="css/font-awesome.min.css">
<link rel="stylesheet" href="css/custom.css">
</head>
<body>
<div id="nav"></div>
<div id="reactapp"></div>
<script type="text/javascript" src="js/react.js"></script>
<script type="text/javascript" src="js/react-dom.min.js"></script>
<script src="js/browser.min.js"></script>
<script src="js/jquery-1.10.2.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="https://unpkg.com/react-router/umd/
ReactRouter.min.js"></script>
<script src="components/bootstrap-navbar.js"
type="text/babel"></script>
<script src="components/router.js" type="text/babel"></script>
</body>
</html>
让我们再次看一下我们之前创建的路由器:
ReactDOM.render((
<Router>
<Route path="/" component={PageLayout}>
<IndexRoute component={TwoColumnLayout}/>
<Route path="/edit" component={Edit} />
<Route path="/alltickets" component={allTickets} />
<Route path="/newticket" component={addNewTicket} />
</Route>
</Router>
), document.getElementById('reactapp'));
现在我们已经在与父级的映射中添加了额外的元素<IndexRoute />
,将其视图设置为我们的{TwoColumnLayout}
组件。IndexRoute
元素负责在应用程序初始加载时显示哪个组件。
不要忘记在{PageLayout}
组件中包装。我们还可以在<indexRoute>
上定义路径规则,与<Route>
相同:
ReactDOM.render((
<Router>
<Route component={PageLayout}>
<IndexRoute path="/" component={TwoColumnLayout}/>
<Route path="/edit" component={Edit} />
<Route path="/alltickets" component={allTickets} />
<Route path="/newticket" component={addNewTicket} />
</Route>
</Router>
), document.getElementById('reactapp'));
观察以下截图:
看起来不错。如我们在<IndexRoute>
中提到的,它总是在第一次加载页面时加载<TwoColumnLayout>
。现在让我们导航并查看一些其他页面。
React 还为我们提供了一种使用<IndexRedirect>
组件重定向路由的方法:
<Route path="/" component={App}>
<IndexRedirect to="/welcome" />
<Route path="welcome" component={Welcome} />
<Route path="profile" component={profile} />
</Route>
观察以下截图:
您可能已经注意到,我点击了编辑个人资料页面,它呈现了编辑页面组件,但没有在当前活动链接上添加活动类。为此,我们需要用 React 的<Link>
标签替换<a>
标签。
React 路由
React 路由使用了<link>
组件,而不是我们在nav
中使用的<a>
元素。如果我们使用 React 路由,则必须使用这个。让我们在导航中添加<link>
而不是<a>
标签,并替换href
属性为两个。
<a>
标签:
<li className="active"><a href="#/">Home</a></li>
用这个替换:
<li className="active"><Link to="#/">Home</Link></li>
让我们在浏览器中查看<link>
的行为:
它在控制台中显示错误,因为我们没有在ReactRouter
对象中添加Link
组件引用:
var { Router, Route, IndexRoute, IndexLink, Link, browserHistory } = ReactRouter
我们还添加了browserHistory
对象,稍后我们会解释。
这是我们的PageLayout
组件的样子:
var PageLayout = React.createClass({
render: function() {
return (
<main>
<div className="navbar navbar-default navbar-static-top"
role="navigation">
<div className="container">
<div className="navbar-header">
<button type="button" className="navbar-toggle"
data-toggle="collapse"
data-target=".navbar-collapse">
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
<Link className="navbar-brand" to="/">
EIS</Link>
</div>
<div className="navbar-collapse collapse">
<ul className="nav navbar-nav">
<li className="active">
<IndexLink activeClassName="active" to="/">
Home</IndexLink>
</li>
<li>
<Link to="/edit" activeClassName="active">
Edit Profile</Link>
</li>
<li className="dropdown">
<Link to="#" className="dropdown-toggle"
data-toggle="dropdown">
Help Desk <b className="caret"></b></Link>
<ul className="dropdown-menu">
<li>
<Link to="/alltickets">
View Tickets</Link>
</li>
<li>
<Link to="/newticket">
New Ticket</Link>
</li>
</ul>
</li>
</ul>
</div>
</div>
</div>
<div className="container">
<h1>Welcome to EIS</h1>
<hr/>
<div className="row">
<div className="col-md-12 col-lg-12">
{this.props.children}
</div>
</div>
</div>
</main>
)
}
})
为了激活默认链接,我们使用了<IndexRoute>
。这会自动定义默认链接的活动类。activeClassName
属性将 URL 与to
值匹配并将活动类添加到其中。如果我们不使用activeClassName
,则无法自动在活动链接上添加类。让我们快速看一下浏览器:
它按预期工作。让我们在控制台中查看 DOM HTML:
我们只需要覆盖<li> .active
上的 Bootstrap 默认样式为<a>
:
.navbar-default .navbar-nav li>.active, .navbar-default
.navbar-nav li>.active:hover, .navbar-default
.navbar-nav li>.active:focus {
color: #555;
background-color: #e7e7e7;
}
我们还可以在路由中传递参数来匹配、验证和渲染 UI:
<Link to={`/tickets/${ticket.id}`}>View Tickets</Link>
在路由器中,我们需要添加:
<Route path="tickets/:ticketId" component={ticketDetail} />
我们可以添加尽可能多的参数,并且很容易在我们的组件中提取这些参数。我们将以对象的形式访问所有route
参数。
React 路由支持 IE9+浏览器版本,但对于 IE8,您可以使用 Node npm
包react-router-ie8
NotFoundRoute
React 路由还提供了一种在客户端显示 404 错误的方法,如果路径与路由不匹配:
var NoMatch = React.createClass({
render: function() {
return (<h1>URL not Found</h1>);
}
});
<Route path="*" component={NoMatch}/>
观察以下截图:
我们可以很容易地处理不匹配的 URL,这太棒了。
这是我们的路由器的样子:
ReactDOM.render((
<Router>
<Route path="/" component={PageLayout}>
<IndexRoute component={TwoColumnLayout}/>
<Route path="/edit" component={Edit} />
<Route path="/alltickets" component={allTickets} />
<Route path="/newticket" component={addNewTicket} />
</Route>
<Route path="*" component={NoMatch}/>
</Router>
), document.getElementById('reactapp'));
以下是我们可以使用的其他link
属性列表:
activeStyle
:我们可以用这个来自定义内联样式。例如:
<Link activeStyle={{color:'#53acff'}} to='/'>Home</Link>
onlyActiveOnIndex
:当我们使用activeStyle
属性添加自定义内联样式时,我们可以使用这个属性。它只在我们在精确链接上时应用。例如:
<Link onlyActiveOnIndex activeStyle={{color:'#53acff'}}
to='/'>Home</Link>
浏览器历史
React 路由的另一个很酷的功能是它使用browserHistory
API 来操作 URL 并创建干净的 URL。
使用默认的hashHistory
:
http://localhost:9090/react/chapter7/#/?_k=j8dlzv
http://localhost:9090/react/chapter7/#/edit?_k=yqdzh0 http://localhost:9090/react/chapter7/#/alltickets?_k=0zc49r
http://localhost:9090/react/chapter7/#/newticket?_k=vx8e8c
当我们在我们的应用程序中使用browserHistory
时,URL 看起来很干净:
http://localhost:9090/react/chapter7/
http://localhost:9090/react/chapter7/edit
http://localhost:9090/react/chapter7/alltickets
http://localhost:9090/react/chapter7/newticket
现在 URL 看起来干净且用户友好。
查询字符串参数
我们还可以将查询字符串作为props
传递给将在特定路由上呈现的任何组件。要访问这些 prop 参数,我们需要在我们的组件中添加props.location.query
属性。
要查看这是如何工作的,让我们创建一个名为RouteQueryString
的新组件:
var QueryRoute = React.createClass({
render: function(props) {
return (<h2>{this.props.location.query.message}</h2>);
// Using this we can read the parameters from the
request which are visible in the URL's
}
});
<IndexLink activeClassName='active' to=
{{ pathname: '/query', query: { message: 'Hello from Route Query' } }}>
Route Query
</IndexLink>
在路由器中包含此路由路径:
<Route path='/query' component={QueryRoute} />
让我们在浏览器中看看输出:
很好,它的工作正常。
现在我们的Router
配置看起来是这样的:
ReactDOM.render((
<Router>
<Route path="/" component={PageLayout}>
<IndexRoute component={TwoColumnLayout}/>
<Route path="/edit" component={Edit} />
<Route path="/alltickets" component={allTickets} />
<Route path="/newticket" component={addNewTicket} />
<Route path='/query' component={QueryRoute} />
</Route>
<Route path="*" component={NoMatch}/>
</Router>
), document.getElementById('reactapp'));
进一步定制您的历史记录
如果我们想要定制历史选项或使用历史记录中的其他增强器,那么我们需要使用 React 的useRouterHistory
组件。
useRouterHistory
已经使用useQueries
和useBasename
从历史工厂预增强。示例包括:
import { useRouterHistory } from 'react-router'
import { createHistory } from 'history'
const history = useRouterHistory(createHistory)({
basename: '/base-path'
})
使用useBeforeUnload
增强器:
import { useRouterHistory } from 'react-router'
import { createHistory,useBeforeUnload } from 'history'
const history = useRouterHistory(useBeforeUnload(createHistory))()
history.listenBeforeUnload(function () {
return 'Are you sure you want to reload this page?'
})
在使用 React 路由之前,我们必须了解 React 路由版本更新。
请访问此链接github.com/ReactTraining/react-router/blob/master/upgrade-guides/v2.0.0.md
以获取更新。
以下是路由器中不推荐使用的语法的简短列表:
<Route name="" /> is deprecated. Use <Route path="" /> instead.
<Route handler="" /> is deprecated. Use <Route component="" /> instead.
<NotFoundRoute /> is deprecated. See Alternative
<RouteHandler /> is deprecated.
willTransitionTo is deprecated. See onEnter
willTransitionFrom is deprecated. See onLeave
query={{ the: 'query' }} is deprecated. Use to={{ pathname: '/foo', query: { the: 'query' } }}
history.isActive
被替换为router.isActive
。
RoutingContext
被重命名为RouterContext
。
摘要
在本章中,我们将我们的应用程序从单个页面转换为多个页面和多路由应用程序,我们可以在其上构建我们的 EIS 应用程序。我们首先规划了应用程序中的主要路由,然后创建了一个组件。
然后,我们看了如何使用<Router>
和<Route>
方法设置我们的路由。这是通过var { Router, Route, IndexRoute,IndexLink, Link, browserHistory } = ReactRouter
完成的。我们还看了其他方法:<Link>
、<IndexLink>
和<IndexRoute>
。
这使我们能够设置包含参数的静态和动态路由,使我们的应用程序 UI 与 URL 完美同步。
在下一章中,我们将讨论如何将其他 API 与 React 集成。
第八章:ReactJS API
在之前的章节中,我们学习了 React 路由器,它允许我们创建单页应用程序,并确保我们的 UI 与 URL 同步。我们还介绍了 React 路由器的优势、动态路由匹配以及如何配置路由器中的组件以与匹配的 URL 在 DOM 中呈现。通过 React 路由器浏览器历史功能,用户可以向后/向前导航并恢复应用程序的先前状态。现在我们将检查如何将 React API 与 Facebook、Twitter 和 Git 等其他 API 集成。
React 顶级 API
当我们谈论 React API 时,这是进入 React 库的第一步。不同的 React 用法会提供不同的输出。例如,使用 React 的script
标签将使顶级 API 在React
全局上可用,使用 npm 的 ES6 将允许我们编写import React from 'react'
,而使用 npm 的 ES5 将允许我们编写var React = require('react')
,因此有多种不同特性初始化 React 的方式。
React API 组件
通常,在处理 React 时,我们正在构建适合其他组件的组件,并且我们假设用 React 构建的任何东西都是一个组件。然而,这并不正确。需要有其他一些方法来编写支持代码,以将外部世界与 React 连接起来。观察以下代码片段:
ReactDOM.render(reactElement, domContainerNode)
render
方法用于更新组件的属性,然后我们可以声明一个新元素来再次呈现它。
另一种方法是unmountComponentAtNode
,用于清理你的代码。当我们使用 React 组件构建 SAP 时,我们必须插入unmountComponentAtNode
以在正确的时间启动,从而清理应用程序的生命周期。观察以下代码片段:
ReactDOM.unmountComponentAtNode(domContainerNode)
我经常观察到开发人员不调用unmountComponentAtNode
方法,这导致他们的应用程序出现内存泄漏问题。
挂载/卸载组件
在你的 API 中,建议始终使用自定义包装器 API。假设你有一个或多个根,它将在某个时期被删除,那么在这种情况下,你将不会丢失它。Facebook 就有这样的设置,它会自动调用unmountComponentAtNode
。
我还建议不要每次调用ReactDOM.render()
,而是通过库来编写或使用它的理想方式。在这种情况下,应用程序将使用挂载和卸载来进行管理。
创建一个自定义包装器将帮助您在一个地方管理配置,比如国际化、路由器和用户数据。每次在不同的地方设置所有配置都会非常痛苦。
面向对象编程
如果我们在声明变量下面再次声明它,它将被覆盖,就像ReactDOM.render
覆盖了它的声明属性一样:
ReactDOM.render(<Applocale="en-US"userID={1}/>,container);
// props.userID == 1
// props.locale == "en-US"
ReactDOM.render(<AppuserID={2}/>,container);
// props.userID == 2
// props.locale == undefined ??!?
如果我们只覆盖组件中的一个属性,那么建议使用面向对象编程将覆盖所有声明的属性可能会令人困惑。
您可能会认为我们通常使用setProps
作为辅助函数,以帮助覆盖选择性属性,但由于我们正在使用 React,我们不能使用它;因此,建议在您的 API 中使用自定义包装器。
在下面的代码中,您将看到一个样板,以帮助您更好地理解它:
classReactComponentRenderer{
constructor(componentClass,container){
this.componentClass=componentClass;
this.container=container;
this.props={};
this.component=null;
}
replaceProps(props,callback){
this.props={};
this.setProps(props,callback);
}
setProps(partialProps,callback){
if(this.componentClass==null){
console.warn(
'setProps(...): Can only update a mounted or '+
'mounting component. This usually means you called
setProps() on '+'an unmounted component. This is a no-op.'
);
return;
}
Object.assign(this.props,partialProps);
varelement=React.createElement(this.klass,this.props);
this.component=ReactDOM.render(element,this.container,callback);
}
unmount(){
ReactDOM.unmountComponentAtNode(this.container);
this.klass=null;
}
}
在前面的例子中,似乎我们仍然可以在面向对象的 API 中编写更好的代码,但为此我们必须了解自然的面向对象 API 及其在 React 组件中的使用:
classReactVideoPlayer{
constructor(url,container){
this._container=container;
this._url=url;
this._isPlaying=false;
this._render();
}
_render(){
ReactDOM.render(
<VideoPlayerurl={this._url}playing={this._isPlaying}/>,
this._container
);
}
geturl(){
returnthis._url;
}
seturl(value){
this._url=value;
this._render();
}
play(){
this._isPlaying=true;
this._render();
}
pause(){
this._isPlaying=false;
this._render();
}
destroy(){
ReactDOM.unmountComponentAtNode(this._container);
}
}
我们可以从前面的例子中了解到命令式API 和声明式API 之间的区别。这个例子还展示了我们如何在声明式 API 或反之上提供命令式 API。在使用 React 创建自定义 Web 组件时,我们可以使用声明式 API 作为包装器。
React 与其他 API 的集成
React 集成只是通过使用 JSX、Redux 和其他 React 方法将 Web 组件转换为 React 组件。
让我们看一个 React 与另一个 API 集成的实际例子。
React 与 Facebook API 集成
这个应用将帮助您集成 Facebook API,并且您将可以访问您的个人资料图片以及您在好友列表中有多少个朋友。您还将看到在各自朋友列表中有多少个赞、评论和帖子。
首先,您必须安装 Node.js 服务器并在系统中添加 npm 包。
如果您不知道如何安装 Node.js,请参阅以下说明。
安装 Node
首先,我们必须下载并安装 Node.js 版本 0.12.10,如果我们还没有在系统上安装它。我们可以从nodejs.org
下载 Node.js,它包括 npm 包管理器。
设置完成后,我们可以检查 Node.js 是否设置正确。打开命令提示符并运行以下命令:
**node -v**
或者
**node --version**
这将返回 Node.js 安装的版本,如下所示:
您应该能够看到版本信息,这可以确保安装成功。
安装 Node 后,您将拥有babel-plugin-syntax-object-rest-spread
和babel-plugin-transform-object-rest-spread
。
这两者之间有一个基本的区别:spread
只允许您阅读语法,但transform
将允许您将语法转换回 ES5。
完成此操作后,您将不得不将插件存储到.babelrc
文件中,如下所示:
{
"plugins": ["syntax-object-rest-spread", "transform-object-rest-spread"]
}
设置应用程序
首先,我们需要为我们的项目创建一个package.json
文件,其中包括项目信息和依赖项。现在,打开命令提示符/控制台并导航到您创建的目录。运行以下命令:
**Npm init**
这个命令将初始化我们的应用程序,并在创建一个名为package.json
的 JSON 文件之前询问几个问题。该实用程序将询问有关项目名称、描述、入口点、版本、作者名称、依赖项、许可信息等的问题。一旦执行了该命令,它将在项目的根目录中生成一个package.json
文件。
我已经根据我的要求创建了package.json
文件,如下所示:
{
"name": "facebook-api-integration-with-react",
"version": "1.2.0",
"description": "Web Application to check Like, Comments and
Post of your Facebook Friends,
在上述代码中,您可以看到应用程序的name
,您的应用程序的version
和您的应用程序的description
。观察以下代码片段:
"scripts": {
"lint": "eslint src/ server.js config/ webpack/",
"start": "npm run dev",
"build": "webpack -p --config webpack/webpack.config.babel.js
--progress --colors --define process.env.NODE_ENV='"production"'",
"clean": "rimraf dist/",
"deploy": "npm run clean && npm run build",
"dev": "./node_modules/.bin/babel-node server.js"
},
从上述代码中,您可以设置您的scripts
,以详细说明如何start
您的服务器,如何build
,如何clean
,以及deploy
和dev
。请确保您在各自变量中定义的路径是正确的,否则您的应用程序将无法按预期工作。观察以下代码片段:
"author": "Mehul Bhatt <mehu_multimedia@yahoo.com>",
"license": "MIT",
"keywords": [
"react",
"babel",
"ES6",
"ES7",
"async",
"await",
"webpack",
"purecss",
"Facebook API"
],
上述代码显示了author
名称,license
(如果适用)以及您的应用程序的keywords
。观察以下代码片段:
"devDependencies": {
"babel-cli": "⁶.3.17",
"babel-core": "⁶.3.26",
"babel-eslint": "⁶.0.0",
"babel-loader": "⁶.2.0",
"babel-plugin-react-transform": "².0.0-beta1",
"babel-plugin-transform-regenerator": "⁶.5.2",
"babel-polyfill": "⁶.5.0",
"babel-preset-es2015": "⁶.3.13",
"babel-preset-react": "⁶.3.13",
"babel-preset-stage-0": "⁶.5.0",
"css-loader": "⁰.23.0",
"enzyme": "².4.1",
"eslint": "².12.0",
"eslint-config-airbnb": "⁹.0.1",
"eslint-plugin-import": "¹.8.1",
"eslint-plugin-jsx-a11y": "¹.5.3",
"eslint-plugin-react": "⁵.2.0",
"express": "⁴.13.3",
"file-loader": "⁰.9.0",
"imports-loader": "⁰.6.5",
"json-loader": "⁰.5.4",
"lolex": "¹.4.0",
"react-transform-catch-errors": "¹.0.1",
"react-transform-hmr": "¹.0.1",
"redbox-react": "¹.2.0",
"rimraf": "².5.0",
"sinon": "¹.17.4",
"style-loader": "⁰.13.0",
"url-loader": "⁰.5.7",
"webpack": "¹.12.9",
"webpack-dev-middleware": "¹.4.0",
"webpack-hot-middleware": "².6.0",
"yargs": "⁴.1.0"
},
"dependencies": {
"classnames": "².2.5",
"jss": "⁵.2.0",
"jss-camel-case": "².0.0",
"lodash.isequal": "⁴.0.0",
"react": "¹⁵.0.2",
"react-addons-shallow-compare": "¹⁵.0.2",
"react-dom": "¹⁵.0.2",
"reqwest": "².0.5",
"spin.js": "².3.2"
}
}
最后,您可以在上述代码中看到您的应用程序的dependencies
,这将帮助您设置所需的组件并获取数据,以及前端内容。您还可以看到定义的devDependencies
及其版本,这些与您的应用程序相关联。
设置package.json
文件后,我们有如下所示的 HTML 标记,名为index.html
:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React Integration with Facebook API</title>
<meta name="viewport" content="width=device-width,
initial-scale=1">
</head>
<body>
<div id=" Api-root"></div>
<script src="dist/bundle.js"></script>
</body>
</html>
在config.js
中使用唯一 ID 配置您的应用程序:
export default {
appId: '1362753213759665',
cookie: true,
xfbml: false,
version: 'v2.5'
};
如前所示,您可以将配置放在一个文件中。您可以将其命名为index.js
。该文件包括您的appId
,在本地目录中运行应用程序时非常重要。
要获得您的 ID,您必须在 Facebook 上注册您的应用程序developers.facebook.com
,然后您将需要按照以下步骤进行操作:
-
登录到您的 Facebook 开发者帐户:
-
登录后,您将在右侧看到一个名为我的应用程序的下拉菜单。点击它并打开列表菜单。在那里,您将找到添加新应用程序。点击它将打开一个对话框,显示创建新应用程序 ID,如下截图所示:
输入所需的详细信息,然后点击创建应用程序 ID按钮。
-
创建应用程序 ID 后,请转到仪表板页面,您将看到类似以下的屏幕:
-
在仪表板页面上,您左侧的导航显示设置链接。请点击该链接设置应用程序的基本和高级设置:
-
一旦您能够看到前面的屏幕,您将能够看到您动态生成的应用程序 ID,显示名称类别和应用程序密钥自动填充。您还将看到应用程序域。在访问应用程序并通知我们需要在此处定义域时,此字段非常重要。但是,如果您直接将您的
localhost
写为域,它将不被接受,您的应用程序将出现错误。
为了使您的本地主机可访问,我们必须定义其平台。现在,请向下滚动一点以访问+ 添加平台:
-
一旦您点击+添加平台,您将在屏幕上看到以下选项,并且您必须选择网站在本地服务器上运行应用程序:
-
在您选择网站作为平台后,将会在屏幕上添加一个字段,如下截图所示:
-
一旦你得到了前面的屏幕,你需要将站点 URL定义为
http://localhost:3000/
,然后以类似的方式,在应用域字段中定义相同的域,如下面的截图所示: -
在做了上述更改之后,请通过点击右下角的保存更改按钮来保存你的更改:
现在你的 ID 已经创建好了,你可以在你的config.js
文件中使用它来链接你的应用在本地服务器上运行。
在设置好config.js
文件之后,下一步是在应用程序中设置你所需的文件,并将你的动态内容注入到 HTML ID 中。
你可以在index.js
文件中导入所需的组件、工具和 CSS,并将其放在不同的文件夹中,这样它就不会与你的配置index.js
文件冲突:
import React from 'react';
import { render } from 'react-dom';
import App from './components/App';
import 'babel-polyfill';
// import CSS
import '../vendor/css/base.css';
import '../vendor/css/bootstrap.min.css';
render(
<App />,
document.querySelector('#Api-root')
);
在前面的代码中,你可以看到我导入了React
来支持 React 文件,并导入了所需的 CSS 文件。最后一步,render
方法在定义了你的 HTML ID 之后将为你完成这个技巧。确保document.querySelector
有正确的选择器,否则你的应用将无法以正确的结构渲染。
你可以在前面的代码中看到,我创建了一个名为App
的组件并导入了它。
在App.js
文件中,我导入了几个组件,这些组件帮助我从我的 Facebook 账户中获取数据,借助 Facebook API 集成。
观察一下App.js
文件的代码结构:
/* global Facebook */
import React, { Component } from 'react';
import Profile from './Profile';
import FriendList from './FriendList';
import ErrMsg from './ErrMsg';
import config from '../../config';
import Spinner from './Spinner';
import Login from './Login';
import emitter from '../utils/emitter';
import { getData } from '../utils/util';
import jss from 'jss';
前面导入的 JavaScript 文件已经设置好了获取数据的结构,关于它将如何在你的应用程序中执行。
const { classes } = jss.createStyleSheet({
wrapper: {
display: 'flex'
},
'@media (max-width: 1050px)': {
wrapper: {
'flex-wrap': 'wrap'
}
}
}).attach();
前面的代码定义了常量来为包装器创建样式,在页面在浏览器中渲染时将应用这些样式。
class App extends Component {
state = {
status: 'loading'
};
componentWillMount = () => {
document.body.style.backgroundColor = '#ffffff';
};
componentWillUnmount = () => {
emitter.removeListener('search');
};
componentDidMount = () => {
emitter.on('search', query => this.setState({ query }));
window.fbAsyncInit = () => {
FB.init(config);
// show login
FB.getLoginStatus(
response => response.status !== 'connected' &&
this.setState({ status: response.status })
);
FB.Event.subscribe('auth.authResponseChange', (response) => {
// start spinner
this.setState({ status: 'loading' });
(async () => {
try {
const { profile, myFriends } = await getData();
this.setState({ status: response.status, profile, myFriends });
} catch (e) {
this.setState({ status: 'err' });
}
})();
});
};
前面的代码扩展了组件,包括挂载/卸载的细节,这些细节我们在之前的章节中已经涵盖过了。如果你对这个领域还不确定,那么请重新查看一下。
window.fbAsyncInit
将会将 Facebook API 与登录设置同步,并验证登录的状态。
它还将异步获取 Facebook 数据,比如你的个人资料和好友列表,这部分有单独的 JavaScript,将在本章后面进行讲解。
// Load the SDK asynchronously
(function (d, s, id) {
const fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) { return; }
const js = d.createElement(s); js.id = id;
js.src = '//connect.facebook.net/en_US/sdk.js';
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
};
_click = () => {
FB.login(() => {}, { scope: ['user_posts', 'user_friends'] });
};
定义一个范围数组意味着我们正在访问用户的 Facebook 好友和帖子。
观察下面的截图:
在上述截图中,您可以看到在创建 Facebook 登录应用程序时App Review选项卡中的默认登录权限访问。我们可以提交批准以访问任何其他用户信息:
mainRender = () => {
const { profile, myFriends, status, query } = this.state;
if (status === 'err') {
return (<ErrMsg />);
} else if (status === 'unknown' || status === 'not_authorized') {
return <Login fBLogin={this._click} />;
} else if (status === 'connected') {
return (
<div className={classes.wrapper}>
<Profile {...profile} />
<FriendList myFriends={myFriends} query={query} />
</div>
);
}
return (<Spinner />);
};
render() {
return (
<div>
{this.mainRender()}
</div>
);
}
}
export default App;
在上述代码中,mainRender
方法将呈现Profile
,myFriends
(好友列表)和status
,并且它将在render return
中返回值。您可以在render
方法中看到一个<div>
标签;我称之为{this.mainRender()}
来在其中注入数据。
正如您所知,这里我们正在处理第三方 API 集成。我们不确定我们将连接到该 API 多长时间以及加载内容需要多长时间。最好有一个内容加载器(旋转器),表示用户需要等待一段时间,因此我们使用以下旋转器来显示页面上内容加载的进度。旋转器的代码也包含在App.js
文件中。以下是旋转器的样子:
您还可以选择自己的自定义旋转器。
一旦您的应用程序页面准备就绪,最终输出应该如下截图所示,您将看到基本的外观和感觉,以及所需的元素:
一旦您启动本地服务器,上述屏幕将要求您允许继续登录过程。
一旦您按下同意按钮,它将重定向您到 Facebook 登录页面。这可以通过以下代码(Login.js
)实现:
import React, { PropTypes } from 'react';
import jss from 'jss';
import camelCase from 'jss-camel-case';
jss.use(camelCase());
在导入 React PropTypes
之后,在以下代码中,您将看到我已经定义了一个常量来为登录页面创建样式。您也可以在这里定义样式,并且可以将它们放入一个 CSS 文件中,并且有一个外部文件调用。
const { classes } = jss.createStyleSheet({
title: {
textAlign: 'center',
color: '#008000'
},
main: {
textAlign: 'center',
backgroundColor: 'white',
padding: '15px 5px',
borderRadius: '3px'
},
wrapper: {
display: 'flex',
minHeight: '60vh',
alignItems: 'center',
justifyContent: 'center'
},
'@media (max-width: 600px)': {
title: {
fontSize: '1em'
},
main: {
fontSize: '0.9em'
}
}
}).attach();
以下代码显示了登录页面的 HTML 结构,并且还定义了Login.propTypes
用于登录按钮:
const Login = ({ fBLogin }) => (
<div className={classes.wrapper}>
<div>
<h2 className={classes.title}>Please check your friend list
on Facebook</h2>
<div className={classes.main}>
<h4>Please grant Facebook to access your friend list</h4>
<button className="btn btn-primary"
onClick={fBLogin}>Agree</button>
</div>
</div>
</div>
);
Login.propTypes = {
fBLogin: PropTypes.func.isRequired
};
export default Login;
当您点击同意按钮时,您的应用程序将被重定向到 Facebook 登录页面。请参考以下截图:
一旦您使用您的凭据登录,它将要求您允许访问您的数据,如下截图所示:
一旦您提供了所需的细节并按下继续按钮,它将给您最终屏幕和最终输出。
出于安全原因,我已经模糊了我的朋友的个人资料图片和他们的名字,但是您将在您的 Facebook 账户中获得相同的布局。现在您在考虑在您的应用程序中获取朋友列表,对吧?所以,借助以下代码的帮助,我在我的自定义应用程序中获取了一个列表。
FriendList.js
被导入到App.js
文件中:
import React, { PropTypes } from 'react';
import FriendItem from './FriendItem';
import { MAX_OUTPUT } from '../utils/constants';
import jss from 'jss';
import camelCase from 'jss-camel-case';
jss.use(camelCase());
在前面的代码片段中,我们还导入了React
,constants
和FriendItem
来获取数据。在这里,我们只是导入了FriendItem
,但它将有一个单独的文件来处理这个问题:
const { classes } = jss.createStyleSheet({
nodata: {
fontSize: '1.5em',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
textAlign: 'center',
color: 'white',
minHeight: '100vh',
},
wrapper: {
flex: '3'
},
'@media (max-width: 1050px)': {
wrapper: {
flex: '1 1 100%'
},
nodata: {
minHeight: 'auto'
}
}
}).attach();
前面的代码定义了朋友列表内容的包装器样式。正如我之前所说,您也可以将它们放在一个单独的 CSS 文件中,并进行外部调用,以便您方便。
const emptyResult = (hasFriends, query) => {
return (
<div className={classes.nodata}>
{hasFriends ? `No results for: "${query}"` : 'No friends to show'}
</div>
);
};
在前面的代码中,您可以看到一个条件来验证某人是否有朋友或没有朋友。如果某人在他们的 Facebook 账户中没有朋友列表,它将显示上述消息。
const renderFriends = ({ myFriends, query }) => {
const result = myFriends.reduce((prev, curr, i) => {
if (curr.name.match(new RegExp(query, 'i'))) {
prev.push(<FriendItem key={i} rank={i + 1} {...curr} />);
}
return prev;
}, []);
return result.length > 0 ? result : emptyResult
(!!myFriends.length, query);
};
const FriendList = (props) => (
<div className={classes.wrapper}>
{renderFriends(props)}
</div>
);
FriendList.propTypes = {
myFriends: PropTypes.array.isRequired,
query: PropTypes.string
};
export default FriendList;
如果您的账户有朋友,那么您将获得一个包括他们的个人资料图片、点赞、评论和帖子数量的完整朋友列表,因此您也可以通过 React 与 Facebook API 集成。
总结
我们已经探索了如何借助 React 集成 Facebook API,您也可以以类似的方式集成其他 API。
我们使用了常量、工具和扩展组件来实现集成并获得预期的输出。
本章中展示的关键示例将帮助您理解或澄清您对将其他 API 与 React 集成的概念。
第九章:React 与 Node.js
在之前的章节中,我们已经学习了关于 React 路由、Facebook API 的集成,以及如何配置和处理应用程序的 URL。我们还学习了如何根据 URL 在 DOM 中注册我们的组件。
在本章中,我们将使用 Node.js 构建我们现有的应用程序。我不打算在这里向您展示如何连接服务器和构建服务器端方面,因为这超出了本书的范围。但是,它包含在随书附带的代码文件中。本章我们将涵盖以下内容:
-
使用 npm 安装所有模块
-
运行编译器和预处理器
-
集成添加票务表单
-
提交表单并将其保存在本地存储中
-
存储和读取本地存储数据
-
运行开发 Web 服务器,文件监视器和浏览器重新加载
-
React 调试工具
到目前为止,我们的应用程序完全基于前端,而且它并没有模块化。当然,这意味着我们的应用程序代码看起来很混乱。我们还使用了 React 的每个依赖库的解包文件,浏览器必须去获取每个 JavaScript 文件并进行编译。
我们将不再需要手动连接和压缩,而是可以设置监视我们的文件进行更改并自动进行更改,比如webpack
和webpack-hot-middleware
。
让我们继续对我们的项目进行更改,不断重复这个过程将会很繁琐。
安装 Node 和 npm
首先,我们需要下载并安装 Node.js。如果您已经安装并配置了 Node,请随意跳过本节。我们可以从nodejs.org
下载 Node.js,并按照以下说明进行操作:
-
从
nodejs.org/
下载适用于您操作系统的安装程序。Node.js 根据您的平台提供不同的安装程序。在本章中,我们将使用 Windows 安装程序来设置 Node。 -
我们还可以从
nodejs.org/en/download/releases/
下载以前的 Node 版本。在本章中,我们正在使用 Node.js 0.12 分支,所以请确保您正在下载这个版本。 -
运行我们下载的安装程序和 MSI 文件。
安装向导将询问您要安装的功能选择,并且您可以选择您想要的功能。通常,我们选择默认安装:
- 如果安装要求,然后重新启动您的计算机。
系统重新启动后,我们可以检查 Node.js 是否设置正确。
打开命令提示符并运行以下命令:
**node --version // will result something like v0.12.10**
您应该能够看到版本信息,这可以确保安装成功。
React 应用程序设置
首先,我们需要为我们的项目创建一个package.json
文件,其中包括 npm 模块的项目信息和依赖项。npm 对于 JavaScript 开发人员来说非常有用,可以创建和共享他们创建的可重用代码,以构建应用程序并在开发过程中解决特定问题。
现在,打开命令提示符/控制台并导航到您创建的目录。运行以下命令:
**Npm init**
此命令将初始化我们的应用程序并询问若干问题以创建名为package.json
的 JSON 文件。该实用程序将询问有关项目名称、描述、入口点、版本、作者名称、依赖项、许可信息等的问题。一旦命令执行,它将在项目的根目录中生成一个package.json
文件。
{
"name": "react-node",
"version": "1.0.0",
"description": "ReactJS Project with Nodejs",
"scripts": {
"start": "node server.js",
"lint": "eslint src"
},
"author": "Harmeet Singh <harmeetsingh090@gmail.com>",
"license": "MIT",
"bugs": {
"url": ""
},
在上述代码中,您可以看到应用程序的name
,应用程序的入口点(start
),应用程序的version
和应用程序的description
。
安装模块
现在我们需要安装一些 Node 模块,这些模块将帮助我们构建一个带有 Node 的 React 应用程序。我们将使用 Babel、React、React-DOM、Router、Express 等。
以下是通过npm
安装模块的命令:
**npm install <package name> --save**
当我们使用<package name>
运行上述命令时,它将在您的project folder/node_modules
中安装包并将package name/version
保存在您的package.json
中,这将帮助我们在任何系统中安装所有项目依赖项并更新模块。
如果您已经有了带有项目依赖项的package.json
文件,那么您只需要运行以下命令:
**npm install**
更新我们需要运行以下命令:
**npm update**
以下是我们应用程序中具有依赖项的模块列表:
"devDependencies": {
"babel-core": "⁶.0.20",
"babel-eslint": "⁴.1.3",
"babel-loader": "⁶.0.1",
"babel-preset-es2015": "⁶.0.15",
"babel-preset-react": "⁶.0.15",
"babel-preset-stage-0": "⁶.0.15",
"body-parser": "¹.15.2",
"eslint": "¹.10.3",
"eslint-plugin-react": "³.6.2",
"express": "⁴.13.4",
"react-hot-loader": "¹.3.0",
"webpack": "¹.12.2",
"webpack-dev-middleware": "¹.6.1",
"webpack-hot-middleware": "².10.0"
},
"dependencies": {
"mongodb": "².2.11",
"mongoose": "⁴.6.8",
"react": "⁰.14.6",
"react-dom": "⁰.14.6",
"react-router": "¹.0.0-rc1",
"style-loader": "⁰.13.1",
"url-loader": "⁰.5.7",
"css-loader": "⁰.26.0",a
"file-loader": "⁰.9.0"
}
在上述dependencies
列表中可能有一些您没有听说过或对您来说是新的模块。好的,让我解释一下:
-
mongoose
和mongodb
:这些在应用程序或 MongoDB 中作为中间件工作。安装 MongoDB 和 mongoose 对您来说是可选的,因为我们在应用程序中没有使用它们。我只是为了您的参考而添加了它们。 -
nodemon
:在 Node.js 应用程序中进行开发时,nodemon
将监视目录中的文件,如果有任何文件更改,它将自动重新启动您的节点应用程序。 -
react-hot-loader
:这是 Web 开发中最常用的模块,用于实时代码编辑和项目重新加载。react-hot-loader
本身对其他模块有一些依赖: -
webpack
-
webpack-hot-middleware
-
webpack-dev-middleware
-
webpack-hot-middleware
:这允许您在不使用webpack-dev-server
的情况下将热重载添加到现有服务器中。它将浏览器客户端连接到 webpack 服务器以接收更新,并订阅来自服务器的更改。然后使用 webpack 的热模块替换(HMR)API 执行这些更改。 -
webpack-dev-middleware
:这是 webpack 的包装器,并在连接的服务器上提供从 webpack 发出的文件。在开发过程中具有以下优势: -
文件不会写入磁盘,而是在内存中处理。
-
在开发过程中,如果在监视模式下更改了文件,则不会提供旧的包,而是在编译完成之前请求会延迟。在文件修改后,我们不需要进行页面刷新。
注意
webpack-dev-middlware
仅在开发中使用。请不要在生产中使用它。
style-loader
、url-loader
、css-loader
和file-loader
有助于加载静态路径、CSS 和文件。
例如:import '../vendor/css/bootstrap.min.css'
,其中包括字体 URL 和图像路径。
设置package.json
文件后,我们的 HTML 标记如下所示,命名为index.html
:
<!doctype html>
<html>
<head>
<title>React Application - EIS</title>
<script src="//ajax.googleapis.com/ajax/libs/jquery/
1.11.1/jquery.min.js"></script>
</head>
<body>
<div id='root'>
</div>
<script src="/static/bundle.js"></script>
</body>
</html>
现在我们需要在server.js
中创建一个服务器来运行我们的应用程序:
var path = require('path');
var webpack = require('webpack');
var express = require('express');
var config = require('./webpack.config');
var app = express();
var compiler = webpack(config);
app.use(require('webpack-dev-middleware')(compiler, {
publicPath: config.output.publicPath
}));
app.use(require('webpack-hot-middleware')(compiler));
在上述代码中,我们正在配置我们应用程序中的webpack
。它连接到服务器并接收更新通知以重新构建客户端包:
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.listen(3000, function(err) {
if (err) {
return console.error(err);
} console.log('Listening at http://localhost:3000/');
})
在上述代码中,我们正在发送一个 HTML 文件并启动服务器。您可以根据需要更改端口号。
现在让我们来看一下webpack.config.js
,我们刚刚在server.js
文件的顶部包含了它。
module.exports = {
devtool: 'cheap-module-eval-source-map',
entry: [
'webpack-hot-middleware/client',
'./src/index'
],
output: {
path: path.join(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/static/'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
],
在上述代码中,我们正在设置webpack-hot-middleware
插件并添加我们脚本的入口点来编译和运行:
module: {
loaders: [{
test: /\.js$/,
loaders: ['react-hot', 'babel'],
include: path.join(__dirname, 'src')
},
{
test: /\.css$/,
loader: 'style!css',
exclude: /node_modules/
}, {
test: /\.(woff|woff2|ttf|svg)$/,
loader: 'url?limit=100000',
exclude: /node_modules/
},
{
test: /\.(eot|png)$/,
loader: 'file',
exclude: /node_modules/
}
]
}
};
在这里,我们根据应用程序中匹配的文件加载模块。
我们还需要配置 Babel,包括 ECMAScript 版本和eslint
,以添加一些规则、插件信息等。
.babelrc
文件包括:
{
"presets": ["es2015", "stage-0", "react"]
}
.eslintrc
文件包括:
{
"ecmaFeatures": {
"jsx": true,
"modules": true
},
"env": {
"browser": true,
"node": true
},
"parser": "babel-eslint",
"rules": {
"quotes": [2, "single"],
"strict": [2, "never"],
"react/jsx-uses-react": 2,
"react/jsx-uses-vars": 2,
"react/react-in-jsx-scope": 2
},
"plugins": [
"react"
]
}
请查看以下屏幕截图:
上述屏幕截图显示了我们的根目录的文件夹结构。在src
目录中,我们有所有的脚本,在 vendor 文件夹中,我们有 Bootstrap 字体和 CSS。
使用 React 和 Node 创建响应式 Bootstrap 应用程序
我们将包含并模块化我们迄今为止开发的 Bootstrap 应用程序。在这个应用程序中,我们可以看到静态用户配置文件在线提出帮助台工单,并在服务器端渲染 React 组件。我们没有使用任何数据库,所以我们将我们的工单存储在浏览器的本地存储中。我们可以在查看工单中看到工单的提交。
供您参考,我已经在代码片段中包含了 Mongodb 配置和与 db 的连接设置,您可以随本书一起获取。此外,我还包含了 Add Ticket Form 的 mongoose 模式,这样您就可以使用它们。
首先,让我们打开src
文件夹中脚本文件index.js
的入口点,并import
React 模块。
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, Link, IndexRoute,IndexLink, browserHistory }
from 'react-router'
在版本 15.4.0 中,React
和ReactDOM
被分成不同的包。在 React 0.14 中,React.render()
已被弃用,推荐使用ReactDOM.render()
,开发人员还完全从 React 中删除了特定于 DOM 的 API。
在 React 15.4.0 中,他们最终将 ReactDOM 实现移动到了 ReactDOM 包中。React 包现在将只包含与渲染器无关的代码,如React.Component
和React.createElement()
。
访问此博客获取有关 React 的最新更新:
facebook.github.io/react/blog/
现在我们需要导入 Bootstrap、CSS 和 JS 文件:
import '../css/custom.css';
import '../vendor/css/base.css';
import '../vendor/css/bootstrap.min.css';
import '../vendor/js/bootstrap.min.js';
现在让我们用以下命令启动服务器,看看我们的代码和配置是否能够构建:
**nodemon start**
它监视应用程序文件的更改并重新启动服务器。
或者如果我们没有安装nodemon
,那么命令应该是:
**node server.js**
服务器在 webpack 中启动,将您的代码捆绑到服务器客户端浏览器。如果一切顺利,当构建完成时,您可以获得以下信息:
目前我们的页面是空白的。因为我们还没有在页面中包含任何组件,所以没有任何内容可显示。
让我们在组件文件夹中创建一个名为navbar.js
的 Bootstrap 导航组件。
module.exports.PageLayout = React.createClass({
})
module.exports
是 Node.js 中的一个特殊对象,并且在每个 JS 文件中都包含它。它将您在module.exports
中编写的函数、变量和任何内容公开为一个模块,使您的代码可重用且易于共享。
让我们在其中添加我们的 Bootstrap 导航组件,使用“容器”布局来呈现页面内容:
render: function() {
return (
<main>
<div className="navbar navbar-default navbar-static-top"
role="navigation">
<div className="container">
<div className="navbar-header">
<button type="button" className="navbar-toggle"
data-toggle="collapse" data-target=".navbar-collapse">
<span className="sr-only">Toggle navigation</span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
<span className="icon-bar"></span>
</button>
<Link className="navbar-brand" to="/">EIS</Link>
</div>
<div className="navbar-collapse collapse">
<ul className="nav navbar-nav">
<li><IndexLink activeClassName="active" to="/">
Home</IndexLink></li>
<li><Link to="/edit" activeClassName="active">
Edit Profile</Link></li>
<li className="dropdown">
<Link to="#" className="dropdown-toggle"
data-toggle="dropdown">Help Desk <b className="caret">
</b></Link>
<ul className="dropdown-menu">
<li><Link to="/alltickets">View Tickets</Link></li>
<li><Link to="/newticket">New Ticket</Link></li>
</ul>
</li>
</ul>
</div>
</div>
</div>
我们的页面导航“容器”到此结束。
在这里,我们开始了页面的主要“容器”,我们可以使用props
来渲染页面内容:
<div className="container">
<h1>Welcome to EIS</h1>
<hr/>
<div className="row">
<div className="col-md-12 col-lg-12">
**{this.props.children}**
</div>
</div>
</div>
</main>
);
}
让我们继续添加主页内容并准备我们的第一个布局:
const RightSection = React.createClass({
render: function() {
return (<div className="col-sm-9 profile-desc" id="main">
<div className="results">
<PageTitle/>
<HomePageContent/>
</div>
</div>)
}
})
// include Left section content in ColumnLeft component with the wrapper of bootstrap responsive classes classes
const ColumnLeft = React.createClass({
render: function() {
return (
)
}
})
const LeftSection = React.createClass({
render: function() {
return (
//Left section content
)
}
})
const TwoColumnLayout = React.createClass({
render: function() {
return (
<div>
<ColumnLeft/>
<RightSection/>
</div>
)
}
})
在这里,我们在这个组件中包含了页面标题和主页内容:
const PageTitle = React.createClass({
render: function() {
return (
<h2>//page content</h2>
);
}
});
const HomePageContent = React.createClass({
render: function() {
return (
<p>//page content</p>
);
}
});
现在我们需要配置路由以在 UI 中呈现组件:
ReactDOM.render((
<Router history={browserHistory}>
<Route path="/" component={PageLayout}>
<IndexRoute component={TwoColumnLayout}/>
</Route>
</Router>
), document.getElementById('root'));
我们需要重复与其他组件和页面相同的流程:
我们的页面看起来很棒;我们已成功将我们的第一个页面与 Node.js 集成。
让我们转到我们的主要组件,并在帮助台部分添加一个工单。
创建一个名为addTicketForm.js
的文件,并包含以下代码:
import React from 'react';
import ReactDOM from 'react-dom';
在每个包含 React 代码的文件中包含 React 模块是很重要的:
var max_Char='140';
var style = {color: "#ffaaaa"};
module.exports.AddTicket = React.createClass({
getInitialState: function() {
return {value: '', char_Left: max_Char};
},
handleChange: function(event) {
var input = event.target.value;
this.setState({value: input.substr(0, max_Char),char_Left:
max_Char - input.length});
if (input.length == max_Char){
alert("You have reached the max limit")
}
},
提示
在上述代码中,我们使用与我们在第五章中创建的相同代码来控制textarea
组件,使用 React 的 jQuery Bootstrap 组件。
handleSubmitEvent: function (event) {
event.preventDefault();
var values = {
date: new Date(),
email: this.refs.email.value.trim(),
issueType: this.refs.issueType.value,
department: this.refs.department.value,
comment: this.state.value
};
this.props.addTicketList(values);
localStorage.setItem('Ticket', JSON.stringify(values));
},
之前我们只是在提交表单后在AddTicket
UI 中显示。现在我们使用本地存储来保存工单。
render: function() {
return (
<form onSubmit={this.handleSubmitEvent}>
在这里,您需要放入我们之前添加的其他表单元素:
<div className="form-group">
<label htmlFor="comments">Comments <span style={style}>*</span>
</label>(<span>{this.state.char_Left}</span> characters left)
<textarea className="form-control" value={this.state.value}
maxLength={max_Char} ref="comments" onChange={this.handleChange} />
</div>
<div className="btn-group">
<button type="submit" className="btn btn-primary">Submit</button>
<button type="reset" className="btn btn-link">cancel</button>
</div>
</form>
);
}
});
接下来,我们需要创建addTicketList.js
,在这里我们将这个 JSX 表单包装成组件:
<AddTicket addTicketList={this.addTicketList} />
还需要创建listView.js
来显示用户提交后的列表:
import { AddTicket } from "./addTicketForm.js";
import { List } from "./listView.js";
在这里,我们导入了之前创建的AddTicket
模块,并创建了另一个模块addTicketForm
来管理更新的表单状态:
module.exports.AddTicketsForm = React.createClass({
getInitialState: function () {
return {
list: {}
};
},
updateList: function (newList) {
this.setState({
list: newList
});
},
addTicketList: function (item) {
var list = this.state.list;
list[item] = item;
this.updateList(list);
},
render: function () {
var items = this.state.list;
return (
<div className="container">
<div className="row">
<div className="col-sm-6">
<List items={items} />
<AddTicket addTicketList={this.addTicketList} />
</div>
</div>
</div>
);
在render
方法中,我们将表单和list
项传递给组件:
}
});
listView.js
import { ListPanel } from "./ListUI.js";
在ListPanel
中,我们有实际的 JSX 代码,用于在用户提交并创建我们在addTicketList.js
中包含的模块后将票据呈现到 UI 中:
module.exports.List = React.createClass({
getListOfIds: function (items) {
return Object.keys(items);
},
createListElements: function (items) {
var item;
return (
this
.getListOfIds(items)
.map(function createListItemElement(itemId,id) {
item = items[itemId];
return (<ListPanel key={id} item={item} />);
}.bind(this))
.reverse()
);
},
render: function () {
var items = this.props.items;
var listItemElements = this.createListElements(items);
return (
<div className={listItemElements.length > 0 ? "":""}>
{listItemElements.length > 0 ? listItemElements : ""}
在这里,我们将listItemElements
呈现到 DOM 中:
</div>
);
}
});
现在让我们创建ListUI.js
,最后一个模块,它将完成表单组件的功能:
module.exports.ListPanel =
React.createClass({
render: function () {
var item = this.props.item;
return (
<div className="panel panel-default">
<div className="panel-body">
Emailid: {item.email}<br/>
IssueType: {item.issueType}<br/>
IssueType: {item.department}<br/>
Message: {item.comment}
</div>
<div className="panel-footer">
{item.date.toString()}
</div>
</div>
);
}
});
让我们看看浏览器中的输出是什么样子的。
确保你已经在你的路由器中包含了以下代码:
<Route path="/newticket" component={AddTicketsForm} />
观察以下截图:
看起来不错。现在让我们填写这个表单,提交它,然后查看输出:
太棒了;我们的表单按预期工作。
你还可以在浏览器的本地存储中看到以 JSON 表示格式的提交Ticket的Key和Value:
开发者工具 > 应用程序 > 存储 > 本地存储
观察以下截图:
现在我们需要从本地存储中获取这个 JSON Ticket并在查看票据部分向用户显示。
让我们创建另一个模块来获取票据并将其呈现到 Bootstrap 响应式表格中。文件
allTickets.js
将如下所示:
module.exports.allTickets = React.createClass({
getInitialState: function() {
return {
value :JSON.parse(localStorage.getItem( 'Ticket' )) || 1};
},
在组件的初始状态中,我们使用localStorage.getItem
来获取tickets
并将它们解析为 JSON 来设置状态:
getListOfIds: function (tickets) {
return Object.keys(tickets);
},
createListElements: function (tickets) {
var ticket;
return (
this
.getListOfIds(tickets)
.map(function createListItemElement(ticket,id) {
ticket = tickets[ticket];
return (<ticketTable key={id} ticket={ticket}/>)
}.bind(this))
);
},
使用我们在添加票据时使用的相同方法,我们通过props
将ticket key
和值映射到 React 组件中:
render: function() {
var ticket = this.state.value;
在render
方法中,我们将state
的值赋给了我们传递到createListElements
函数中的ticket
变量:
var listItemElements = this.createListElements(ticket);
return (
<div>
<div className={listItemElements.length > 0 ? "":"bg-info"}>
{listItemElements.length > 0 ? "" : "You have not raised any ticket yet."}
我们正在使用 JavaScript 三元运算符来检查是否有任何ticket
,如果没有,则在 UI 中显示消息。
</div>
<table className="table table-striped table-responsive">
<thead>
<tr>
<th>Date</th>
<th>Email ID</th>
<th>Issue Type</th>
<th>Department</th>
<th>Message</th>
</tr>
</thead>
<tbody>
<tr>
{listItemElements.length > 0 ? listItemElements : ""}
</tr>
</tbody>
</table>
</div>
// In the preceding code, we are creating the table header and appending the ticket list items.
);
}
});
现在我们需要创建包含<td>
并继承ticket
数据的组件。ticketTable.js
将如下所示:
module.exports.ticketTable = React.createClass({
render: function () {
var ticket = this.props.ticket;
return (
<td>{ticket}</td>
);
}
});
我们还需要在allTickets.js
文件中导入此模块:
const table = require("./ticketTable.js");
你可能会注意到我使用了const
对象,而不是使用import
。你也可以使用var
。const
指的是常量;它们是块作用域的,就像变量一样。常量的值不能改变和重新赋值,也不能重新声明。
例如:
const MY_CONST = 10;
// This will throw an error because we have reassigned again.
MY_CONST = 20;
// will print 10
console.log("my favorite number is: " + MY_CONST);
// const also works on objects
const MY_OBJECT = {"key": "value"};
这是我们最终的路由器配置:
ReactDOM.render((
<Router history={browserHistory}>
<Route path="/" component={PageLayout}>
<IndexRoute component={TwoColumnLayout}/>
<Route path="/profile" component={Profile} />
<Route path="/alltickets" component={allTickets} />
<Route path="/newticket" component={AddTicketsForm} />
</Route>
<Route path="*" component={NoMatch}/>
</Router>
), document.getElementById('root'));
Bootstrap 表格
让我们看看以下要点:
-
斑马纹行:在
<table class="table table-striped">
中使用.table-striped
来为表格行添加斑马纹 -
带边框的表格:添加
.table-bordered
以在整个表格和单元格中添加边框 -
悬停行:添加
.table-hover
以在表格行上启用悬停状态 -
紧凑表格:添加
.table-condensed
以减少单元格填充 -
上下文类:使用上下文类(
.active
、.success
、.info
、.warning
、.danger
)为表格行或单元格添加背景颜色
在表格上应用这些类,看看它们如何影响表格的外观和感觉。
Bootstrap 响应式表格
在创建响应式表格时,我们需要将任何.table
包装在.table-responsive
中,以便在小设备上(小于 768 像素)水平滚动。当我们在大于 768 像素宽的任何设备上查看它们时,你将不会看到这些表格有任何区别。
让我们再次提交票证并快速查看表格。
转到导航中的帮助台下拉菜单,点击查看票证。
如果你还没有提出任何票证,你将在 UI 中收到适当的消息(你还没有提出任何票证。)。
好的,让我们提交新的票证并再次打开这个页面。一旦票证被添加,它将显示在你的表格中:
我们现在可以在表格中看到我们提交的票证。
React 开发者工具
React 为开发者提供了调试 React 代码的工具。它允许我们检查一个由 React 渲染的组件,包括组件层次结构、props 和状态。
安装
有两个官方扩展可用于 Chrome 和 Firefox 浏览器。
为 Chrome 下载扩展:
chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en
和 Firefox:
addons.mozilla.org/en-US/firefox/addon/react-devtools/
注意
一个独立的应用程序仍在开发中,很快将可用。
如何使用
一旦你在浏览器中下载或安装了扩展,打开 React 页面上的开发者工具。你应该会看到一个名为React的额外选项卡:
在侧面板中,您可以看到每个 React 组件的State和Props。如果展开组件的State,您将看到组件的完整层次结构,其中包括您在 React 应用程序中使用的组件的名称。
请参阅以下屏幕截图:
右键单击侧面板,我们可以检查和编辑右侧面板中当前 props 和 state。
我们还可以通过单击执行函数来查看代码执行函数:
如果您使用 React 工具检查allTicket
组件,您可以看到props
流入子元素的数据流:
如果您在元素选项卡上检查页面中的 React 元素,然后切换到React选项卡,该元素将自动在 React 树中被选中。使用搜索选项卡,我们也可以按名称搜索组件。
如果您还需要跟踪组件的更新,您需要选择顶部复选框跟踪 React 更新。
总结
在本章中,您学习了如何将我们的 React 独立应用程序转换为 Node.js npm 包,并将 React 组件模块化。我们首先安装了 Node.js 并设置了 React 环境。然后,我们看了如何使用module.export
导入和导出模块。
我们还学习了如何在一个文件中创建和导入多个模块,例如react-router
,{ Router, Route, IndexRoute,IndexLink, Link, browserHistory } = ReactRouter
。
我们还看了如何从本地存储中存储和读取数据。使用 Bootstrap 表格,我们将数据显示在表格网格中。我们还学习了 Bootstrap 表格,样式类可以使您的表格响应,并且看起来更好。
第十章:最佳实践
在深入探讨处理 React 时应遵循的最佳实践之前,让我们回顾一下我们在之前章节中所看到的内容。
我们已经涵盖了以下关键点:
-
什么是 ReactJS
-
我们如何使用 React-Bootstrap 和 ReactJS 构建响应式主题
-
与 React 的 DOM 交互
-
ReactJS-JSX
-
React-Bootstrap 组件集成
-
Redux 架构
-
使用 React 进行路由
-
React API 与其他 API 集成
-
与 Node.js 一起使用 React
通过前面的主题,你应该对 ReactJS、响应式主题、自定义组件、JSX、Redux、Flux 以及与其他 API 的集成有了更清晰的理解。希望你喜欢这个旅程。现在我们知道从哪里开始以及如何编写代码,但了解如何遵循最佳实践编写标准代码也很重要。
2015 年,全球范围内有许多关于 React 的新发布和会议,现在我看到很多人在问我们如何在 React 中编写标准代码?
每个人对遵循最佳实践都有自己的看法。到目前为止,我已经与你分享了一些观察和经验,但你可能有不同的看法。
如果你想了解更详细的内容,你可以随时访问 React 的官方网站和教程。
在 React 中处理数据
每当我们有具有动态功能的组件时,数据就会出现。同样适用于 React;我们必须处理动态数据,这似乎很容易,但并非总是如此。
听起来有点混乱!
为什么它既容易又困难?因为在 React 组件中,传递属性很容易,构建渲染树的方式有很多,但关于更新视图的清晰度并不多。
2015 年,我们看到了许多 Flux 库,随之而来的是许多功能性和反应性解决方案的发布。
使用 Flux
根据我的经验,许多人对 Flux 存在误解,认为它是不必要的。他们使用它是因为他们对它有很好的掌握。
在我们的例子中,我们已经看到 Flux 有一种清晰的方式来存储和更新应用程序的状态,当需要时,它会触发渲染。
我们经常听到这句话:“每个硬币都有两面”。同样,Flux 也有利有弊。例如,为应用程序声明全局状态是有益的。假设你必须管理已登录的用户,并且正在定义路由器的状态和活动帐户的状态;当你开始使用 Flux 来管理临时或本地数据时,这将是痛苦的。
从我的角度来看,我不建议仅仅为了管理/items/:itemIdroute
相关数据而使用 Flux。相反,你可以在你的组件中声明它并将其存储在那里。这有什么好处?答案是,它将依赖于你的组件,所以当你的组件不存在时,它也将不存在。
例如:
export default function users(state = initialState, action) {
switch (action.type) {
case types.ADD_USER:
constnewId = state.users[state.users.length-1] + 1;
return {
...state,
users: state.users.concat(newId),
usersById: {
...state.usersById,
[newId]: {
id: newId,
name: action.name
}
},
}
case types.DELETE_USER:
return {
...state,
users: state.users.filter(id => id !== action.id),
usersById: omit(state.usersById, action.id)
}
default:
return state;
}
}
在前面基于 Redux 的 reducer 代码中,我们正在管理 reducers 的一部分作为应用程序的state
。它存储先前的state
和action
,并返回下一个状态。
使用 Redux
我们知道,在单页应用程序中,当我们必须处理状态和时间时,难以掌握随时间变化的状态。在这里,Redux 非常有帮助。为什么?因为在 JavaScript 应用程序中,Redux 处理两种状态:一种是数据状态,另一种是 UI 状态,这是单页应用程序的标准选项。此外,请记住,Redux 可以与 AngularJS、jQuery 或 React JavaScript 库或框架一起使用。
Redux 等于 Flux,真的吗?
Redux 是一个工具,而 Flux 只是一个模式,你不能通过即插即用或下载来使用它。我不否认 Redux 从 Flux 模式中获得了一些影响,但我们不能说它与 Flux 完全相似。
让我们继续看一些区别。
Redux 遵循三个指导原则,如下所示。我们还将介绍一些 Redux 和 Flux 之间的区别。
单存储方法
我们已经在之前的图表中看到,存储假装是应用程序中所有种类状态修改的中间人,而 Redux 通过存储控制两个组件之间的直接通信,具有单一通信点。
Redux 和 Flux 之间的区别在于:Flux 有多个存储方法,而 Redux 有单一存储方法。
只读状态
在 React 应用中,组件不能直接改变状态,而必须通过actions
将更改分派到存储中。
在这里,store
是一个对象,它有四种方法,如下所示:
-
store.dispatch(action)
-
store.subscribe(listener)
-
store.getState()
-
replaceReducer(nextReducer)
Reducer 函数用于改变状态
Reducer 函数将处理dispatch
动作以改变state
,因为 Redux 工具不允许两个组件直接通信;因此它也不会改变state
,而是会描述state
的改变。
这里的 Reducer 可以被视为纯函数,编写 Reducer 函数的一些特点如下:
-
没有外部数据库或网络调用
-
根据其参数返回值
-
参数是不可变的
-
相同的参数返回相同的值
Reducer 函数被称为纯函数,因为它们除了根据其设置的参数返回值之外什么都不做;它们没有任何其他后果。
在 Flux 或 Redux 架构中,总是很难处理 API 返回的嵌套资源,因此建议在组件中使用normalize
等平面状态。
专业提示:
const data = normalize(response,arrayOf(schema.user))
state= _.merge(state,data.entities)
不可变的 React 状态
在平面状态下,我们可以处理嵌套资源和不可变
对象的好处,以及声明状态不可修改的好处。
不可变
对象的另一个好处是,通过它们的引用级别相等检查,我们可以大大改善渲染性能。例如,使用不可变
对象有shouldComponentUpdate
:
shouldComponentUpdate(nexProps){
// instead of object deep comparsion
returnthis.props.immutableFoo!==nexProps.immutableFoo
}
在 JavaScript 中,使用不可变深冻结节点将帮助您在变异之前冻结节点,然后验证结果。以下代码示例显示了相同的逻辑:
return{
...state,
foo
}
return arr1.concat(arr2)
我希望前面的例子已经阐明了 Immutable.js 及其好处。它也有简单的方法,但并没有被广泛使用:
import{fromJS} from 'immutable'
const state =fromJS({ bar:'biz'})
constnewState=foo.set('bar','baz')
在我看来,这是一个非常快速和美丽的功能。
可观察和响应式解决方案
我经常听到人们询问 Flux 和 Redux 的替代方案,因为他们想要更多的响应式解决方案。您可以在以下列表中找到一些替代方案:
-
Cycle.js:这是一个功能性和响应式的 JavaScript 框架,用于编写更干净的代码。
-
.rx-flux:这是带有附加功能 RxJS 的 flux 架构。
-
redux-rx:这是用于 Redux 的 RxJS 实用程序。
-
Mobservable:这带有三种不同的风味--可观察数据,响应式函数和简单代码。
React 路由
我们必须在客户端应用程序中使用路由。对于 ReactJS,我们还需要另一个路由库,因此我建议您使用由 React 社区提供的react-router
。
React 路由的优势包括:
-
在标准化结构中查看声明有助于我们立即识别我们的应用程序视图
-
延迟加载代码
-
使用
react-router
,我们可以轻松处理嵌套视图及其渐进式视图分辨率 -
使用浏览历史功能,用户可以向后/向前导航并恢复视图的状态
-
动态路由匹配
-
导航时的 CSS 过渡
-
标准化的应用程序结构和行为,在团队合作时非常有用
注意
React 路由器不提供处理数据获取的任何方法。我们需要使用async-props
或其他 React 数据获取机制。
React 如何帮助将您的代码拆分成延迟加载
很少有处理webpack 模块打包程序的开发人员知道如何将应用程序代码拆分为多个 JavaScript 文件:
require.ensure([],()=>{
const Profile = require('./Profile.js')
this.setState({
currentComponent: Profile
})
})
为什么需要拆分代码是因为每个代码块并不总是对每个用户有用,并且不需要在每个页面上加载它;这会使浏览器过载。因此,为了避免这种情况,我们应该将应用程序拆分为多个代码块。
现在,您可能会有以下问题:如果我们有更多的代码块,那么我们是否需要更多的 HTTP 请求,这也会影响性能?借助 HTTP/2 多路复用(http2.github.io/faq/#why-is-http2-multiplexed
),您的问题将得到解决。观察以下图表:
您还可以将您的代码块与代码块哈希组合,这也将在您更改代码时优化您的浏览器缓存比率。
JSX 组件
JSX 就是简单地说,只是 JavaScript 语法的扩展。如果您观察 JSX 的语法或结构,您会发现它与 XML 编码类似。
JSX 执行预处理步骤,将 XML 语法添加到 JavaScript 中。您当然可以在没有 JSX 的情况下使用 React,但 JSX 使 React 更加整洁和优雅。与 XML 类似,JSX 标签具有标签名称、属性和子元素。JSX 也类似于 XML,如果属性值被引号括起来,那个值就成为字符串。
XML 使用平衡的开放和关闭标记;JSX 类似地工作,并且有助于创建更易于阅读的大型树,而不是函数调用或对象文字。
在 React 中使用 JSX 的优势包括:
-
JSX 比 JavaScript 函数更容易理解
-
JSX 中的标记对设计师和团队的其他成员更加熟悉
-
您的标记变得更有语义,结构化和有意义
有多容易可视化?
正如我所说,结构和语法在 JSX 中非常容易可视化和注意到。与 JavaScript 相比,它们旨在在 JSX 格式中更清晰和可读。
以下简单的代码片段将给您一个更清晰的想法。让我们看一个简单的 JavaScript render
语法:
render: function () {
returnReact.DOM.div({className:"divider"},
"Label Text",
React.DOM.hr()
);
}
让我们看一下以下 JSX 语法:
render: function () {
return<div className="divider">
Label Text<hr />
</div>;
}
希望您非常清楚,对于已经熟悉 HTML 的非程序员来说,使用 JSX 比使用 JavaScript 要容易得多。
熟人或理解
在开发领域,有许多团队,如非开发人员,UI 开发人员和 UX 设计师熟悉 HTML,以及质量保证团队负责彻底测试产品。
JSX 是一种清晰而简洁地理解这种结构的好方法。
语义/结构化语法
到目前为止,我们已经看到 JSX 语法易于理解和可视化。背后有一个具有语义语法结构的重要原因。
JSX 很容易将您的 JavaScript 代码转换为更有语义,有意义和结构化的标记。这使您能够使用类似 HTML 的语法声明组件结构和信息,知道它将转换为简单的 JavaScript 函数。
React 概述了您在React.DOM
命名空间中期望的所有 HTML 元素。好处是它还允许您在标记中使用自己编写的自定义组件。
请查看以下 HTML 简单标记,并查看 JSX 组件如何帮助您拥有语义标记:
<div className="divider">
<h2>Questions</h2><hr />
</div>
将此包装在divider
React 复合组件中后,您可以轻松地像使用任何其他 HTML 元素一样使用它,并且具有更好语义标记的附加好处:
<Divider> Questions </Divider>
使用类
观察以下代码片段:
classHelloMessage extends React.Component{
render(){
return<div>Hello {this.props.name}</div>
}
}
您可能已经注意到,在前面的代码中,React.Component
被用来代替creatClass
。如果您使用其中任何一个都没有问题,但许多开发人员对此并不清楚,他们错误地同时使用两者。
使用 PropType
了解属性是必须的;它将使您能够更灵活地扩展组件并节省时间。请参考以下代码片段:
MyComponent.propTypes={
isLoading:PropTypes.bool.isRequired,
items:ImmutablePropTypes.listOf(
ImmutablePropTypes.contains({
name:PropTypes.string.isRequired,
})
).isRequired
}
您还可以验证您的属性,就像我们可以使用 React ImmutablePropTypes
验证 Immutable.js 的属性一样。
高阶组件的好处
观察以下代码片段:
PassData({ foo:'bar'})(MyComponent)
高阶组件只是原始组件的扩展版本。
使用它们的主要好处是我们可以在多种情况下使用它们,例如在身份验证或登录验证中:
requireAuth({ role: 'admin' })(MyComponent)
另一个好处是,通过高阶组件,您可以单独获取数据并设置逻辑,以简单的方式呈现视图。
Redux 架构的好处
与其他框架相比,Redux 架构有更多的优点:
-
它可能没有其他副作用
-
正如我们所知,不需要绑定,因为组件不能直接交互
-
状态是全局管理的,因此出现管理不善的可能性较小
-
有时,对于中间件,管理其他副作用可能会很困难
从上述观点来看,Redux 架构非常强大,并且具有可重用性。
为您的应用程序定制 Bootstrap
在审查 React 的最佳实践时,我们怎么能忘记我们应用程序的外观和感觉呢?当我们谈论响应性和精美的组件时,只有一个名字会浮现在脑海中:Bootstrap。Bootstrap 为我们提供了一个魔法棒,可以在较少的努力下实现最佳效果,并节省金钱。
如今,响应性非常重要,或者我应该说,它是强制性的。在制作应用程序时,您应该在包中包含 Bootstrap,并且可以利用 Bootstrap 类、Bootstrap 网格和 Bootstrap 准备好的组件。此外,Bootstrap 的响应式主题也可用;有些是免费的,有些需要付费,但它们非常有用。以前,我们在 CSS 中编写媒体查询以实现响应性,但 Bootstrap 通过提供精美的现成功能,真正帮助我们节省了时间、精力和客户的金钱。
Bootstrap 内容 - 排版
您可能已经注意到,在 Bootstrap 包中,Bootstrap 使用的是全球通用的 Helvetica 字体类型。因此,您不仅可以选择使用 Helvetica,还可以使用一些自定义字体,您可以在www.google.com/fonts
找到。例如,如果我想要从 Google 库中选择Lato字体,那么我可以从那里选择字体,并在包中选择所需的字体,如下面的截图所示:
现在的问题是:我如何在我的系统中使用这个字体?我应该下载它吗?或者有什么办法?有一个非常简单的方法,正如我们在前面的截图中看到的那样;同一个对话框框有一个名为EMBED的选项卡。
当您点击它时,它会显示以下屏幕:
如@IMPORT选项卡中所示,您可以从@import url()
复制该行,并将其添加到您的bootstrap.less
文件或bootstrap.scss
文件的所有 CSS 顶部。然后您可以在应用程序中使用 Lato 字体系列。
此外,如果需要,您还可以自定义其他字体属性,例如字体大小、字体颜色和字体样式。
Bootstrap 组件-导航栏
在任何应用程序中,导航流非常重要,Bootstrap 的导航栏
为您提供了一种通过多种选项构建响应式导航的方式。您甚至可以通过定义其大小、颜色和类型来自定义它,如下面的代码所示:
@navbar-default-bg: # 962D91;
如前面的代码所示,我们可以根据期望的外观定义任何颜色,以符合我们的导航栏
及其链接:
@navbar-default-color: #008bd1;
@navbar-default-link-color: #008bd1;
@navbar-default-link-hover-color: # 962D91;
@navbar-default-link-active-color: #008bd1;
不仅适用于桌面,也适用于移动导航,您可以根据自己的需求自定义“导航栏默认”颜色设置:
@navbar-default-toggle-hover-bg: darken(@navbar-default-bg, 10%);
@navbar-default-toggle-icon-bar-bg: #008bd1;
@navbar-default-toggle-border-color: #008bd1;
您甚至可以设置导航栏
的高度
和边框
设置,如下面的代码所示:
@navbar-height: 50px;
@navbar-border-radius: 5px;
Bootstrap 组件-表单
表单非常常用于从用户那里获取数据,您可以使用表单元素并创建诸如查询表单、注册表单、登录表单、联系我们表单等组件。Bootstrap 还提供了form
组件,其好处在于其响应式行为。它也是可定制的。
在 Bootstrap 包中有一些文件,您可以更改与表单相关的 CSS 并获得预期的输出。
例如,更改input
字段的border-radius
CSS 属性:
@input-border-radius: 2px;
更改input
字段的border-focus
颜色:
@input-border-focus: #002D64;
我非常喜欢 Bootstrap 最新版本的一个特点,它为每个组件/元素都有单独的部分,就像 React 一样。例如,在混合中,您可以看到单独的文件,其中只有各自的 CSS 属性,因此它们易于理解、调试和更改。
表单控件(.form-control
)是 Bootstrapform
组件的一个美丽特性,您可以在以下代码中看到自定义更改是多么容易:
.form-control-focus(@color: @input-border-focus) {
@color-rgba: rgba(red(@color), green(@color), blue(@color), .3);
&:focus {
border-color: @color;
outline: 1;
.box-shadow(~"inset 1px0 1px rgba(0,0,0,.055), 0 0 6px
@{color-rgba}");
}
}
在前面的示例中,我们已经看到了如何自定义边框颜色、轮廓和框阴影;如果您不想要框阴影,那么可以注释掉那一行代码,看看没有框阴影的输出,如以下代码所示:
//.box-shadow(~"inset 1px 0 1px rgba(0,0,0,.055), 0 0 6px @{color-rgba}");
您可能已经注意到,我用//
对代码进行了注释,这是我们通常在 JavaScript 中做的,但在这里也是有效的,我们还可以使用 CSS 标准注释/* */
来注释一行代码或多行代码。
Bootstrap 组件 - 按钮
Bootstrap 组件还有一个名为button
的现成组件,因此无论我们在应用程序中组合什么按钮,都可以使用 Bootstrap 类来增强它。Bootstrap 的button
组件具有不同的大小、颜色和状态,可以根据您的要求进行自定义:
我们还可以通过使用 Bootstrap 的按钮类来实现类似的外观和感觉,如下所定义:
.btn-default
.btn-primary
.btn-success
.btn-info
.btn-warning
.btn-danger
.btn-link
在编写按钮的 HTML 代码时,您可以在应用程序的button
标签中定义 Bootstrap 类:
<button type="button" class="btnbtn-default">Default</button>
<button type="button" class="btnbtn-primary">Primary</button>
<button type="button" class="btnbtn-success">Success</button>
<button type="button" class="btnbtn-info">Info</button>
<button type="button" class="btnbtn-warning">Warning</button>
<button type="button" class="btnbtn-danger">Danger</button>
<button type="button" class="btnbtn-link">Link</button>
在之前的章节中,我们还使用了 Bootstrap 类来实现响应性和 Bootstrap 的默认组件。您可以在以下截图中看到button
的一个示例,我在其中定义了以下代码。我们还可以更改所有已定义的button
状态的颜色:
<button type="button" class="btnbtn-primary">Agree</button>
请参考以下截图:
Bootstrap 主题
正如我之前所说,Bootstrap 还提供了现成的响应式主题,如果需要的话,我们应该使用。有关更多详细信息,您可以查看getbootstrap.com/examples/theme/
。
您还可以访问以下参考资料,了解更多有关 Bootstrap 主题的选项:
Bootstrap 响应式网格系统
Bootstrap 网格系统有一些预定义的类和行为,因此设置页面布局并为不同的设备和分辨率设置相同布局的不同行为将非常有帮助。
以下截图显示了移动设备和桌面列的设置:
以下截图显示了移动设备、平板和桌面列的设置:
这是如何使用预定义类来设置您的列。在小型和中型设备上,它们将自动调整您的数据以适应分辨率,而不会破坏用户界面。
最后,我想告诉你一些在处理 ReactJS 时需要记住的事情。
关于 ReactJS 和 Bootstrap 项目的有趣信息
ReactJS 和 Bootstrap 都被开发者社区广泛使用和关注。有数百万的项目在这两个框架上运行,显然这两个成功框架背后有一个专门的团队。
Bootstrap 总是在他们的最新版本或扩展中推出一些新的有用的东西。我们都知道 Bootstrap 是由 Twitter Bootstrap 拥有的,两位开发者应该得到成功的功劳:Mark Otto (@mdo
) 和 Jacob Thornton (@fat
)
有许多关于 Bootstrap 的有用网站,值得在寻找增加知识的过程中访问:
-
www.getbootstrap.com
| Twitter:@twbootstrap
-
expo.getbootstrap.com
| Twitter: Mark Otto (@mdo
) -
www.bootsnipp.com
| Twitter:@BootSnipp
和 Maksim Surguy (@msurguy
) -
roots.io/
| Twitter: Ben Word (@retlehs
) -
www.shoelace.io
| Twitter: Erik Flowers (@Erik_UX
) 和 Shaun Gilchrist -
github.com/JasonMortonNZ/bs3-sublime-plugin
| Twitter: Jason Morton (@JasonMortonNZ
) -
fortawesome.github.io/Font-Awesome/
| Twitter: Dave Gandy (@davegandy
) -
bootstrapicons.com/
| Twitter: Brent Swisher (@BrentSwisher
)
有用的 React 项目
在初学者级别,许多开发者发现 React 非常令人困惑,但如果你能够熟练掌握并深入了解它,你会喜欢它。有许多基于 ReactJS 完成的开源项目,我在下面的列表中分享了其中一些;我希望这肯定会帮助你更好地理解 React:
-
Calypso:
-
GitHub:Automattic/wp-calypso
-
开发者:Automattic
-
前端级别技术:React Redux wpcomjs
-
后端级别技术:Node.js ExpressJS
-
Sentry:
-
GitHub:getsentry/sentry
-
前端级别技术:React
-
后端级别技术:Python
-
SoundRedux:
-
URL:soundredux.io/
-
GitHub:andrewngu/sound-redux
-
开发者:Andrew Nguyen
-
前端级别技术:React Redux
-
后端级别技术:Node.js
-
Phoenix Trello:
-
GitHub:bigardone/phoenix-trello
-
开发者:Ricardo García
-
前端级别技术:React Webpack 用于样式表的 Sass React 路由器 Redux ES6/ES7 JavaScript
-
后端级别技术:Elixir Phoenix 框架 Ecto PostgreSQL
-
Kitematic:
-
URL:kitematic.com
-
GitHub:docker/kitematic
-
开发者:Docker
-
前端级别技术:React
-
Google 地图聚类示例:
-
GitHub:istarkov/google-map-clustering-example
-
开发者:Ivan Starkov
-
前端级别技术:React
-
Fil:
-
GitHub:fatiherikli/fil
-
开发者:FatihErikli
-
前端级别技术:React Redux
-
React iTunes 搜索:
-
GitHub:LeoAJ/react-iTunes-search
-
开发者:Leo Hsieh
-
前端级别技术:React 打包组件:Webpack
-
Sprintly:
-
URL:sprintly.ly
-
GitHub:sprintly/sprintly-ui
-
开发者:Quick Left
-
前端级别技术:React Flux React Router
-
后端技术:Node.js
-
Glimpse:
-
URL:getglimpse.com/
-
GitHub:Glimpse/Glimpse
-
开发者:Glimpse
-
前端级别技术:React 打包组件:Webpack
-
后端级别技术:Node.js
当您需要对 ReactJS 和 Bootstrap 进行支持时,请参考以下网站:
对于 React:
对于 Bootstrap:
要记住的事情
请注意以下要记住的要点:
-
在开始使用 React 之前,请始终记住它只是一个视图库,而不是 MVC 框架。
-
建议组件长度较小,以处理类和模块;这也使得在理解代码、单元测试和长期维护组件时更加轻松。
-
React 在其 0.14 版本中引入了 props 函数,建议使用。它也被称为功能组件,有助于拆分您的组件。
-
在处理基于 React 的应用程序时,为了避免痛苦的旅程,请不要使用太多状态。
-
如我之前所说,React 只是一个视图库,因此在处理渲染部分时,我建议使用 Redux 而不是 Flux。
-
如果您想要更多的类型安全性,那么始终使用
PropTypes
,这也有助于早期发现错误并起到文档的作用。 -
我建议使用浅渲染方法来测试 React 组件,这允许渲染单个组件而不触及其子组件。
-
在处理大型 React 应用程序时,始终使用 webpack、NPM、ES6、JSX 和 Babel 来完成您的应用程序。
-
如果您想深入了解 React 的应用程序及其元素,可以使用 Redux-dev 工具。
总结
我们在本章中涵盖了很多内容,因此在结束之前让我们回顾一下。
当我们在 React 中处理数据时,每当我们有具有动态功能的组件时,数据就会出现。在 React 中,我们必须处理动态数据,这似乎很容易,但并非总是如此。
从我的个人观点来看,我不建议仅仅为了管理与/items/:itemIdroute
相关的数据而使用 Flux。相反,您可以在组件内部声明它并将其存储在那里。这有什么好处?答案是:它将依赖于您的组件,因此当您的组件不存在时,它也将不存在。
关于使用 Redux,正如我们所知,在单页应用程序中,当我们必须处理状态和时间时,难以掌握随时间变化的状态。在这里,Redux 非常有帮助。
我们还研究了其他关键因素,如 JSX、平面状态、不可变状态、可观察对象、响应式解决方案、React 路由、React 类、ReactPropTypes
等等,这些都是 React 应用中最常用的元素。
我们还介绍了 Bootstrap 及其组件的实用性,这将使您在处理不同的浏览器和设备时更加灵活。
最后,我们为您提供了在处理任何 React 应用程序时需要记住的事项,无论是新应用程序还是集成应用程序;这些要点肯定会对您有很大帮助。