概述

React 是近期非常热门的一个前端开发框架,其本身作为 MVC 中的 View 层可以用来构建 UI,也可以以插件的形式应用到 Web 应用非 UI 部分的构建中,轻松实现与其他 JS 框架的整合,比如 AngularJS。同时,React 通过对虚拟 DOM 中的微操作来实对现实际 DOM 的局部更新,提高性能。其组件的模块化开发提高了代码的可维护性。单向数据流的特点,让每个模块根据数据量自动更新,让开发者可以只专注于数据部分,改善程序的可预测性。

React 简介

虚拟Dom(Virtual DOM)

传统的web应用,操作DOM一般都是直接进行更新操作的,但对DOM进行更新通常是比较昂贵的。而React为了尽可能减少对DOM的操作,提供了一种强大的方式来更新DOM,代替直接的DOM操作,这就是Virtual DOM,一个轻量级的虚拟的DOM。
React抽象出一个对象,然后通过这个Virtual DOM去更新真实的DOM,由这个Virtual DOM管理真实DOM的更新。简单说, React在每次需要渲染时,会先比较当前DOM内容和待渲染内容的差异, 然后再决定如何最优地更新DOM。这个过程被称为reconciliation。
除了性能的考虑,React引入虚拟DOM更重要的意义是提供了一种一致的开发方 式来开发服务端应用、Web应用和手机端应用。

React 介绍及实践教程_高性能

虚拟 DOM 是一个 JavaScript 的树形结构,包含了 React 元素和模块。组件的 DOM 结构就是映射到对应的虚拟 DOM 上,React 通过渲染虚拟 DOM 到浏览器,使得用户界面得以显示。与此同时,React 在虚拟的 DOM 上实现了一个 diff 算法,当要更新组件的时候,会通过 diff 寻找到要变更的 DOM 节点,再把这个修改更新到浏览器实际的 DOM 节点上,所以在 React 中,当页面发生变化时实际上不是真的渲染整个 DOM 树。

虚拟 DOM 中数据更新过程示意图:

React 介绍及实践教程_数据_02

那为什么采用虚拟DOM会加快速度呢?
是因为虚拟DOM采用了Diff算法(​​​虚拟DOM技术揭秘​​)。在Web开发中,我们总需要将变化的数据实时反应到UI上,这时就需要对DOM进行操作。而复杂或频繁的DOM操作通常是性能瓶颈产生的原因(如何 进行高性能的复杂DOM操作通常是衡量一个前端开发人员技能的重要指标)。React为此引入了虚拟DOM(Virtual DOM)的机制:在浏览器端用Javascript实现了一套DOM API。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都会重新构建整个DOM树,然后React将当前 整个DOM树和上一次的DOM树进行对比,得到DOM结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新。而且React能够批处理虚拟 DOM的刷新,在一个事件循环(Event Loop)内的两次数据变化会被合并,例如你连续的先将节点内容从A变成B,然后又从B变成A,React会认为UI不发生任何变化,而如果通过手动控 制,这种逻辑通常是极其复杂的。尽管每一次都需要构造完整的虚拟DOM树,但是因为虚拟DOM是内存数据,性能是极高的,而对实际DOM进行操作的仅仅是 Diff部分,因而能达到提高性能的目的。这样,在保证性能的同时,开发者将不再需要关注某个数据的变化如何更新到一个或多个具体的DOM元素,而只需要 关心在任意一个数据状态下,整个界面是如何Render的。

传统的web应用和采虚拟DOM技术的对比图:

React 介绍及实践教程_angularjs_03

React组件

React 中最基础最重要的就是 Component 了,它的构造和功能相当于 AngularJS 里面的 Directive,或是其他 JS 框架里面的 Widgets 或 Modules。Component 可以认为是由 HTML、CSS、JS 和一些内部数据组合而成的模块。当然 Component 也可以由很多 Component 组建而成。不同的 Component 既可以用纯 JavaScript 定义,也可以用特有的 JavaScript 语法 JSX 创建而成。关于 JSX,我们会在后面加以详细介绍。
对于React而言,将UI分成不同的组件,每个组件都独立封装。在React开发中,整个UI通过小组件构成的大组件,每个组件实现自己的逻辑部分即可,彼此独立。

为了更好的理解组件的概念,我们来看一个实例。

React 介绍及实践教程_ui_04

如图,在上面的实例中,当用户把鼠标移动到底部的颜色条上时,上方方框内会提示对应的颜色。最外面一层父级 Component 称为 ColorPanel,同时它包含了两个子 Component:上方的 ColorDisplay 和下方的 ColorBar。
通过上面上面一段的介绍,相信您已经对于 React 有了一定的概念,下面就让我们开始着手自己开发一个 React 的小实例吧!

创建一个 Component

var ColorPanel = React.createClass({
render: function()
return (
<div>
Color Panel
</div>
)
}
});
React.render(<ColorPanel/>, document.getElementById('demo'));

说明:通过 createClass 方法来创建了一个 Component。其中,createClass 需要传入一个 object 对象,这个对象可以定义不同的属性,render 方法是必须存在的,因为 render 方法的返回值代表的是 Component 的 template。

看到这里,您可能会觉得疑惑:为什么 JavaScript 代码中嵌入了 HTML 标签?其实这段嵌套在在 render 方法里面的并非真正意义上的 HTML,React 称之为“JSX”。JSX 可以允许把 HTML 类似的语法转换为轻量级的 JavaScript 对象。

使用JSX 转化成 JavaScript 的方式来创建 Component。

var ColorPanel = React.createClass({
displayName: "ColorPanel",
render: function()
return React.createElement("div", null, "Color Panel");
}
});
React.render(<ColorPanel/>, document.getElementById('demo'));

使用 JSX,开发人员还可以跳出 HTML 节点的限制自定义 Component。

给 Component 添加 state

对于 UI 界面元素的诸多状态使得维护 UI 页面变的十分困难, React 却让着一切变的轻松起来。因为在 React 中,每一个 Component 都会维护自己的 state,当子 Component 需要这些 state 作为 props 时,则将其顺序传递下去。换句话说,如果有多个层级的 Component,它们公共的父级 Component 负责管理 state,然后通过 props 把 state 传递到子 Component 中。

为Component 添加 state:

var ColorPanel = React.createClass({
getInitialState: function()
return {
selectedColor: 'red'
}
},
render: function()
return (
<div>
{this.state.selectedColor}
</div>
)
}
});
React.render(<ColorPanel/>, document.getElementById('demo'));

我们通过创建组件时添加了一个 getInitialState 方法,来设置 Component 的 state 的,它返回的是一个对象包含了 Component 的 data 或者 state 值。在本例中,通过这个方法告诉 Component 需要保存一个叫 selectedColor 的对象,在 Component 的 render 方法中就可以通过{this.state.selectedColor}来使用。
当然,Component 也可以来更改这个内部状态值,这要通过 setState 方法。之前说的“某一信号触发应用中某些数据发生改变”指的就是 setState 方法。不管 setState 方法何时调用,虚拟 DOM 都会被重新渲染,之后运行差异算法并按需更新真实的 DOM。

从父 Component 中获取 State

React 里有一个非常常用的模式就是对组件做一层抽象。组件对外公开一个简单的属性(Props)来实现功能,但内部细节可能有非常复杂的实现。

通过 props,React 框架可以保持良好的数据的直线传递——在最顶层的父级 Component 中处理所需要使用的特殊数据,当子的 Component 也需要使用时就把它们通过 props 来传递下去。 事实上 props 对于 Component 就像 Attribute 对于 HTML 一样,当我们提供了 property 的 name 和 value 时就传递到 Component 里面了。在 Component 里面通过 this.props 来获取 Properties。

从父节点中获取 state实例:

var ColorPanel = React.createClass({
render: function() {
return (
<div>
<ColorBar colors={this.props.colors} />
</div>
);
},
});
var colors = [
{id: 1, value: 'red', title: 'red'},
{id: 2, value: 'blue', title: 'blue'},
{id: 3, value: 'green', title: 'green'},
{id: 4, value: 'yellow', title: 'yellow'},
{id: 5, value: 'pink', title: 'pink'},
{id: 6, value: 'black', title: 'black'}
];
React.render(<ColorPanel colors={colors}/>, document.getElementById('demo'));

我们可以通过改变 render 函数的内容来改变组件的行为。但是如果想要根据外部的信息来改变组件的行为,就需要使用 Properties。
通过 getDefaultProps 获取 props 初始值实例:

var ColorPanel = React.createClass({
getDefaultProps: function() {
return {
colors: [
{id: 1, value: 'red', title: 'red'},
{id: 2, value: 'blue', title: 'blue'},
{id: 3, value: 'green', title: 'green'},
{id: 4, value: 'yellow', title: 'yellow'},
{id: 5, value: 'pink', title: 'pink'},
{id: 6, value: 'black', title: 'black'}
]
}
},
render: function() {
return (
<div>
<ColorBar colors={this.props.colors} />
</div>
);
},
});
React.render(<ColorPanel/>, document.getElementById('demo'));

Data Flow(单向数据流)

传统的MVC开发模式:

React 介绍及实践教程_高性能_05

到了 Flux 当中, 除了名字改变了, 重要的是大量的 Model 归到了 Store, View 也统一了,从而得到了所谓单向的数据流, 就是 Model 和 View 之间关系非常清晰了。React 标榜自己是 MVC 里面 V 的部分,那么 Flux 就相当于添加 M 和 C 的部分,Flux 是 Facebook 使用的一套前端应用的架构模式。
一个完整的Flux项目主要由以下4部分构成:
1. dispatcher 处理动作分发,维护 Store 之间的依赖关系
2. stores 数据和逻辑部分
3. views React 组件,这一层可以看作 controller-views,作为视图同时响应用户交互
4. actions 提供给 dispatcher 传递数据给 store

React采用单向的数据流,即父节点传递到子节点的传递,因此更加灵活便捷,也提高了代码的可控性。简单的总结下单向数据流的流程如下:Action -> Dispatcher -> Store -> View

来看一个完整的流程图:

React 介绍及实践教程_数据_06

Flux

Flux 其实就是一种单向数据流的模式。与常见的 View 和 Store 相互交互的 MVC 模式不同的是,Flux 引入了 Dispatcher。用户在 View 上的操作,不会直接引起 Store 的变化,而是通过 Action 触发在 Dispatcher 上注册的回调函数,从而触发 Store 数据的更新,最终组件会重新渲染。这样一来,即可以保证数据流是单向流通的。Flux 相对于 MVC,让事情变得更加可以预见。

Flux数据单向数据流流程图:

React 介绍及实践教程_高性能_07

当用户和 View(Component)交互的时候,View 会触发一个 Action 到中央 Dispatcher。然后将 Action 再分配到保存着应用的数据和业务逻辑的 Store 里面,Store 内数据的更新会引起所有 View 的更新。这个对 React 这种申明式的语法非常适用,这样就允许 Store 在更新的时候,不用去关注不同的 View 和 State 是如何交互的。
Dispatcher 类似于一个中央枢纽,管理着所有的数据流。它本身没有什么业务逻辑,只是负责把不同的 Action 分发到不同的 Store 中。其逻辑是 Store 会在 Dispatcher 中按照不同的 actiontype 注册不同的回调函数。当 Action 到达 Dispatcher 的时候,Dispatcher 会根据 Action 的 type 找到之前注册 Store,并触发其回调函数,从而更新 Store 的数据,达到 View 的更新。
Store 存储着应用的 state 和逻辑。它们的角色类似于传统 MVC 里面的 model,但是它们可以管理许多对象的状态,它们不只是代表了一个 ORM 模型或者说像 Backbone 里面的 collection。它们更多的是管理着应用的一个特定领域状态。就像之前提到的,Store 会注册自己和对应的回调函数到 Dispatcher 上。回调函数会接收 Action 作为参数。不同的 actiontype 会对应不同的业务逻辑,从而更新不同的 state。当 Store 被更新后会广播告知 View 去更新 Component。
Views 和 controller-views:React 提供了可组建的、会自动重新渲染的 View。在嵌套结构的最上层,有一种特殊类型的 View 监听这着 Store 的广播,我们称之为 controller-view,它会获取 Store 里面的数据,然后会调用 setState 方法,从而触发该 Component 的 render 方法和子 Component 的 render 方法。
我们通常会把 Store 的所有的 state 放在一个对象里面沿着链式地 View 传递下去,这样允许不同的子孙 Component 都能获取的它们所需。并且把这种 controller-view 放在嵌套结构的最顶层,是为了保持子孙 Component 逻辑更简单,而且也可以减少需要管理的 props 的数量。
Dispatcher 暴露了一个方法,允许我们来触发 Action 到 Store 的转发,而且可以把 Action 作为参数传递过去。Action 除了来自用户和 View 的交互,也可以来自 server 端。

组件的生命周期

每个组件都有自己的生命周期,在此期间 React 提供了很多方法用于对不同阶段的组件加以操作。

React 介绍及实践教程_高性能_08

组件的生命周期主要可以分为三个阶段:Mounting、Updating、Unmounting。React 在每一阶段开始前提供了will 方法用于执行恰好在这一阶段开始前需要实行的操作,为每一段结束之后提供 did 方法用于执行恰好这一阶段结束时需要实现的操作。

Mounting阶段:Component 通过 React.createClass 被创建出来,然后调用 getInitialState 方法初始化 this.state。在 Component 执行 render 方法之前,通过调用 componentWillMount(方法修改 state 状态),然后执行 render。Reder 的过程即是组件生成 HTML 结构的过程。在 render 之后,Component 会调用 componentDidMount 方法。在这个方法执行之后,开发人员才能通过 this.getDOMNode()获取到组件的 DOM 节点。

Updating阶段:当有数据源变化时就会调用Updating方法。Component 的 componentWillReceiveProps 方法会监听组件中 props。监听到 props 发生修改,它会比对新的数据与之前的数据之间是否存在差别进而修改 state 的值。当比对的结果为数据变化需要对 Component 对应的 DOM 节点做出修改的时候,shouldCoponentUpdate 方法它会返回 true 用于触发 componentWillUpdate 和 componentDidUpdate 方法。在默认的情况下 shouldComponentUpdate 返回为 true。有些特殊的情况是当 component 中的 props 发生修改,但是其本身数据并没有改变,或者是开发人员手工设置 shouldComponentUpdate 为 false 时,React 就不会更新这个 component 对应的 DOM 节点了。与 componentWillMount 和 componentDidMount 相类似,componentWillUpdate 和 componentDidUpdate 也分别在组件更新的 render 过程前后执行。

UnMounting 阶段:当开发人员需要将 component 从 DOM 中移除时,就会触发 UnMounting 阶段。在这个阶段中,React 只提供了一个 componentWillUnmount 方法在卸载和销毁这个 component 之前触发,用于删除 component 中的 DOM 元素等。

JSX简介

关于Jsx的介绍请查看​​JSX简介​

React 实例

通过上面的介绍我们对React 有了一个初步的了解,下面我们通过一个颜色版的改变实例来讲讲React。
本实例主要实现:当用户把鼠标停留在某一个 colorbar 上时,就会触发 mouseover 上绑定的 onColorHover 事件,同时传递了当前颜色的 ID。在 index.html 里面引入三个 JSX 文件。
HTML 文件中引用的 JSX 文件:

<script type="text/jsx" src="js/colorBar.jsx"></script>
<script type="text/jsx" src="js/colorDisplay.jsx"></script>
<script type="text/jsx" src="js/colorPanel.jsx"></script>

colorPanel.jsx:

var ColorPanel = React.createClass({
getDefaultProps: function()
return {
colors: [
{id: 1, value: 'red', title: 'red'},
{id: 2, value: 'blue', title: 'blue'},
{id: 3, value: 'green', title: 'green'},
{id: 4, value: 'yellow', title: 'yellow'},
{id: 5, value: 'pink', title: 'pink'},
{id: 6, value: 'black', title: 'black'}
],
defaultColorId: 1
}
},
getInitialState: function()
return {
selectedColor: this.getSelectedColor(this.props.defaultColorId)
}
},
getSelectedColor: function(colorId)
if(!colorId)
return null;
var length = this.props.colors.length;
for(var i = 0; i< length; i++) {
if(this.props.colors[i].id === colorId)
break;
}
return this.props.colors[i];
},
shouldComponentUpdate: function(nextProps, nextState)
return this.state.selectedColor.id !== nextState.selectedColor.id;
},
render: function()
console.log('Render Color Panel');
return (
<div>
<ColorDisplay selectedColor={this.state.selectedColor}/>
<ColorBar colors={this.props.colors} onColorHover={this.onColorHover} />
</div>
);
},
onColorHover: function(colorId)
this.setState({selectedColor: this.getSelectedColor(colorId)});
}
});
React.render(<ColorPanel/>, document.getElementById('demo'));

colorBar.jsx:

var ColorBar = React.createClass({
shouldComponentUpdate: function(nextProps, nextState)
return false;
},
render: function()
console.log('Render Color Bar Component');
return (
<ul>
{this.props.colors.map(function(color){
return (
<li key={color.id}
onMouseOver={this.props.onColorHover.bind(null, color.id)}
className={color.value}></li>
)
}, this)}
</ul>
);
}
});

colorDisplay.jsx

var ColorDisplay = React.createClass({
shouldComponentUpdate: function(nextProps, nextState)
return this.props.selectedColor.id !== nextProps.selectedColor.id;
},
render: function()
console.log('Render Color Display Component');
return (
<div className="color-display">
<div className={this.props.selectedColor.value}>
{this.props.selectedColor.title}
</div>
</div>
);
}
});