它让我有幸见识到了那些备受推崇的不因循守旧者们骨子里的固执。——爱因斯坦
也许在拿到本书的时候,你已经有了一些JavaScript知识。很有可能你大概知道React是什么。本章突出React作为框架这一层面的关键问题,阐释它所解决的问题,并描述如何利用这些特性以及本书其他信息来改善网页开发实践,创建复杂且可维护的用户界面。
1 定义React
React是一个JavaScript框架。它起初是由Facebook的工程师创建,用于解决在开发数据随时间而变的复杂用户界面时的困难。这不是件简单的事,它不仅必须是可维护的,还得是可伸缩的以便服务于Facebook这样的规模。React实际上诞生于Facebook的广告组织,他们那儿一直在使用传统客户端的模型-视图-控制器(Model-View-Controller)方法。这些应用程序通常由双向数据绑定(two-way data binding)和渲染模板组成。React改变了创建这些应用程序的方式,在Web开发中取得了一些大胆的进步。在2013年React刚发布时,Web开发社区对其所作所为既有兴趣又似乎感到厌恶。
随着通览全书你会发现,React挑战了实际上已成为标准的JavaScript框架最佳实践。为此,React 引入许多新范式,并改变现状以创建可伸缩和可维护的JavaScript应用程序和用户界面。伴随着前端开发思路的转换,React 自带一系列丰富的特性,使得各种技能水平(从刚接触JavaScript到具有丰富Web开发经验)的开发者都可以创建单页面(single-page)应用程序或友好的用户界面。阅读本书,你会看到这些特性(比如虚拟DOM、JSX和Flux等概念),并了解它们是如何被用于创建复杂用户界面。
简单来说,你还将看到Facebook是如何使用React Native持续不断地挑战开发界的。React Native是一个新的开源库,它利用与React的JavaScript库相同的原理来创建原生用户界面。通过创建Native UI库,React已经推出了其价值主张——“一次学习,随处可用”。这一模式转变也适用于利用React的核心概念做出可维护界面。到目前为止,你可能认为用React进行开发时没有什么是它无法做的。但情况并非如此,为了更进一步地了解React是什么,你需要先了解React不是什么,在本章稍后的部分你会学到。首先,你会理解导致React被创造出来的根本问题,以及React如何解决这些问题。
2 为什么是React
如前所述,React与常规的Web开发是不同的概念,是对公认的工作流和最佳实践的一个转变。为什么Facebook要躲开这种趋势,转而创建一种全新的Web开发过程呢?挑战公认的最佳实践是草率行事吗?还是说创建React有普遍的商业理由?
如果看看React背后的思考,你将会明白创建它是为了满足Facebook面临的一系列特殊技术挑战所产生的特殊需求。这些挑战并非Facebook独有,但Facebook所做的是立即用一种方法来解决问题,以应对这些挑战。可想而知,这与Eric Raymond在他的书(The Art of Unix Programming [1])中总结的UNIX哲学类似。书中,Raymond写了下面这段关于模块化的规则。
要编写复杂软件又不至于一败涂地,唯一的方法就是用定义清晰的接口把若干简单模块组合起来,如此一来,多数问题只会出现在局部,那么还有希望对局部进行改进或优化,而不至于牵动全身。
这恰好就是React用来解决复杂的用户界面所需要的方法。在开发React时,Facebook没有创建完全的模型-视图-控制器架构来代替现有框架。没必要去重新发明特殊的轮子和增加创建大规模用户界面问题的复杂性。创建React是用来解决奇异问题的。
构建React是为了处理用户界面中显示的数据。你可能认为用户界面中显示数据的问题已被解决,你这么想也不算错。不同的是React服务于大规模用户界面(Facebook和Instagram规模的界面)中随时间而变的数据。这种界面可以被React以外的现有工具创建和解决。事实上,Facebook在创建React前一定已经解决了这些问题。但Facebook还是创建了React,因为它已经有正当的理由,并发现React可以被用于解决构建复杂用户界面时遇到的特殊问题。
3 React解决什么问题
React不能解决在用户界面设计和前端开发中所遇到的所有问题。React只解决一系列特殊的问题,简而言之,就是单个问题。正如Facebook和Instagram所阐明的,React用于构建数据随时间而变的大规模用户界面。
数据随时间而变的大规模用户界面或许与许多Web开发者的工作和业余编码经历都有关联。在现代Web开发世界中,你通常会把用户界面的大部分责任转移给浏览器以及HTML、CSS和JavaScript。这类应用程序通常被称为单页面应用程序,常见的请求/响应服务器只能有限地展示浏览器的性能。这是自然的,既然大部分的浏览器都能做复杂的布局和交互,为什么你不这样做呢?
到了周末,项目代码不再是可维护的,问题就来了。你不得不附加额外的代码段,将数据恰当地绑定。有时你不得不重建应用程序,因为接下来的业务需求已经在用户开始一项任务后,不经意地破坏了界面渲染一些交互的方式。这一切导致用户界面脆弱,高度相连,以及不易维护。React尝试解决所有这些问题。
以你之前看到所提及的客户端模型-视图-控制器架构与模板里的双向数据绑定为例。该应用程序必须包含监听模型的视图,然后视图基于用户交互或模型改变独立地更新它们的表现。在基础应用程序中,这不是一个明显的性能瓶颈,或者更重要的,对开发者的生产力而言,这不是一个明显的瓶颈。该应用程序的规模将不可避免地增长,因为新模型和视图会被加入应用程序。所有的连接都是通过微妙而杂乱的代码,它们可以指明每个视图及其模型的关系,且很快会变得越来越复杂。在渲染链或遥远的模型深处的某一项正影响着其他项的输出。在大多数情况下所发生的更新可能并不会被开发者所知,因为维护跟踪机制变得逐渐困难。它会让开发和测试代码更为困难,这意味着开发一个方法或新特性并发布它变得更难。如此一来,代码缺乏可预见性,开发时间也飞速猛涨,这正是React需着手解决的问题。
首先,React是一个思想实验。Facebook认为他们已经写了最初的布局代码来描述应用程序可以也应该看起来像什么,所以为什么不在数据或状态改变应用程序的时候再跑一下启动代码呢?你可能会立刻有些退缩,因为你知道这意味着它们会牺牲性能和用户体验。当你完全替换浏览器中的代码时,你将看到屏幕闪烁和无样式内容出现的一瞬间,这只是显得效率低下。Facebook知道这一点,而且注意到他们所创建的(在数据改变时代替状态的机制)实际上在某种程度上是有作用的。然后Facebook决定,只要替换机制可以被优化,那就有解决方案了。这就是React如何因为一组特定问题的解决方案而诞生的。
4 React不只是另一个框架
大多数情况下,你要学某样东西,首先需要认识到你要学的是什么。就React来说,了解哪些概念不属于React框架是有帮助的。这将有助于你理解你所学的哪些标准实践需要忘掉,或者至少需要放一边,以彻底理解新框架的概念,比如React。那么是什么使React与众不同,以及为什么它如此重要呢?
许多人认为,React 相比于其他框架是同等级的完整 JavaScript 框架,比如 Backbone、Knockout.js、AngularJS、Ember、CanJS和Dojo,或大多数其他现有MVC框架。图1-1显示了经典MVC框架的示例。
图1-1 基本MVC架构
图1-1展示了模型-视图-控制器架构中每个组件的基本要素。模型处理应用程序的状态,并将状态改变事件发送给视图。视图是面向用户的外观和对终端用户的交互界面。视图可以将事件发送给控制器,有时候也发送给模型。控制器是事件主要的分派器,事件会被发送给模型,以更新状态,然后视图来更新表现。你可能会注意到这是一个常见的MVC架构的代表,在实际中有很多变体和定制化实现,因此不存在单一的MVC架构。这里不是声明MVC架构看起来像什么,而是指出React不是什么。
用这种MVC架构来评价React是什么或想成为什么,实际上不公平。这是因为React是那些现有框架中的特例。React处于其最简形式,仅为MVC、MVVM或者MV*框架中的视图。如你在之前所见,React是一种描述应用程序用户界面的方法,是一种在数据发生变化时随时更改用户界面的机制。React由描述界面的声明性组件组成。在构建应用程序时,React没有使用可观察的数据绑定。React也是易于维护的,因为你可以用你创造的组件,并组合它们,在任何你期望的时候自定义组件,因为它可以扩展。促使React出现的那些理由,使得React可以比其他框架更好地扩展。在创建React界面时,你是以由多组件建立起来的方式构建它们的。
让我暂停一下,来检验几个框架最基础的结构,以便突出它们与React的差异。对每个框架,你都将用最基础的备忘录(to-do list)应用程序检验,正如网站http://todomvc.com上创建的。我不是要否定其他框架,因为它们都是为某个目的服务的。相反地,我试图展示相比于其他的框架,React是如何被构建的。我在这将仅列出重要的部分,以突出和限定一个应用程序的完整重现。如果你想看完整示例,链接中含有资源。不要过于集中在这些示例的实现细节上,包括React示例,因为本书后面会详细讲解这些概念,这些概念将帮助你完全理解是怎么回事。
4.1 Ember.js
Ember.js是流行框架,它是由句柄模板(handlebar template)形式的视图所组成的MVC框架。在这一段,应注意,为了便于集成模板、模型和控制器,有一点工作要做。这不是说Ember.js是个不好的框架,因为修改是这种框架的副产品。
清单1-1中是TodoMVC
的Ember.js
示例的主体,你会看到标签由两个句柄模板(todo-list和to-dos)组成。
清单1-1 使用Ember.js的TodoMVC主体
<body>
<script type="text/x-handlebars" data-template-name="todo-list">
/* Handlebars todo-list template */
</script>
<script type="text/x-handlebars" data-template-name="todos">
/* Handlebars todos template */
</script>
<script src="node_modules/todomvc-common/base.js"></script>
<script src="node_modules/jquery/dist/jquery.js"></script>
<script src="node_modules/handlebars/dist/handlebars.js"></script>
<script src="node_modules/components-ember/ember.js"></script>
<script src="node_modules/ember-data/ember-data.js"></script>
<script src=" node_modules/ember-localstorage-adapter/localstorage_
adapter.js"></script>
<script src="js/app.js"></script>
<script src="js/router.js"></script>
<script src="js/models/todo.js"></script>
<script src="js/controllers/todos_controller.js"></script>
<script src="js/controllers/todos_list_controller.js"></script>
<script src="js/controllers/todo_controller.js"></script>
<script src="js/views/todo_input_component.js"></script>
<script src="js/helpers/pluralize.js"></script>
</body>
接着还有三个控制器,即app.js
入口、路由器以及todo输入视图组件。看起来好像有很多文件,但在产品环境中,都会被压缩起来。注意控制器和视图的分离,包括清单1-2中显示的备忘录列表视图在内的视图都非常详细并且易于确定代码的功能。
清单1-2 Ember.js的句柄模板
{{#if length}}
<section id="main">
{{#if canToggle}}
{{input type="checkbox" id="toggle-all" checked=allTodos.allAreDone}}
{{/if}}
<ul id="todo-list">
{{#each}}
<li {{bind-attr class="isCompleted:completed isEditing:editng"}}>
{{#if isEditing}}
{{todo-input
type="text"
class="edit"
value=bufferedTitle
focus-out="doneEditing"
insert-newline="doneEditing"
escape-press="cancelEditing"}}
{{else}}
{{input type="checkbox" class="toggle" checked=isCompleted}}
<label {{action "editTodo" on="doubleClick"}}>{{title}}</label>
<button {{action "removeTodo"}} class="destroy"></button>
{{/if}}
</li>
{{/each}}
</ul>
</section>
{{/if}}
这是个清楚的例子,是个可读的视图。正如你所期望的,控制器规定了各自的属性。该控制器在router.js
文件中命名,所用的视图也由该文件命名。清单1-3中显示了该控制器。
清单1-3 Ember.js的TodosListController
(function () {
'use strict';
Todos.TodosListController = Ember.ArrayController.extend({
needs: ['todos'],
allTodos: Ember.computed.alias('controllers.todos'),
itemController: 'todo',
canToggle: function () {
var anyTodos = this.get('allTodos.length');
var isEditing = this.isAny('isEditing');
return anyTodos && !isEditing;
}.property('allTodos.length', '@each.isEditing')
});
})();
你可以看到,该TodosListController
采用了备忘录的模式,然后随着名为“todo
”的itemController
添加了一些属性。该todo
控制器实际上就是大部分指明动作和你在之前所见的视图中可见条件的JavaScript所在之处。作为熟悉Ember.js的人,这对于Ember.js可以做什么是个很好的定义和组织示例。你很快就会知道,无论如何,它与React都是迥异的。首先,让我们看一下AngularJS的TodoMVC
示例。
4.2 AngularJS
AngularJS也许是世界上最流行的MV*框架。它上手极其简单,背后有谷歌支持,还有大量的开发者创造了很好的教程、书籍和博客文章。当然,它不同于React的框架,你将很快看到这一点。清单1-4展示了AngularJS的TodoMVC
应用程序。
清单1-4 AngularJS主体
<body ng-app="todomvc">
<ng-view />
<script type="text/ng-template" id="todomvc-index.html">
<section id="todoapp">
<header id="header">
<h1>todos</h1>
<form id="todo-form" ng-submit="addTodo()">
<input
id="new-todo"
placeholder="What needs to be done?"
ng-model="newTodo"
ng-disabled="saving" autofocus
>
</form>
</header>
<section id="main" ng-show="todos.length" ng-cloak>
<input id="toggle-all" type="checkbox" ng-model="allChecked"
ng-click="markAll(allChecked)">
<label for="toggle-all">Mark all as complete</label>
<ul id="todo-list">
<li ng-repeat="todo in todos | filter:statusFilter track by $index"
ng-class="{
completed: todo.completed,
editing: todo == editedTodo}"
>
<div class="view">
<input class="toggle" type="checkbox" ng-model="todo.completed"
ng-change="toggleCompleted(todo)">
<label ng-dblclick="editTodo(todo)">{{todo.title}}</label>
<button class="destroy" ng-click="removeTodo(todo)"></button>
</div>
<form ng-submit="saveEdits(todo, 'submit')">
<input class="edit" ng-trim="false" ng-model="todo.title"
todo-escape="revertEdits(todo)" ng-blur="saveEdits(todo, 'blur')"
todo-focus="todo == editedTodo">
</form>
</li>
</ul>
</section>
<footer id="footer" ng-show="todos.length" ng-cloak>
/* footer template */
</footer>
</section>
</script>
<script src="node_modules/todomvc-common/base.js"></script>
<script src="node_modules/angular/angular.js"></script>
<script src="node_modules/angular-route/angular-route.js"></script>
<script src="js/app.js"></script>
<script src="js/controllers/todoCtrl.js"></script>
<script src="js/services/todoStorage.js"></script>
<script src="js/directives/todoFocus.js"></script>
<script src="js/directives/todoEscape.js"></script>
</body>
你可以看到,和之前的Ember.js相比,Angular的模板在本质上是更其声明性的。你还可以在该应用程序中看到诸如控制器(controller)、指令(directive)和服务(service)的概念。todoCtrl
文件里有控制器用来操纵该视图的数值。清单1-5中的示例,只是该文件的一小段,但你可以看到它如何运作。
清单1-5 AngularJS的备忘录控制器
angular.module('todomvc')
.controller('TodoCtrl', function TodoCtrl($scope</strong>, $routeParams, $filter,
store) {
/* omitted */
$scope.addTodo = function () {
var newTodo = {
title: $scope.newTodo.trim(),
completed: false
};
if (!newTodo.title) {
return;
}
$scope.saving = true;
store.insert(newTodo)
.then(function success() {
$scope.newTodo = '';
})
.finally(function () {
$scope.saving = false;
});
};
/* omitted */
});
该示例展示了todoCtrl
及其如何构建$scope
机制以让你添加方法和属性到你的AngularJS视图中。下一段研究React,解释它是如何以不同于Ember.js和AngularJS的方式作用于用户界面的。
4.3 React
正如你在其他示例中所见到的,TodoMVC
应用程序中有一种基本结构,让这些示例更易于演示其差异性。我认为Ember.js和AngularJS这两个流行的框架有助于证明React不是个MV*框架,它只是用于构建用户界面的基本JavaScript框架。这段React示例的细节显示了怎样从组件层级构建React应用,然后反过来解释组件是如何组成的。最后,许多页组成了这本关于React的书,最终你会在清单1-6中看到React的代码。
{■ 注意:}
所提供的代码是运行在服务器上的,可以用Python中的SimpleHTTP Server、Apache服务器,或者任何你习惯用的。如果都不行,你可以为浏览器提供HTML文件,但你需要确保文件是本地相关的,并能够在你的浏览器获取到。
清单1-6 React备忘录应用的基本HTML
<!-- some lines removed for brevity -->
<body>
<section id="todoapp"></section>
<script src="react.js"></script>
<script src="JSXTransformer.js"></script>
<script src="js/utils.js"></script>
<script src="js/todoModel.js"></script>
<script type="text/jsx" src="js/todoItem.jsx"></script>
<script type="text/jsx" src="js/footer.jsx"></script>
<script type="text/jsx" src="js/app.jsx"></script>
</body>
在清单1-6中,你可以看到React的todoMVC
应用程序的基本主体。注意这一段及其id
属性。比较AngularJS和Ember.js示例的主体就会注意到,对这类应用而言,script标签的数量和需要处理的文件少多了。有人会说按照文件数量不是公平的比较,因为理论上来说你可以在构建AngularJS应用程序时,在每个文件中包含不止一个控制器,或以类似的方式限制script元素的数量。从这一点来看,由于创造组件的方式,React看起来是自然而然地分成这些类型的结构。这不是说React肯定更好,抑或更简明,但React创建组件的创建机制至少看起来更简明。
这段会以放置React被渲染的组件为目标。所包含的脚本是React库和JSX变换器文件。之后的两项是被并入每个todoMVC
应用程序的数据模型和工具。下面那些项是三个JSX文件,它们组成了整个应用程序。应用程序由文件app.jsx
中所包含的组件开始渲染,在清单1-7中会有示例。
清单1-7 app.jsx的渲染函数
var model = new app.TodoModel('react-todos');
function render() {
React.render(
<TodoApp model={model}/>,
document.getElementById('todoapp')
);
}
model.subscribe(render);
render();
清单1-7显示了React视图有趣的工作方式。在本书的后面,你会学到这是如何被实现的,但示例中加粗的部分是基础。首先,你可以看到一个类似 HTML 或 XML 的元素<TodoApp model={model}/>
。这是JSX,或者叫JavaScript XML对译器(transpiler)[2],它被集成在React中。JSX不是React所必需的工具,但它可使创作应用程序更容易。它不仅使书写React应用程序变得更为容易,而且使你在读代码以及更新它们时的语法更清晰。之前的JavaScript转换为JSX的函数如下:
React.createElement(TodoApp, {model: model});
这是目前为止要注意到的有趣之处,你会在第3章读到更多关于JSX以及它是如何转换的。
从该示例可以看出,你可以创建一个组件,然后通过对DOM中的元素命名,而将其附到你想将其作为渲染方法第二个参数的DOM上。这个被命名的元素在之前的例子中是document.getElement ById('todoapp')
。在之后的一些示例中,你会看到TodoApp
组件如何被创建,并获悉表现React组件是如何组成的基本思想,本书后面会详细涵盖所有这些内容。
var TodoApp = React.createClass({
/* several methods omitted for brevity */
render: function() {
/* see next example */
}
});
在该示例中,你可以看到一些构成TodoApp
组件的核心概念。首先,使用函数React.createClass()
创建它。该函数接受一个对象。createClass
方法将在下一章中深入介绍,还有如何使用ES6的类创造这样的组件。在TodoMVC
应用程序中,该对象有几种方法,但在该示例中强调渲染方法是很重要的,对于所有React组件它是必需的。你可以在清单1-8中更仔细地检视它们。这是一种代码量很大的方法,因为React做的事情很大一部分用这种方法处理,所以读完它需要有耐心。
清单1-8 TodoMVC的React渲染方法
render: function() {
var footer;
var main;
var todos = this.props.model.todos;
var showTodos = todos.filter(function (todo) {
switch (this.state.nowShowing) {
case app.ACTIVE_TODOS:
return !todo.completed;
case app.COMPLETED_TODOS:
return todo.completed;
default:
return true;
}, this);
var todoItems = shownTodos.map(function (todo) {
return (
<TodoItem
key={todo.id}
todo={todo}
onToggle={this.toggle.bind(this, todo)}
onDestroy={this.destroy.bind(this, todo)}
onEdit={this.edit.bind(this, todo)}
editing={this.stat.editing === todo.id}
onSave={this.save.bind(this, todo)}
onCancel={this.cancel}
****/>
);
}, this);
var activeTodoCount = todos.reduce(function (accum, todo) {
return todo.completed ? accum : accum + 1;
}, 0);
var completedCount = todos.length - activeTodoCount;
if (activeTodoCount || completedCount) {
footer =
<TodoFooter
count={activeTodoCount}
completedCount={completedCount}
nowShowing={this.state.nowShowing}
onClearCompleted={this.clearCompleted}
/>;
}
if (todos.length) {
main = (
<section id="main">
<input
id="toggle-all"
type="checkbox"
onChange={this.toggleAll}
checked={activeTodoCount === 0}
/>
<ul id="todo-list">
{todoItems}
****</ul>**
);
}
return (
<div>
<header id="header">
<h1>todos</h1>
<input
ref="newField"
id="new-todo"
placeholder="What needs to be done?"
onKeyDown={this.handleNewTodoKeyDown}
autoFocus={true}
/>
</header>
{main}
{footer}
</div>
);
}
如你所见,有许多东西要解释,但我希望你也能从发展的角度看到它是多么简单和多么具有声明性。它显示了React是如何比其他框架更具声明性的,包括AngularJS的例子。在你的应用被渲染时,这种声明性的方法精准地显示了你将在你的页面上看到的。
在前文中你已见过组件<TodoApp model={model}>
了。该组件在app.jsx
文件的末尾的渲染函数中作为主要组件。在刚刚的示例中,我加粗了代码中的关键点。首先,注意在TodoApp
的开头,model={model}
被传入函数,然后作为this.props.model.todos
被处理。这是React声明性本质的一部分。你可以在组件中声明属性,并在你的组件中的方法里使用this.props
对象中的它们。
下面是子组件(subcomponent)的概念。创建变量todoItems
是为了引入另一个叫作<TodoItem/>
的React组件。TodoItem
是另一个React组件,是在它所在的JSX文件中被创建的。TodoItem
组件具体描述特定TodoItems
的行为,并且它可以在TodoApp
组件中作为被命名的元素,这是一个极为强大的概念。随着你使用React建立越来越复杂的应用程序,你会发现恰好知道你需要改变的组件,并且它是自包含的(self-contained),这将会在你应用程序的稳定性上给予你很大的信心。清单1-9是TodoItems
中的渲染函数,及其整体上的组件。
清单1-9 TodoItems渲染方法
app.TodoItem = React.createClass({
/* omitted code for brevity */
render: function () {
return (
<li className={React.addons.classSet({
completed: this.props.todo.completed,
editing: this.props.editing
})}>
<div className="view">
<input
className="toggle"
type="checkbox"
checked={this.props.todo.completed}
onChange={this.props.onToggle}
/>
<label onDoubleClick={this.handleEdit}>
{this.props.todo.title}
</label>
<button className="destroy" onClick={this.props.onDestroy} />
</div>
<input
ref="editField"
className="edit"
value={this.state.editText}
onBlur={this.handleSubmit}
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
/>
</li>
);
}
});
在该示例中,你可以看到TodoItem
组件的渲染,它是TodoApp
的子组件。它就是个操纵TodoApp
中所包含的独立列表项的组件。它分裂成自己的组件,因为在应用程序中它代表它自己的一系列交互。除了可以操纵编辑,它也可以标记项目是否完成。这个功能并不一定需要知道应用程序的其余部分或与应用程序的其余部分交互,因此是作为独立组件被构建的。它本来可能仅是要简单地添加到TodoApp
上的,但正如你将在后面所见的,在React的世界中,东西的模块化程度越高越好。这是因为通过利用该交互的逻辑独立性,未来的维护花费会得到补偿。
现在你在较高层面上明白了往往可以如何把交互包含在React应用程序的子组件中。TodoApp渲染函数的代码显示了TodoItem
作为子组件而存在,也显示了包含在JSX中的TodoFooter
囊括它自己的交互。下一个重要概念集中在这些子组件如何被重组。TodoItems
被添加到另一个无序列表中,该列表被放在变量main
里,它返回TodoApp
的主要部分的JSX标签。类似地,footer
变量包含了TodoFooter
组件。footer
和main
这两个变量被添加在TodoApp
的返回值里,你可以在示例的末尾看到它。JSX通过使用花括号访问这些变量,你可以在下面看到它们:
{main}
{footer}
现在你有了React应用程序和组件是如何被构建的总体印象,虽然仅是基础概况。你可以通过访问todomvc.com
,将这些思想与 Ember.js
和Angular 或任何其他框架所构建的相同应用程序的概况进行比较。 React 作为框架是与众不同的,因为它就是利用JavaScript制作复杂用户界面的一种方式。这意味着声明性的组件可以包含所有交互,而不需要像其他框架那样通过直接观测来创建数据绑定。标签是,或至少可以是,利用嵌入的 XML语法的JSX所生成的。最后,你可以把这一切放在一起创建自定义组件,比如单个<Todo Appl>
。
5 React的概念和术语
这一段重点讲解一些贯穿全书的关键术语和概念,它们会帮助你更清楚地理解之后的章节写了什么。你也会从工具箱获得能让你使用React立马变得顺手的工具和实用功能。第2章深入讲解许多React核心的概念和构建React应用程序和执行React插件和附件的进程。
5.1 React入门
现在你已经对React有了大致的了解,知道它是什么以及为什么它很重要,更重要的是还要知道获取React并开始使用它。在React文档中,有链接连到可编程的JSFiddle演示,你可以播放它们。这些应该足以让你继续阅读和学习本书。
带JSX的JSFiddle: http://jsfiddle.net/reactjs/69z2wepo/
不带JSX的JSFiddle: http://jsfiddle.net/reactjs/5vjqabv3/
除了在浏览器中开发,还有个最易学习React的方式,就是访问React入门网站,单击Download Starter Kit的大按钮,如下所示。
你当然可以获取源文件,然后将它用script标签放在你的应用程序中。但实际上,Facebook在其CDN上托管了一份,链接可以在React的下载页面https://facebook.github.io/react/downloads.html
找到。当你将React放在script标签上时,变量React将是全局对象,只要页面加载React资源,你就可以访问它。
更常见的是,你会看到人们将React集成到他们用Browserify和WebPack工具搭建的工作流中。这样做可以让你以更符合CommonJS模块加载系统的方式做require('React')
[3]。开始这一过程,你需要通过npm
安装React:
npm install react
5.2 组件
组件是React的核心,也是你应用程序的视图。这是通过调用React.createClass()
所创建的典型,如下:
var MyClass = React.createClass({
render: function() {
return (
<div>hello world</div>
);
}
});
或通过使用ES6的类,例如:
class MyClass extends React.Component {
render() {
return <div>hello world</div>;
}
}
你将在下一章见到更多关于React组件的内容。
5.3 虚拟DOM
也许React最重要的部分是虚拟DOM的概念。这是本章开头间接提到的,本章开头提到,每次遇到数据改变或用户与应用程序交互时,Facebook都要重建界面。注意,虽然Facebook认识到尚不成熟的框架的性能并不是其性能标准,但仍然想让它在理想状态下工作。所以Facebook从伴随每次数据改变的一系列DOM的改变入手,以此改变框架,这就是所谓的调合(reconciliation)。Facebook通过创建虚拟DOM,在每次遇到更新时,计算更新应用程序的实际DOM所需的最小改变。你会在第2章进一步学到该过程。
5.4 JSX
之前已讲到,JSX是转换XML语法的转换层,这是为了用React在JavaScript中渲染元素的语法编写React组件。这不是React必需的元素,但这肯定是最被重视的,并且可以更平滑地构建应用程序。该语法不仅可以接受自定义React类,还可以接受纯HTML标签。它将标签转换成适当的React元素,如下面的示例所示。
// JSX version
React.render(
<div>
<h1>Header</h1>
</div>
);
// This would translate to
React.render(
React.createElement('div', null,
React.createElement('h1', null, 'Header')
);
);
你会在第3章深入阅读JSX的概况时,看到这方面的所有细节。
5.5 属性
属性通常在React中用this.props
引用,因为这是访问属性最频繁的方式。属性是组件的一系列选项。this.props
是React中的纯JavaScript对象。这些属性在组件的生命周期中自始至终都不会改变,所以你不应该把它们当作可改变的。如果你想改变组件中的东西,你会改变其状态,且应该利用状态对象。
5.6 状态
状态在每个组件初始化时设定,也会随着组件的生命周期而变。状态不应该从组件外部访问,除非父组件添加或设置了该组件的初始状态。不过总的来说,你仍应该尝试以尽可能小的状态对象制作你的组件。这是因为当你添加状态时,组件的复杂性也会增加,因为React组件不会根据状态随时间改变。如果可以避免,在组件内完全没有任何状态也是可以的。
5.7 Flux
Flux是个与React密切相关的项目。理解其如何与React协作是很重要的。Flux是Facebook的应用程序架构,用于让数据知道如何以一种有条理且有意义的方式与React组件交互。Flux不是模型-视图-控制器架构,因为那些架构利用了双向(bi-directional)数据流。Flux对于React是必不可少的,因为它有助于React组件以希望的方式使用这些组件。Flux是通过创建单向(one-directional)数据流这样做的。数据流通过FLux架构的三个主要部分:分派器、存储器以及最终的React视图。这里不会深入探讨Flux,但在第5章和第6章,你会看到Flux的整体介绍,然后学习将其集成到你的React应用程序中,这样对React的介绍就完整了。
5.8 工具
有几个工具可以让React开发更有趣。要访问JSX转换器,可以通过npm
在命令行中安装它们,使用该命令:
npm install -g react-tools
这些实用工具和综合编辑器,大部分在https://github.com/facebook/react/wiki/Complementary-Tools#jsx-integrations
中列出。你可能会找到你需要的工具。举个例子,如果你使用Sublime Text或Vim编辑JavaScript,可使用代码高亮插件。
另外有一些对检查(lint)代码很有用的工具。为了检查你的文件,JSX 提供一些特别的检验,有个jsxhint
项目是目前流行的JSHint提示工具的JSX版本。
随着开发的进行,你极有可能最后会需要在浏览器中检查你的React项目。目前有个Chrome插件可以在https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
找到,它可以让你直接检查你的React组件。当你调试和优化你的React应用程序的时候,你可以获取关于属性、状态的重要信息和所有你需要的细节。
5.9 附件
Facebook在React.addons
对象中提供了一些实验性的React附件。在开发应用程序时,只能通过使用/react-with-addons.js
文件获取。另一个选择是,如果你通过React的npm
包使用了Browserify或者WebPack,你可以把你的require('react')
改为require('react/addons')
。你可以在React的站点https://facebook.github.io/react/docs/addons.html
找到关于当前可用的附件的文件。
除了上述附件,还有几个社区附件,它们对React开发非常有用。这些附件的数量仍在增加,我们仅举一个有用附件的例子,是一个叫作react-router的项目,它为React应用程序提供了路由功能。
var App = React.createClass({
getInitialState: function() {
},
render: function () {
return (
<div>
<ul>
<li><Link to="main">Demographics</Link></li>
<li><Link to="profile">Profile</Link></li>
<li><Link to="messages">Messages</Link></li>
</ul>
<UserSelect />
</div>
<RouteHandler name={this.state.name}/>
</div>
);
}
});
var routes = (
<Route name="main" path="/" handler={App}>
<Route name="profile" handler={Profile}/>
<Route name="messages" handler={Messages}/>
<DefaultRoute handler={ Demographics }/>
</Route>
);
Router.run(routes, function (Handler, state) {
React.render(<Handler />, document.getElementById("content"));
});
这个例子展示了路由器如何处理菜单选择并将如何从路由器转至相应的组件。这是对React的有力延伸,没它也行,但有了它事情就简单了。React社区庞大,发展迅速。你可能会遇到新的附件,在创建自己的React应用的过程中甚至可能会创建自己的附件。下一章你会见到React更多的核心内容,学到React是如何工作的,这将有助于你进一步掌握React,进一步理解它为什么这么重要。
6 小结
本章介绍了使Facebook构建React的概念。你学到了React的概念通常被认为是从用户界面开发中的大众认可的最佳实践转换而来的。挑战现状和验证理论让React成为了针对构建用户界面的高性能和高可伸缩的JavaScript框架。
你还通过一些例子看到了React如何以新方式解决一些领先的模块-视图-控制器框架的视图部分。
最后,你对术语、概念和构成React框架的工具及其社区有了一定了解。在下一章中,你将深入了解如何使用React以及它的作用。
[1] 中文版为《UNIX编程艺术》。(本书所有脚注均为译者所加。)
[2] 由trans与compiler合成而来,也译作转译器。
[3] 原文为`to require(‘React’)`。require既为动词也为代码。
本文截选自《React导学》,由人民邮电出版社异步社区出版,定价39。
本书是介绍React的快速实践指南。全书共6章,系统地概括了有关React的方方面面,详细介绍了React的概念、核心、JSX原理、网页应用的构建、程序架构、Flux的用法等内容。目录见下图。