React的起源

Imgur

React来自于Facebook,是的,就是那个你们听说过但是打不开的网站。Facebook的开发者当时在开发一个广告系统,因为对当前所有的MVC框架不满意,所以就自己写了一个UI框架,于是就有了React。后来因为觉得实在是好用,所以在2013年月份开源的此框架。经过这几年的沉淀,React越来越强大,也受到了越来越多的开发者喜爱。React目前(2015-05-04)的版本是0.14.0,从版本号上看还没有达到1.0版,意味着React还在频繁地修改,普遍应用于产品中还需要一定的时间。2015年三月份的F8开发者大会上,Facebook又发布了React Native,正式把React的触角伸向了APP。同时还为React native开发了一款基于Atom的IDE-Nuclide,也是开源。

React来势汹汹,大有一统江湖的气势。前端开发者应该保持学习新技术的热情,很有必要熟悉React相关技术。下面我们简要谈谈React相关的技术。

React的设计思想

熟悉一个新技术的关键是熟悉他的特色和理念

React框架本身和我们常用的JavaScript MVC框架,如:AngularJS,Backbone,Ember等,没有直接的可比性。在React的官方博客中明确阐述了React不是一个MVC框架,而是一个用于构建组件化UI的库,是一个前端界面开发工具。所以顶多算是MVC中的V(view)。React并没有重复造轮子,而是有很多颠覆性的创新,具体的特性如下:

编写简单直观的代码

在年初的React开发者大会上,React的项目经理Tom Occhino讲述了React的最大的价值,React最大的价值不是高性能的虚拟DOM、封装的事件机制、服务器端渲染,而是声明式的直观的编码方式。React号称能让新人第一天开始使用就能开发新功能。简单的编码方式会让新手能很快地上手,同时也降低了代码维护的成本。这一特性决定了React能快速引起开发者的兴趣并广泛传播的基础。以下是React基于这一理念的具体做法。

简化可复用的组件

React构建UI是使用组件化的方式,而不是常见的模板。组件并不是一个新概念,它是某个独立功能或者界面的封装,达到复用或者UI和业务松耦合的目的。

组件化的设计理念也出现了很多年了,我们常用的ExtJS、YUI、jQueryUI、BootStrap等等都会提供大量的可复用的UI组件。比如在Bootstrap中使用对话框组件:

// 初始化
$('#myModal').modal({
    keyboard: false
});

// 显示
$('#myModal').modal('show');

// 关闭事件
$('#myModal').on('hidden.bs.modal', function (e) {
  // do something...
});

可以看到我们常用的这些组件提供了大量的接口和配置,让开发者选择合适的使用场景。这些组件的设计复杂,使用也较繁琐,新人上手有一定的门槛。W3C也在加紧制定Web Components(即组件)的标准,试图制定一个统一的简单实用的标准化的组件概念。我们看看React是如何设计组件模型的以及其和Web Component的区别。

React框架里面使用了简化的组件模型,但更彻底地使用了组件化的概念。React将整个UI上的每一个功能模块定义成组件,然后将小的组件通过组合或者嵌套的方式构成更大的组件。这种做法已经在instagram网站上普遍实施,大家可以看看instagram的前端源代码。 如下通过一个简单的例子来阐述React中模块化的概念。这个例子来自于React的官方网站教程,是完成一个简单的评论框。这个评论框主要包含两个部分,评论列表和评论表单。显示效果如下:

简单评论框

按照React模块组合的设计,把评论框组件commentBox分为两个子组件模块:commentList和commentForm,代码如下:

<div className="commentBox">
  <h1>Comments</h1>
  <CommentList data={this.state.data} />
  <CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>

commentList和commentForm组件对应的代码如下:

<div className="commentList">
  {commentNodes}
</div>


<form className="commentForm" onSubmit={this.handleSubmit}>
  <input type="text" placeholder="Your name" ref="author" />
  <input type="text" placeholder="Say something..." ref="text" />
  <input type="submit" value="Post" />
</form>

从代码中可以看到CommentList组件又可以划分为comment组件的列表。comment组件代码如下:

<div className="comment">
  <h2 className="commentAuthor">
    {this.props.author}
  </h2>
  <span dangerouslySetInnerHTML= />
</div>

可以看出,为了完成评论框功能,使用React定义了四个不同的组件:commentBox、commentList、commentForm、comment。commentBox是由commentList和commentForm组合而来,commentList是由comment组合而来。这个例子充分体现了React组件化的理念,每个组件的UI和逻辑都定义到了内部,暴露少量的API和外部交互,组件之间组合形成更复杂的组件。总结一下,React的组件具有如下的特性:

  • 可组合:简单组件可以组合为复杂的组件
  • 可重用:每个组件都是独立的,可以被多个组件使用
  • 可维护:和组件相关的逻辑和UI都封装在了组件的内部,方便维护
  • 可测试:因为组件的独立性,测试组件就变得方便很多。

React使用了组件化的设计,所以开发者自然而然和原生的Web Components相提并论,StackExchange上有一篇精彩的讨论,解释了React的组件和原生组件的对比。文章从语言层面、样式的封装、数据绑定、DOM操作方式等这几个方面展开讨论,结论是说两者没有优劣之分,只是编码习惯问题。Web Components规范毕竟还在制定过程中,应该可以从React的组件设计方式上借鉴一些理念。在后续的文章中,会深入探讨React中组件的设计原理及使用。

虚拟DOM

在JavaScript中DOM操作是独立成为一个分支的。各浏览器在实现DOM操作库也是大同小异,都是在单独的模块中实现了DOM操作,由于各种技术上的原因,DOM操作的性能损耗相对于其他操作是很大的。在前端开发中都是需要特别尽量保持较小的DOM操作次数来提高性能。

React作为一个UI框架,不可避免要有界面上元素的交互。为了提高性能,React在操作页面交互时引入了虚拟DOM的概念。虚拟DOM是在React中用JavaScript重新实现的一个DOM模型,和原生的DOM并没有多少关系,只是借鉴了原生DOM的一些概念。虚拟DOM并没有完全实现DOM,只是保留了元素直接的层级关系和少量必要的属性。因为减少了不必要的复杂性,实践校验的结果是虚拟DOM的性能比原生DOM高很多。来看看普通DOM和虚拟DOM在代码上的差别。

如下是使用原生DOM生成的元素:

var a = document.createElement('a')
a.setAttribute('class', 'link')
a.setAttribute('href', 'https://github.com/facebook/react')
a.appendChild(document.createTextNode('React'))

那么使用虚拟DOM则代码为如下:

var a = React.createElement('a', {
    className: 'link',
    href: 'https://github.com/facebook/react'
}, 'React')

可以看到React中使用了自己实现的createElement方法来生成元素DOM结构。

基于React开发中构建的DOM都是通过虚拟DOM进行的。在React的实际的使用中,需要根据不同的数据展现不同的UI,当数据变化时,React会重新构建整个DOM树,然后将当前的DOM树和之前的比较,得到DOM树的区别,然后仅仅把变化的部分反映到实际的浏览器UI更新上。React会在同一个事件循环内合并DOM的变化,只是会对比开始和结束的DOM变化,忽略中间过程的DOM变化。尽管每次数据变化都是重新构建DOM树,但虚拟DOM的操作性能极高。这样使用React时,开发者不在需要关心数据变化时页面上DOM元素的更新,而只是关心各个数据状态下页面实际展现的效果。此外,因为React使用了由JavaScript实现的虚拟DOM,意味着可以在服务器端完成HTML结构的构建。

JSX

JSX是React的重要组成部分,他使用类似XML标记的方式来声明界面及关系,所以他只是一个文档规范。如下是一个在React里面使用JSX的例子:

var HelloMessage = React.createClass({
  render: function() {
    return <div>Hello {this.props.name}</div>;
  }
});

React.render(<HelloMessage name="John" />, mountNode);

可以看到如上使用了JSX的代码,像是HTML和JavaScript代码的混合体。很多人很不习惯这样的编码方式,认为这和我们一直倡导的表现和逻辑分离的思想相违背,是一种倒退。那么React这样的设计用意是啥呢?

React一个主要的设计理念是编写简单容易理解的代码。HTML模板的作用是让表现和逻辑分离,但是很多情况下模板还是严重依赖于业务逻辑,两者没有办法做到完全的松耦合。稍微复杂一点的例子,比如AngularJS使用了一套独特的机制来让UI和逻辑交互,示例代码如下。

<ul class="unstyled">
  <li ng-repeat="todo in todoList.todos">
    <input type="checkbox" ng-model="todo.done">
    <span class="done-"></span>
  </li>
</ul>

使用AngularJS的确从代码角度做到表现和逻辑分离,但是在HTML里面混入了大量的属性标记,这些标记但从语义上很难理解,新手比如要整个熟悉Angular中每个类似ng-*对应的用法及意义才能理解整个逻辑,所以有一定的入门门槛。如上例子使用JSX方式编写如下:

render: function () {
  var lis = this.todoList.todos.map(function (todo) {
    return  (
      <li>
        <input type="checkbox" checked={todo.done}>
        <span className="done-{todo.done}">{todo.text}</span>
      </li>);
  });
  return (
    <ul class="unstyled">
      {lis}
    </ul>
  );
}

可以看到,JSX中除了使用HTML标记之外,并没有复杂的标记。这种自然而直观的方式直接降低了React的学习门槛并且让代码更容易理解。

JSX只是简化了React的使用难度,但并不是必须的。在React中也可以不使用JSX,而是使用原生JavaScript的方式编写代码。在实际使用过程中也是把JSX转换成了JavaScript代码来运行的。React官方网站上提供了一个在线转换JSX到原生JavaScript代码的工具,通过这个工具也可以体会JSX使用上的优势及其内在原理。

Flux

Flux是另外一个独立于React的架构。之所以说Flux是一个架构而不是框架或者类库,是因为Flux仅仅用于配合React框架来处理组件和数据之间的交互。简单来说Flux就是用于管理数据流。和其他MVC框架倡导的双向数据绑定不同,Flux使用了单向数据绑定的机制,即数据模型到视图的流动。如下两个图展示MVC和Flux之间的差异:

MVC

Flux

Flux中主要使用了三个概念:Dispatcher、Action和Store。这三个概念区别于MVC的model、view和controller概念,因为MVC中更多的是数据双向绑定。

Actions是用于传递数据给Dispatcher的操作集合。Action可能来自于用户界面的操作,也可能是服务器端的数据更新。

Dispatcher是一个全局的分发器,接受Action,并传递给注册的回调函数。

Stores包含了应用的状态及注册到Dispatcher的回调函数,这些函数用于处理业务逻辑。

和React Views最密切的是Store,React view从Store取得state和其他数据,并更新界面。

总结

从以上的React相关设计可以看出,React是以降低前端开发的复杂度为原则的。使用React编写的代码也易于理解,所以适合大规模多人开发,能提高项目的开发效率和质量。

参考链接