如今最热门的前端框架,毫无疑问是 React 。javascript
基于 React 的 React Native 发布,结果一天以内,就得到了 5000 颗星,受瞩目程度可见一斑。html
【此文章是本人合成各版本精华部分,感谢各版本的做者:阮一峰,HackerVirus】前端
React 起源于 Facebook 的内部项目,由于该公司对市场上全部 JavaScript MVC 框架,都不满意,就决定本身写一套,用来架设Instagram 的网站。作出来之后,发现这套东西很好用,就在2013年5月开源了。java
ReactJS官网地址:http://facebook.github.io/react/
react
Github地址:https://github.com/facebook/react
jquery
因为 React 的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却很是简单。因此,愈来愈多的人开始关注和使用,认为它多是未来 Web 开发的主流工具。git
这个项目自己也越滚越大,从最先的UI引擎变成了一整套先后端通吃的 Web App 解决方案。衍生的 React Native 项目,目标更是宏伟,但愿用写 Web App 的方式去写 Native App。若是可以实现,整个互联网行业都会被颠覆,由于同一组人只须要写一次 UI ,就能同时运行在服务器、浏览器和手机(参见《也许,DOM 不是答案》)。github
既然 React 这么热门,看上去充满但愿,固然应该好好学一下。从技术角度,能够知足好奇心,提升技术水平;从职业角度,有利于求职和晋升,有利于参与潜力大的项目。可是,好的 React 教程却不容易找到,这一方面由于这项技术太新,刚刚开始走红,你们都没有经验,还在摸索之中;另外一方面由于 React 自己还在不断变更,API 一直在调整,至今没发布1.0版。算法
我学习 React 时,就很苦恼。有的教程讨论一些细节问题,对入门没帮助;有的教程写得不错,但比较短,无助于看清全貌。我断断续续学了几个月,看过二十几篇教程,在这个过程当中,将对本身有帮助的 Demo 都收集下来,作成了一个库 React Demos 。后端
下面,我就根据这个库,写一篇全面又易懂的 React 入门教程。你只须要跟着每个 Demo 作一遍,就能初步掌握 React 。固然,前提是你必须拥有基本 JavaScript 和 DOM 知识,可是你读完就会发现,React 所要求的预备知识真的不多。
首先,对于React,有一些认识误区,这里先总结一下:
一、ReactJS的背景和原理 在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的。 若是你像在90年代那样写过服务器端Render的纯Web页面那么应该知道,服务器端所要作的就是根据数据Render出HTML送到浏览器端。若是这时由于用户的一个点击须要改变某个状态文字,那么也是经过刷新整个页面来完成的。服务器端并不须要知道是哪一小段HTML发生了变化,而只须要根据数据刷新整个页面。换句话说,任何UI的变化都是经过总体刷新来完成的。而React将这种开发模式以高性能的方式带到了前端,每作一点界面的更新,你均可以认为刷新了整个页面。至于如何进行局部更新以保证性能,则是React框架要完成的事情。 借用Facebook介绍React的视频中聊天应用的例子,当一条新的消息过来时,传统开发的思路如上图,你的开发过程须要知道哪条数据过来了,如何将新的DOM结点添加到当前DOM树上;而基于React的开发思路以下图,你永远只须要关心数据总体,两次数据之间的UI如何变化,则彻底交给框架去作。能够看到,使用React大大下降了逻辑复杂性,意味着开发难度下降,可能产生Bug的机会也更少。 二、组件化 虚拟DOM(virtual-dom)不只带来了简单的UI开发逻辑,同时也带来了组件化开发的思想,所谓组件,即封装起来的具备独立功能的UI部件。React推荐以组件的方式去从新思考UI构成,将UI上每个功能相对独立的模块定义成组件,而后将小的组件经过组合或者嵌套的方式构成大的组件,最终完成总体UI的构建。例如,Facebook的instagram.com整站都采用了React来开发,整个页面就是一个大的组件,其中包含了嵌套的大量其它组件,你们有兴趣能够看下它背后的代码。 若是说MVC的思想让你作到视图-数据-控制器的分离,那么组件化的思考方式则是带来了UI功能模块之间的分离。咱们经过一个典型的Blog评论界面来看MVC和组件化开发思路的区别。 对于MVC开发模式来讲,开发者将三者定义成不一样的类,实现了表现,数据,控制的分离。开发者更多的是从技术的角度来对UI进行拆分,实现松耦合。 对于React而言,则彻底是一个新的思路,开发者从功能的角度出发,将UI分红不一样的组件,每一个组件都独立封装。 在React中,你按照界面模块天然划分的方式来组织和编写你的代码,对于评论界面而言,整个UI是一个经过小组件构成的大组件,每一个组件只关心本身部分的逻辑,彼此独立。
React认为一个组件应该具备以下特征: (1)可组合(Composeable):一个组件易于和其它组件一块儿使用,或者嵌套在另外一个组件内部。若是一个组件内部建立了另外一个组件,那么说父组件拥有(own)它建立的子组件,经过这个特性,一个复杂的UI能够拆分红多个简单的UI组件; (2)可重用(Reusable):每一个组件都是具备独立功能的,它能够被使用在多个UI场景; (3)可维护(Maintainable):每一个小的组件仅仅包含自身的逻辑,更容易被理解和维护;
React 的安装包,能够到官网下载。不过,React Demos 已经自带 React 源码,不用另外安装,只需把这个库拷贝到你的硬盘就好了。
$ git clone git@github.com:ruanyf/react-demos.git
若是你没安装 git, 那就直接下载 zip 压缩包。
下面要讲解的11个例子在各个 Demo 子目录,每一个目录都有一个 index.html 文件,在浏览器打开这个文件(大多数状况下双击便可),就能马上看到效果。
须要说明的是,React 能够在浏览器运行,也能够在服务器运行,可是本教程只涉及浏览器。一方面是为了尽可能保持简单,另外一方面 React 的语法是一致的,服务器的用法与浏览器差异不大。Demo12 是服务器首屏渲染的例子,有兴趣的朋友能够本身去看源码。
使用 React 的网页源码,结构大体以下。
<!DOCTYPE html> <html> <head> <script src="../build/react.js"></script> <script src="../build/browser.min.js"></script> </head> <body> <div id="example"></div> <script type="text/jsx"> // ** Our code goes here! ** </script> </body> </html>
上面代码有两个地方须要注意。首先,最后一个 script 标签的 type 属性为 text/babel 。这是由于 React 独有的 JSX 语法,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel" 。
其次,上面代码一共用了两个库: react.js 和 Browser.js ,它们必须首先加载。其中,Browser.js 的做用是将 JSX 语法转为 JavaScript 语法。这一步很消耗时间,实际上线的时候,应该将它放到服务器完成。
$ babel src --out-dir build
上面命令能够将 src 子目录的 js 文件进行语法转换,转码后的文件所有放在 build 子目录。
React.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点。
React.render( <h1>Hello, world!</h1>, document.getElementById('example') );
上面代码将一个 h1 标题,插入 example 节点(查看 demo01),运行结果以下。
上一节的代码, HTML 语言直接写在 JavaScript 语言之中,不加任何引号,这就是 JSX 的语法,它容许 HTML 与 JavaScript 的混写(查看 Demo02 )。
var names = ['Alice', 'Emily', 'Kate']; React.render( <div> { names.map(function (name) { return <div>Hello, {name}!</div> }) } </div>, document.getElementById('example') );
上面代码体现了 JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析。上面代码的运行结果以下。
JSX 容许直接在模板插入 JavaScript 变量。若是这个变量是一个数组,则会展开这个数组的全部成员(查看 demo03 )。
var arr = [ <h1>Hello world!</h1>, <h2>React is awesome</h2>, ]; React.render( <div>{arr}</div>, document.getElementById('example') );
上面代码的arr变量是一个数组,结果 JSX 会把它的全部成员,添加到模板,运行结果以下。
React 容许将代码封装成组件(component),而后像插入普通 HTML 标签同样,在网页中插入这个组件。React.createClass 方法就用于生成一个组件类(查看 demo04)。
var HelloMessage = React.createClass({ render: function() { return <h1>Hello {this.props.name}</h1>; } }); React.render( <HelloMessage name="John" />, document.getElementById('example') );
上面代码中,变量 HelloMessage 就是一个组件类。模板插入 <HelloMessage /> 时,会自动生成 HelloMessage 的一个实例(下文的"组件"都指组件类的实例)。全部组件类都必须有本身的 render 方法,用于输出组件。
组件的用法与原生的 HTML 标签彻底一致,能够任意加入属性,好比 <HelloMessage name="John" /> ,就是 HelloMessage 组件加入一个 name 属性,值为 John。组件的属性能够在组件类的 this.props 对象上获取,好比 name 属性就能够经过 this.props.name 读取。上面代码的运行结果以下。
添加组件属性,有一个地方须要注意,就是 class 属性须要写成 className ,for 属性须要写成 htmlFor ,这是由于 class 和 for 是 JavaScript 的保留字。
this.props 对象的属性与组件的属性一一对应,可是有一个例外,就是 this.props.children 属性。它表示组件的全部子节点(查看demo05)。
var NotesList = React.createClass({ render: function() { return ( <ol> { this.props.children.map(function (child) { return <li>{child}</li> }) } </ol> ); } }); React.render( <NotesList> <span>hello</span> <span>world</span> </NotesList>, document.body );
上面代码的 NoteList 组件有两个 span 子节点,它们均可以经过 this.props.children 读取,运行结果以下。
这里须要注意,只有当子节点多余1个时,this.props.children 才是一个数组,不然是不能用 map 方法的, 会报错。
组件的属性能够接受任意值,字符串、对象、函数等等均可以。有时,咱们须要一种机制,验证别人使用组件时,提供的参数是否符合要求。
组件类的PropTypes属性,就是用来验证组件实例的属性是否符合要求(查看 demo06)。
var MyTitle = React.createClass({ propTypes: { title: React.PropTypes.string.isRequired, }, render: function() { return <h1> {this.props.title} </h1>; } });
上面的Mytitle组件有一个title属性。PropTypes 告诉 React,这个 title 属性是必须的,并且它的值必须是字符串。如今,咱们设置 title 属性的值是一个数值。
var data = 123; React.render( <MyTitle title={data} />, document.body );
这样一来,title属性就通不过验证了。控制台会显示一行错误信息。
Warning: Failed propType: Invalid prop `title` of type `number` supplied to `MyTitle`, expected `string`.
更多的PropTypes设置,能够查看官方文档。
此外,getDefaultProps 方法能够用来设置组件属性的默认值。
var MyTitle = React.createClass({ getDefaultProps : function () { return { title : 'Hello World' }; }, render: function() { return <h1> {this.props.title} </h1>; } }); React.render( <MyTitle />, document.body );
上面代码会输出"Hello World"。
组件并非真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫作虚拟 DOM (virtual DOM)。只有当它插入文档之后,才会变成真实的 DOM 。根据 React 的设计,全部的 DOM 变更,都先在虚拟 DOM 上发生,而后再将实际发生变更的部分,反映在真实 DOM上,这种算法叫作 DOM diff ,它能够极大提升网页的性能表现。
可是,有时须要从组件获取真实 DOM 的节点,这时就要用到 React.findDOMNode 方法(查看 demo07 )。
var MyComponent = React.createClass({ handleClick: function() { React.findDOMNode(this.refs.myTextInput).focus(); }, render: function() { return ( <div> <input type="text" ref="myTextInput" /> <input type="button" value="Focus the text input" onClick={this.handleClick} /> </div> ); } }); React.render( <MyComponent />, document.getElementById('example') );
上面代码中,组件 MyComponent 的子节点有一个文本输入框,用于获取用户的输入。这时就必须获取真实的 DOM 节点,虚拟 DOM 是拿不到用户输入的。为了作到这一点,文本输入框必须有一个 ref 属性,而后 this.refs.[refName] 就指向这个虚拟 DOM 的子节点,最后经过 React.findDOMNode 方法获取真实 DOM 的节点。
须要注意的是,因为 React.findDOMNode 方法获取的是真实 DOM ,因此必须等到虚拟 DOM 插入文档之后,才能使用这个方法,不然会返回 null 。上面代码中,经过为组件指定 Click 事件的回调函数,确保了只有等到真实 DOM 发生 Click 事件以后,才会调用 React.findDOMNode 方法。
React 组件支持不少事件,除了 Click 事件之外,还有 KeyDown 、Copy、Scroll 等,完整的事件清单请查看官方文档。
组件免不了要与用户互动,React 的一大创新,就是将组件当作是一个状态机,一开始有一个初始状态,而后用户互动,致使状态变化,从而触发从新渲染 UI (查看 demo08 )。
var LikeButton = React.createClass({ getInitialState: function() { return {liked: false}; }, handleClick: function(event) { this.setState({liked: !this.state.liked}); }, render: function() { var text = this.state.liked ? 'like' : 'haven\'t liked'; return ( <p onClick={this.handleClick}> You {text} this. Click to toggle. </p> ); } }); React.render( <LikeButton />, document.getElementById('example') );
上面代码是一个 LikeButton 组件,它的 getInitialState 方法用于定义初始状态,也就是一个对象,这个对象能够经过 this.state 属性读取。当用户点击组件,致使状态变化,this.setState 方法就修改状态值,每次修改之后,自动调用 this.render 方法,再次渲染组件。
因为 this.props 和 this.state 都用于描述组件的特性,可能会产生混淆。一个简单的区分方法是,this.props 表示那些一旦定义,就再也不改变的特性,而 this.state 是会随着用户互动而产生变化的特性。
用户在表单填入的内容,属于用户跟组件的互动,因此不能用 this.props 读取(查看 demo9 )。
var Input = React.createClass({ getInitialState: function() { return {value: 'Hello!'}; }, handleChange: function(event) { this.setState({value: event.target.value}); }, render: function () { var value = this.state.value; return ( <div> <input type="text" value={value} onChange={this.handleChange} /> <p>{value}</p> </div> ); } }); React.render(<Input/>, document.body);
上面代码中,文本输入框的值,不能用 this.props.value 读取,而要定义一个 onChange 事件的回调函数,经过 event.target.value 读取用户输入的值。textarea 元素、select元素、radio元素都属于这种状况,更多介绍请参考官方文档。
组件的生命周期分红三个状态:
React 为每一个状态都提供了两种处理函数,will 函数在进入状态以前调用,did 函数在进入状态以后调用,三种状态共计五种处理函数。
此外,React 还提供两种特殊状态的处理函数。
这些方法的详细说明,能够参考官方文档。下面是一个例子(查看 demo10 )。
var Hello = React.createClass({ getInitialState: function () { return { opacity: 1.0 }; }, componentDidMount: function () { this.timer = setInterval(function () { var opacity = this.state.opacity; opacity -= .05; if (opacity < 0.1) { opacity = 1.0; } this.setState({ opacity: opacity }); }.bind(this), 100); }, render: function () { return ( <div style={{opacity: this.state.opacity}}> Hello {this.props.name} </div> ); } }); React.render( <Hello name="world"/>, document.body );
上面代码在hello组件加载之后,经过 componentDidMount 方法设置一个定时器,每隔100毫秒,就从新设置组件的透明度,从而引起从新渲染。
另外,组件的style属性的设置方式也值得注意,不能写成
style="opacity:{this.state.opacity};"
而要写成
style={{opacity: this.state.opacity}}
这是由于 React 组件样式是一个对象,因此第一重大括号表示这是 JavaScript 语法,第二重大括号表示样式对象。
组件的数据来源,一般是经过 Ajax 请求从服务器获取,可使用 componentDidMount 方法设置 Ajax 请求,等到请求成功,再用 this.setState 方法从新渲染 UI (查看 demo11 )。
var UserGist = React.createClass({ getInitialState: function() { return { username: '', lastGistUrl: '' }; }, componentDidMount: function() { $.get(this.props.source, function(result) { var lastGist = result[0]; if (this.isMounted()) { this.setState({ username: lastGist.owner.login, lastGistUrl: lastGist.html_url }); } }.bind(this)); }, render: function() { return ( <div> {this.state.username}'s last gist is <a href={this.state.lastGistUrl}>here</a>. </div> ); } }); React.render( <UserGist source="https://api.github.com/users/octocat/gists" />, document.body );
上面代码使用 jQuery 完成 Ajax 请求,这是为了便于说明。React 没有任何依赖,彻底可使用其余库。
(完)