React最初来自Facebook内部的广告系统项目,项目实施过程当中前端开发遇到了巨大挑战,代码变得愈来愈臃肿且混乱不堪,难以维护。因而痛定思痛,他们决定抛开不少所谓的“最佳实践”,从新思考前端界面的构建方式,因而就有了React。html
React带来了不少开创性的思路来构建前端界面,虽然选择React的最重要缘由之一是性能,可是相关技术背后的设计思想更值得咱们去思考。以前我也曾写过一篇React的入门文章,并提供了示例代码,你们能够结合参考。前端
上个月React发布了最新的0.13版,并提供了对ES6的支持。在新版本中,一个小小的改变是React取消了函数的自动绑定,也就是说,之前能够这样去绑定一个事件:react
<button onClick={this.handleSubmit}>Submit</button>
而在以ES6语法定义的组件中,必须写为:git
<button onClick={this.handleSubmit.bind(this)}>Submit</button>
了解前端开发和JavaScript的同窗都知道,作事件绑定时咱们须要经过bind(或相似函数)来实现一个闭包以让事件处理函数自带上下文信息,这是由JavaScript语言特性决定的。而在0.13版本以前,React会自动在初始化时对组件的每个方法作一次这样的绑定,相似于this.func = this.func.bind(this)
,这样在JSX的事件绑定中就能够直接写为onClick={this.handleSubmit}
。angularjs
表面上看自动绑定给开发带来了便利,而Facebook却认为这破坏了JavaScript的语言习惯,其背后的神奇(Magic)逻辑或许会给初学者带来困惑,甚至开发者若是从React再转到其它库也可能会无所适从。基于一样的理由,React还取消了对mixin的支持,基于ES6的React组件再也不可以以mixin的形式进行代码复用或者扩展。尽管这带来了很大不便,但Facebook认为mixin增长了代码的不可预测性,没法直观的去理解。关于mixin的思考,还能够参考这篇文章。github
以简单直观、符合习惯的(idiomatic)方式去编程,让代码更容易被理解,从而易于维护和不断演进。这正是React的设计哲学。编程
所谓可预测(predictable),即容易理解的代码。在年初的React开发者大会上,React项目经理Tom Occhino进一步阐述React诞生的初衷,在演讲中提到,React最大的价值到底是什么?是高性能虚拟DOM、服务器端Render、封装过的事件机制、仍是完善的错误提示信息?尽管每一点都足以重要。但他指出,其实React最有价值的是声明式的,直观的编程方式。canvas
软件工程向来不提倡用高深莫测的技巧去编程,相反,如何写出可理解可维护的代码才是质量和效率的关键。试想,一个月以后你回头看你写的代码,是否一眼就明白某个变量,某个if判断的含义;一个新加入的同事想去增长一个小小的新功能或是修复某个Bug,他是否对本身的代码有足够的信心不引入任何反作用?随着功能的增长,代码很容易变得愈来愈复杂,这些问题也将愈来愈严重,最终致使一份难以维护的代码。而React号称,新同事甚至在加入的第一天就能开始开发新功能。react-native
那么React是如何作的呢?数组
JSX是React的核心组成部分,它使用XML标记的方式去直接声明界面,界面组件之间能够互相嵌套。可是JSX给人的第一印象倒是至关“丑陋”。当下面这样的例子被第一次展现的时候,甚至不少人称之为“巨大的退步(Huge Step Backwards)”:
var React = require(‘React’); var message = <div class=“hello” onClick={someFunc}> <span>Hello World</span> </div>; React.renderComponent(message, document.body);
将HTML直接嵌入到JavaScript代码中看上去确实是一件足够疯狂的事情。人们花了多年时间总结出的界面和业务逻辑相互分离的“最佳实践”就这么被完全打破。那么React为什么要如此另类?
模板出现的初衷是让非开发人员也能对界面作必定的修改。但这个初衷在当前Web程序里已彻底不适用,每一个模板背后的代码逻辑严重依赖模板中的内容和DOM结构,二者是紧密耦合的。即便作到文件位置的分离,实际上二者仍是一体的,而且为了二者之间的协做而不得不引入不少机制和概念。以Angularjs的首页示例代码为例:
<ul class="unstyled"> <li ng-repeat="todo in todoList.todos"> <input type="checkbox" ng-model="todo.done"> <span class="done-{{todo.done}}">{{todo.text}}</span> </li> </ul>
尽管咱们很容易看懂这一小段模板的含义,但你却没法开始写这样的代码,由于你须要学习这一整套语法。好比说,你得知道有ng-repeat这样的标记的准确含义,其中的”todo in todoList.todos”看上去是repeat语法的一部分,或许还有其它语法存在;能够看到有{{todo.text}}这样的数据绑定,那么若是要对这段文本格式化(加一个formatter)该怎么作;另外,ng-model背后又须要什么样的数据结构?
如今来看React怎么写这段逻辑:
//... 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标记以外,并无引入其它任何新的概念(事实上HTML标记也能够彻底用JavaScript去写)。Angular中的repeat在这里被一个简单的数组方法map所替代。在这里你能够利用熟悉的JavaScript语法去定义界面,在你的思惟过程当中其实已经不须要存在模板的概念,须要考虑的仅仅是如何用代码构建整个界面。这种天然而直观的方式直接下降了React的学习门槛而且让代码更容易理解。
组件并非一个新的概念,它意味着某个独立功能或界面的封装,达到复用、或是业务逻辑分离的目的。而React却这样理解界面组件:
所谓组件,就是状态机器
React将用户界面看作简单的状态机器。当组件处于某个状态时,那么就输出这个状态对应的界面。经过这种方式,就很容易去保证界面的一致性。
在React中,你简单的去更新某个组件的状态,而后输出基于新状态的整个界面。React负责以最高效的方式去比较两个界面并更新DOM树。
这种组件模型简化了咱们思考的方式:对组件的管理就是对状态的管理。不一样于其它框架模型,React组件不多须要暴露组件方法和外部交互。例如,某个组件有只读和编辑两个状态。通常的思路多是提供beginEditing()
和endEditing()
这样的方法来实现切换;而在React中,须要作的是setState({editing: true/false})
。在组件的输出逻辑中负责正确展示当前状态。这种方式,你不须要考虑beginEditing和endEditing中应该怎样更新UI,而只须要考虑在某个状态下,UI是怎样的。显而后者更加天然和直观。
组件是React中构建用户界面的基本单位。它们和外界的交互除了状态(state)以外,还有就是属性(props)。事实上,状态更多的是一个组件内部去本身维护,而属性则由外部在初始化这个组件时传递进来(通常是组件须要管理的数据)。React认为属性应该是只读的,一旦赋值过去后就不该该变化。关于状态和属性的使用在后续文章中还会深刻探讨。
数据模型驱动UI界面的两层编程模型从概念角度看上去是直观的,而在实际开发中却困难重重。一个数据模型的变化可能致使分散在界面多个角落的UI同时发生变化。界面越复杂,这种数据和界面的一致性越难维护。在Facebook内部他们称之为“Cascading Updates”,即层叠式更新,意味着UI界面之间会有一种互相依赖的关系。开发者为了维护这种依赖更新,有时不得不触发大范围的界面刷新,而其中不少并不真的须要。React的初衷之一就是,既然总体刷新必定能解决层叠更新的问题,那咱们为何不索性就每次都这么作呢?让框架自身去解决哪些局部UI须要更新的问题。这听上去很是有挑战,但React却作到了,实现途径就是经过虚拟DOM(Virtual DOM)。
关于虚拟DOM的原理我在去年末的文章有过比较详细的介绍,这里再也不重复。简而言之就是,UI界面是一棵DOM树,对应的咱们建立一个全局惟一的数据模型,每次数据模型有任何变化,都将整个数据模型应用到UI DOM树上,由React来负责去更新须要更新的界面部分。事实证实,这种方式不但简化了开发逻辑而且极大的提升了性能。
以这种思路出发,咱们在考虑不断变化的UI界面时,仅仅须要总体考虑UI的构成。编程模型的简化带来的是代码的精简和易于理解,也即React不断提到的可预测(Predictable)的代码,代码的功能一目了然易于理解。Tom Occhino在2015 React开发者大会上也分享了React在Facebook内部的应用案例,随着新功能被不断的添加到系统中,开发进度非但没有变慢,甚至愈来愈快。
既然已经有了组件机制去定义界面,那么还须要必定的机制来定义组件之间,以及组件和数据模型之间如何通讯。为此,Facebook提出了Flux框架用于管理数据流。Flux是一个至关宽松的概念框架,一样符合React简单直观的原则。不一样于其它大多数MVC框架的双向数据绑定,Flux提倡的是单向数据流动,即永远只有从模型到视图的数据流动。
Flux引入了Dispatcher和Action的概念:Dispatcher是一个全局的分发器负责接收Action,而Store能够在Dispatcher上监听到Action并作出相应的操做。简单的理解能够认为相似于全局的消息发布订阅模型。Action能够来自于用户的某个界面操做,好比点击提交按钮;也能够来自服务器端的某个数据更新。当数据模型发生变化时,就触发刷新整个界面。
Flux的定义很是宽松,除了Facebook本身的实现以外,社区中还出现了不少Flux的不一样实现,各有特色,比较流行的包括Flexible, Reflux, Flummox等等。
Immutability含义是只读数据,React提倡使用只读数据来创建数据模型。这又是一个听上去至关疯狂的机制:全部数据都是只读的,若是须要修改它,那么你只能产生一份包含新的修改的数据。假设有以下数据:
var employee = { name: ‘John’, age: 28 };
若是要修改年龄,那么你须要产生一份新的数据:
var updated = { name: employee.name, age: 29 };
这样,原来的employee对象并无发生任何变化,相反,产生了一个新的updated对象,体现了年龄发生了变化。这时候须要把新的updated对象应用到界面组件上来进行界面的更新。
只读数据并非Facebook的全新发明,而是起源于Clojure, Scala, Haskell等函数式编程语言。只读的数据可让代码更加的安全和易于维护,你再也不须要担忧数据在某个角落被某段神奇的代码所修改;也就没必要再为了找到修改的地方而苦苦调试。而结合React,只读数据可以让React的组件仅仅经过比较对象引用是否相等来决定自身是否要从新Render。这在复杂的界面上能够极大的提升性能。
针对只读数据,Facebook开发了一整套框架immutable.js,将只读数据的概念引入JavaScript,而且在github开源。若是不但愿一开始就引入这样一个较大的框架,React还提供了一个工具类插件,帮助管理和操做只读数据:React.addons.update。
在前几天的Facebook F8开发者大会上,React Native终于众望所归的发布,它将React的思想延伸到了原生移动开发。它的口号是“Learn Once, Write Anywhere”,有React开发经验的开发人员将能够无缝的进行React Native开发。不管是组件化的思想,调试工具,动态代码加载等React具备的强大特性均可以应用在React Native。相信这会对之后的移动开发布局产生重要影响。
React对UI层进行了完美的抽象,写Web界面时甚至可以作到彻底的去DOM化:开发者能够无需进行任何DOM操做。所以,这也让对UI层进行总体替换成为了可能。React Native正是将浏览器基于DOM的UI层换成了iOS或者Android的原生控件。而Flipboard则将UI层换成了Canvas。
React Canvas是Flipboard出品的一套前端框架,全部的界面元素都经过Canvas来绘制,infoQ以前也有文章对其进行了介绍。Flipboard追求极致的性能和用户体验,所以对浏览器的缓慢DOM操做深恶痛绝,不惜大刀阔斧完全舍弃了DOM,而彻底用Canvas实现了整套UI控件。有兴趣的同窗不妨一试。
React并非忽然从哪里蹦出来,而是为了解决前端开发中的痛点而生。以简单为原则设计也决定了React具备极其平缓的学习曲线,开发者能够快速上手并应用到实际项目中。本文总结分析了其相关技术背后的设计思想,但愿经过这个角度能让你们对React有一个整体的认识,从而在React的实际项目开发中,遵循简单直观的原则,进行高效率高质量的产品开发。