他们是真的亲戚,可不像 Java 和 Javascript 同样。html
其实第一次看到 React 的语法我是拒绝的,由于这么丑的写法,你不能让我写我就写。前端
但当我发现 React Native 横空出世后,它学习一次处处运行的理念很是诱人。React Native 能够写出原生体验的 iOS/Android 应用?那不就多了一门装逼技能?因此咱们调研小组试了一下,感受 "Duang" 一下,很爽很舒服。写 React Native 须要两门基础技能:React 语法 和 iOS 基础知识。node
很爽很舒服,索性就研究一下,算是入门。
了解以后发现,React 真是有另外一番天地,值得学习。react
接下来总结如下我对 React 的理解,分享给你们。webpack
至于 React Native,有机会再好好探究下。git
这部分废话太多,喜欢实战的能够直接看代码部分。es6
React 是 Facebook 出品的一套颠覆式的前端开发类库。github
为何说它是颠覆式的呢?web
对于传统的 DOM 维护,咱们的步骤多是:
首先,咱们操做 DOM 是最昂贵的开销,对于 须要反复更新 DOM 的网页,无疑是噩梦。其次,对 DOM 局部的更新以及事件绑定加大了维护的难度。
而 React 引入了一个全新的概念:虚拟 DOM。
虚拟 DOM 是躺在内存里的一种特殊的结构,咱们能够理解为这是真实 DOM 在内存里的映射。
除告终构上的映射外,这个虚拟的 DOM 还包括了渲染
真实所须要的数据以及事件绑定。
虚拟 DOM 在建立时,首先是使用 JSX 的语法生成一个真实 DOM 树的映射,其次是从服务器端拉取远程数据,接着注入到这个虚拟 DOM 树中,同时绑定事件。
好了,有了虚拟 DOM、数据、事件,万事俱备。
接下来,调用 render() 方法一次性渲染出真实的 DOM,而后全量插入到网页中。
虚拟 DOM 静静地躺在内存里,等待数据更新。
新数据来临,调用 setState() 方法更新数据到虚拟 DOM 中,而后自动调用 render() 再一次性渲染出真实的 DOM ,而后全量更新到网页中。
一个虚拟 DOM,对应一个真实 DOM 一份数据更新,从新生成虚拟 DOM ,全量更新真实 DOM
就这么简单。
除了带来性能上的提高以外,很显然这种写法简化了咱们维护 DOM 的成本 -- 咱们只须要维护一份数据。
能够看到,React 里最重要的概念有虚拟 DOM、单向数据注入(虚拟 DOM 到真实 DOM)。
这里并无引入太多其余的东西,因此我对 React 的理解是一个类库,而非框架。
若是要使用 MVC、MVVM 等技术的吧,彻底能够把 React 当作其中的 V,即 View, 配合其余类库使用。
我虽然是个前端菜鸟,但日观天象也是能嗅到下一代 Web 将是组件化、组件复用共享的时代。
React 编写起来,就是编写一个个的组件。
我对一个 React 组件的理解是:
以上三者能够打包复用,甚至是无缝接入,我脚得它就多是将来了。
HTML 与 JS 使用 JSX 语法糅合到一块儿的方式是见仁见智,恐怕会引发战争。
我刚接触到 JSX 的时候,一开口也是『我*,好丑』。
但慢慢地却发现,这种方式一开始写起来别扭,但用得却很爽。
接下来,我经过编写一个简单的应用来入门 React。
看完若是大呼不过瘾,建议直飞 React 官方看文档,那才是宝藏!
示例代码放置在 demo/目录下,每一个文件夹为一个独立的示例。
先看下这个 demo 最终的样子吧:
每一个示例的入口文件 index.html 结构大致相同:
<!-- React 真实 DOM 将会插入到这里 --> <div id="demo"></div> <!-- 引入 React --> <script src="../../bower_components/react/react.js"></script> <!-- 引入 JSX 语法格式转换器 --> <script src="../../bower_components/react/JSXTransformer.js"></script> <!-- 注意:script 须要注明 type 为 text/jsx 以指定这是一个 JSX 语法格式 --> <script type="text/jsx" src="demo.js"></script> </body>
使用 render() 方法生成真实 DOM 并插入到网页中。
// 使用 React.createClass 建立一个组件 var DemoComponent = React.createClass({ // 使用 render 方法自动渲染 DOM render: function () { return ( <div className="component-hello"> <h1 className="hello-title">Hello React</h1> <p className="hello-desc">React 初探</p> <div className="hello-movies"> <p2>我喜欢的电影</p2> <ul> <li className="movie-item"> <span className="movie-name">速度与激情7</span> - <span className="movie-date">2015</span> </li> </ul> </div> </div> ) } }); // 将组件插入到网页中指定的位置 React.render(<DemoComponent />, document.getElementById('demo'));
第一次渲染真实 DOM 时将使用 getInitialState() 返回的数据。
// 使用 React.createClass 建立一个组件 var DemoComponent = React.createClass({ // getInitialState 中返回的值将会做为数据的默认值 getInitialState: function () { return { title: '我喜欢的电影', movies: [ { id: 7, name: '速度与激情7', date: 2015 }, { id: 6, name: '速度与激情6', date: 2013 } ] } }, // 使用 render 方法自动渲染 DOM render: function () { // this.state 用于存储数据 var title = this.state.title; var movies = this.state.movies.map(function (movie) { return ( <li className="movie-item" key={movie.id}> <span className="movie-name">{movie.name}</span> - <span className="movie-date">{movie.date}</span> </li> ) }); return ( <div className="component-hello"> <h1 className="hello-title">Hello React</h1> <p className="hello-desc">React 初探</p> <div className="hello-movies"> <p2>{title}</p2> <ul>{movies}</ul> </div> </div> ) } }); // 将组件插入到网页中指定的位置 React.render(<DemoComponent />, document.getElementById('demo'));
第二次更新渲染真实 DOM 时将使用 setState() 设置的数据。
// 使用 componentDidMount 在组件初始化后执行一些操做 componentDidMount: function () { // 拉取远程数据 // 开启假数据服务器: // cd fake-server && npm install && node index.js this.fetchData(); }, // 使用自定义的 fetchData 方法从远程服务器获取数据 fetchData: function () { var self = this; // 发起 ajax 获取到数据后调用 setState 方法更新组件的数据 var url = '../../fake-data/movies.json'; $.getJSON(url, function (movies) { // 本地模拟返回太快了,模拟一下网络延迟 setTimeout(function() { self.setState({ movies: movies }); }, 2000); }); },
绑定事件时,咱们可使用 ref="name" 属性对一个 DOM 节点进行标记,同时能够经过 React.findDOMNode(this.refs.name) 获取到这个节点的原生 DOM。
// 使用 render 方法自动渲染 DOM render: function () { var self = this; // this.state 用于存储数据 var title = this.state.title; var movies = this.state.movies.map(function (movie) { return ( <li className="movie-item" key={movie.id}> <span className="movie-name">{movie.name}</span> - <span className="movie-date">{movie.date}</span> <a href="#" onClick={self.onRemove.bind(null, movie)}>删除</a> </li> ) }.bind(this));// 注意这里 bind(this) 修正了上下文 return ( <div className="component-hello"> <h1 className="hello-title">Hello React</h1> <p className="hello-desc">React 初探</p> <div className="hello-movies"> <p2>{title}</p2> <form onSubmit={this.onAdd}> {/* 注意这里指定 ref 属性,而后咱们就可使用 this.refs.xxx 访问到 */} <input type="text" ref="name" placehlder="输入你喜欢的电影"/> <input type="text" ref="date" placeholder="上映时间"/> <input type="submit" value="提交"/> </form> <ul>{movies}</ul> {this.state.loading ? <div>你们好我是菊花, 我如今在转</div> : null} </div> </div> ) }
onRemove: function (movie) { var id = movie.id; console.log(movie) // 删除这个 item var movies = this.state.movies; var len = movies.length; var index = -1; for(var i = 0; i < len; i++) { var _movie = movies[i]; if (_movie.id === id) { index = i; break; } } if (index > 0) { movies.splice(index, 1); this.setState({ movies: movies }); } }, onAdd: function (e) { e.preventDefault(); var refs = this.refs; var refName = React.findDOMNode(refs.name); var refDate = React.findDOMNode(refs.date); if (refName.value === '') { alert('请输入电影名'); return; } else if (refDate === '') { alert('请输入上映时间'); return; } var movie = { // 使用 findDOMNode 获取到原生的 DOM 对象 name: refName.value, date: refDate.value, id: Date.now() // 粗暴地以时间数字做为随机 id }; var movies = this.state.movies; movies.push(movie); this.setState(movies); refName.value = ''; refDate.value = ''; },
一个组件就包含了 JSX 模板、数据维护、事件绑定的话,代码量已经够多了,这时候能够采用 AMD/CMD 的方式,将组件进行更细粒度的划分,能够以文件即组件的方式来编写,这里就不上 demo 了。
在 React 中,数据流是单向的,且组件之间能够嵌套,咱们能够经过对最顶层组件传递属性方式,向下层组件传送数据。
嵌套组件间,使用 this.props 属性向下传递数据
独立组件之间,自行维护数据则须要自行维护一个全局数据存储,或者使用发布订阅地方式通知数据的更新。
全局数据存储怎么作呢?能够理解为不一样的组件获取的数据源一致,在组件的外部维护这个数据集合,或者干脆直接从服务器端获取。
有人会说了,这样很不方便。
但我以为,既然是一个组件,那就配套有获取组件所需数据的方式,独立组件间有很强的数据依赖时,要么使用上述方式,要么能够简单粗暴,将独立组件用一个顶层组件包裹起来,转化为嵌套组件的关系,便可数据互通。
// 将子组件抽离出来 var LiWrapper = React.createClass({ render: function () { // 使用 this.props 得到传入组件的数据 var movie = this.props.movie; return ( <li>{/* ... */}</li> ) } }); // 使用 React.createClass 建立一个组件 var DemoComponent = React.createClass({ // 使用 getInitialState 的返回值做为数据的默认值 getInitialState: function () { // ... }, // 使用 render 方法自动渲染 DOM render: function () { // this.state 用于存储数据 var movies = this.state.movies.map(function (movie) { return ( <LiWrapper movie={movie}/> ) }.bind(this));// 注意这里 bind(this) 修正了上下文 return ( <div className="component-hello"> {/* ... */} <div className="hello-movies"> <ul>{movies}</ul> </div> </div> ) } }); // 将组件插入到网页中指定的位置 // 在使用组件时传入 movies 数据 var movies = [// ...]; React.render(<DemoComponent movies={movies}/>, document.getElementById('demo'));
ES6 和 gulp 的话就很少介绍啦。
webpack 是一款新生的前端构建工具,兼容 AMD/CMD 等写法,支持 Browser 和 Node 端共享代码,在浏览器端能够像写 Node 同样方便的进行模块化的划分。
在这里主要用 webpack 的两个插件:
使用 jsx-loader 这个插件支持 jsx 语法解析
使用 esx-loader 这个插件支持 es6 语法解析
来看下简单目录结构:
var React = require('react'); var MovieListComponent = require('./components/movie-list'); var HelloMessageComponent = require('./components/hello'); var movies = [ { id: 5, name: '速度与激情5', date: 2011 }, { id: 4, name: '速度与激情4', date: 2009 } ]; var wording = '保罗'; var MainComponent = React.createClass({ render: function () { return ( <div className="component-hello"> <HelloMessageComponent name={wording}/> <MovieListComponent movies={movies} /> </div> ) } }); React.render(<MainComponent />, document.getElementById('demo'));
var React = require('react'); // 引入子组件 var MovieComponent = require('./movie'); // 使用 React.createClass 建立一个组件 var MovieListComponent = React.createClass({ // 使用 getInitialState 的返回值做为数据的默认值 getInitialState: function () { return { loading: true, title: '我喜欢的电影', // 注意这里将 外部传入的数据赋值给了 this.state movies: [] } }, // 使用 render 方法自动渲染 DOM render: function () { // this.state 用于存储数据 var title = this.state.title; // this.props 用于从组件外部传入数据 var movies = this.props.movies; movies = movies.map(function (movie) { return ( <MovieComponent movie={movie}/> ) }.bind(this));// 注意这里 bind(this) 修正了上下文 return ( <ul>{movies}</ul> ) } }); module.exports = MovieListComponent;
var React = require('react'); class HelloComponent extends React.Component { constructor(props) { super(props); this.state = {wording: '你好呀, '}; } render() { return <div>{this.state.wording} {this.props.name}</div> ; } } module.exports = HelloComponent;
module.exports = { entry: ['./js/main.js'], output: { path: __dirname, filename: 'js/bundle.js' }, module: { loaders: [ { test: /\.js$/, loader: 'es6-loader' }, { test: /\.js$/, loader: 'jsx-loader' } ] } };
var gulp = require('gulp'); var livereload = require('gulp-livereload'); var webpack = require("gulp-webpack"); var webpackConfig = require('./webpack.config'); gulp.task("webpack", function() { return gulp.src('./js/main.js') .pipe(webpack(webpackConfig)) .pipe(gulp.dest('.')); }); gulp.task('watch', function() { livereload.listen(); gulp.watch(['js/**/*.js', '!js/bundle.js'], ['webpack']); }); gulp.task('default', [ 'webpack', 'watch' ]);
<!-- React 真实 DOM 将会插入到这里 --> <div id="demo"></div> <script src="./js/bundle.js"></script>
在 js/main.js 中引入两个不一样的组件,而后在 webpack.config.js 中指定编译 JSX 和 ES6 的 loader 工具,使用 gulp 监听 js/ 中文件变化,自动编译出的 js/bundle.js 将被 index.html 引用。
嗯,再在 webpack 中加入各类你喜欢的 loader,在 gulp 中加上各类 css、js、img 的处理任务,编写代码,自动从新编译,纵享丝滑。
文章到这里应该就算结束了,接下来是一些在学习过程当中记下来的几个小点,也分享给你们。
JSX 把 JS 和 HTML 糅合起来了,这么理解是否是感受比较简单:
遇到 {} 包裹的是 JS,遇到 <> 包裹的是 HTML
好比:
// 错误的写法 var MyComponent = React.createClass({ render: function () { return ( <h1>速度与激情7</h1> <p>致敬保罗</p> ) } });
应该写成:
// 正确的写法 var MyComponent = React.createClass({ render: function () { return ( <div> <h1>速度与激情7</h1> <p>致敬保罗</p> </div> ) } });
render()
返回的是一系列嵌套的组件this.props
获取父组件传递给子组件的数据this.setState({data: data});
用于动态更新状态,设置数据(设置后UI会自动刷新)getInitialState()
在整个组件的生命周期中只会执行一次,用于初始化数据componentDidMount
会在 render 后自动调用,用于异步获取数据,更新数据gitInitialState()
初始化数据render()
渲染初始化数据componentDidMount()
异步获取数据setState()
更新数据每个组件均可以理解为有一个简单的状态机。
调用 setState(data, callback) 后,data 将会混入 this.state 中,数据获得了更新,render() 就会被调用,UI 就能被更新。
<Parent><Child /></Parent>
父组件能够获取到子组件:this.props.children
render() 在 React 建立时会调用一次,在数据更新时调用 setState() 方法则会继续调用它来更新网页中的真实 DOM。
这个方法返回的值会在组件初始化第一次调用 render() 时就被使用
// 错误的写法 var MyComponent = React.createClass({ render: function () { return ( <div class="movie"> <h1>速度与激情7</h1> <p>致敬保罗</p> </div> ) } });
应该写成:
// 正确的写法 var MyComponent = React.createClass({ render: function () { return ( <div className="movie"> <h1>速度与激情7</h1> <p>致敬保罗</p> </div> ) } });
// 错误的写法 var myComponent = React.createClass({ render: function () { return ( <div class="movie"> <h1>速度与激情7</h1> <p>致敬保罗</p> </div> ) } }); React.render(<myComponent />, document.getElementById('demo'));
应该写成:
// 正确的写法 var MyComponent = React.createClass({ render: function () { return ( <div className="movie"> <h1>速度与激情7</h1> <p>致敬保罗</p> </div> ) } }); React.render(<MyComponent />, document.getElementById('demo'));
var MyComponent = React.createClass({ getInitialState: function () { return { loading: true } }, showLoading: function () { this.setState({loading: true}) }, hideLoading: function () { this.setState({loading: false}) }, render: function () { return ( { this.state.loading ? <div>你们好我是菊花,我在转</div> : null } ) } });
React 会为咱们过滤 XSS,要让一段 HTML 片断直接显示出来,须要这样:
<div dangerouslySetInnerHTML={{__html: 'First · Second'}} />
React.initializeTouchEvents(true);
表单由于会因用户交互而变化,因此有特定的一些属性
input[type=checkbox]
和 input[type=radio]
具备 checked<select multiple={true} value={['B', 'C']}>
表单项具备 onChange 事件
注意若是这么写:
render: function() { return <input type="text" value="Hello!" />; }
那每次 render 的时候 input 的 value 都会被重置为 "Hello!",因此须要这么控制:
getInitialState: function() { return {value: 'Hello!'}; }, handleChange: function(event) { this.setState({value: event.target.value}); }, render: function() { var value = this.state.value; return <input type="text" value={value} onChange={this.handleChange} />; }
利用这点,能够无缝地接入一些验证规则,好比限制文字为 140 字:
handleChange: function(event) { this.setState({value: event.target.value.substr(0, 140)}); }
若是不想这么被控制呢?那就在返回 input 的时候,不要设置 value 属性,这样随着用户输入,value 不会被重置:
render: function() { return <input type="text" />; }
也能够设置默认值:
render: function() { return <input type="text" defaultValue="Hello!" />; }
除了 defaultValue
以外,还支持 defaultChecked
React 会在内存里维护一个表明 DOM 的结构,调用
render 方法时才生成真正的 DOM 插入到网页中。
一个组件的声明周期能够理解为三个阶段:
getInitialState()
被调用,返回原始数据componentWillMount()
在组件 mounting 前调用componentDidMount()
在组件 mounting 完成后调用componentWillReceiveProps(nextProps)
在接收到新的 props 时调用shouldComponentUpdate(nextProps, nextState)
在组件须要更新 DOM 时调用,若这个函数返回 false 则告诉 React 不要更新componentWillUpdate(nextProps, nextState)
在更新发生时调用,能够在这里调用 this.steState() 刷新数据componentDidUpdate(prevProps, prevState)
在更新完成后调用forceUpdate() 强制使用数据更新组件,而不用调用 this.setState()
React.findDOMNode(component)
返回原生的 DOM 元素
注意要获取原生的 DOM 元素,必须在 render 被调用, 真正的 DOM 已经被插入到页面中时。
能够把 refs 理解为咱们在 HTML 中的id,用于定位到指定的组件。
<form onSubmit={this.onAdd}> {/* 注意这里指定 ref 属性,而后咱们就可使用 this.refs.xxx 访问到 */} <input type="text" ref="name" placehlder="输入你喜欢的电影"/> <input type="text" ref="date" placeholder="上映时间"/> <input type="submit" value="提交"/> </form>
ref 属性能够是一个回调函数而不是名字,这个回调会在组件 mounted 后被调用。回调函数使用被引用的组件做为参数。
<input ref={ function(component){ React.findDOMNode(component).focus();} } />
注意不要在 render 方法中访问 refs 属性。