【本文源址:/q1056843325/article/details/54729657 转载请加入该地址】
明天就是除夕了
预祝大家新春快乐 [ ]~( ̄▽ ̄)~*
天天饭局搞得我是身心疲惫= =
所以更新比較慢
今天想跟大家分享的就是这个大名鼎鼎的React框架
简单介绍
React是近两年非常流行的框架
流行到什么程度呢?
我看了一下Github上的数据
React达到了5w8+的star
在JavaScript中star排名第4
受欢迎程度可见一斑
感兴趣的同学。给大家设置一个传送门:Github-JavaScript-most stars
React并不难。还是挺easy上手的
起源于Facebook内部项目(一个广告系统)
传统页面从server获取数据。显示到浏览器上,用户输入数据传入server
但随着数据量增大,越来越难以维护了
Facebook觉得MVC不能满足他们的扩展需求了(巨大的代码库和庞大的组织)
每当须要加入一项新功能或特性时,系统复杂度就呈几何增长
致使代码脆弱不堪、不可预測,结果导致他们的MVC正走向崩溃
当系统中有非常多的模型和相应视图时,其复杂度就会迅速扩大。非常难以理解和调试
总之就是Facebook对市场上全部JS-MVC框架都不惬意。觉得都不适合大规模应用
就自己写了一套,用来架设Instagram站点
写完后用着用着,发现哎呦这货还真是不错。然后就开源了
随着这几年的沉淀。React已经变得越来越强大了
值得我们去了解~
MVC
科普一下MVC
MVC是一种软件架构模式(后端影响到了前端)
MVC就分为M、V、C三部分
Model(模型):
应用程序中用于处理应用程序数据逻辑的部分,通常负责在数据库中存取数据View(视图):
应用程序中处理数据显示的部分,通常根据模型数据创建Controller(控制器):
应用程序中处理用户交互的部分。通常负责从视图读取数据,控制用户输入,并向模型发送数据
简单的理解一下
我们就是user。在页面中点击了一个按钮触发了事件
控制器Controller调整数据,模型Model中数据改变
数据改变又会导致视图View更新
UI的改变反馈呈现给我们user
这里我要特别说明一下,尽管这里介绍了MVC
可是不能说React就是MVC框架
能够说React是用于构建组件化UI的库。是一个前端界面开发工具
它能够作为MVC中的View视图部分
框架特点
React它具有以下特点
高性能
传统web页面操作DOM涉及重绘重排相当耗性能
React 提供了一种不同而又强大的方式来更新Dom(轻量级虚拟Dom——Virtual Dom)。取代直接操作DOM
更新virtual dom时不一定立即影响真实Dom。React会等到事件循环结束
利用Diff算法。通过当前新Dom表述与之前做比較,计算出最小步骤来更新真实Dom组件化
Dom树上的节点称为元素,而虚拟Dom 的节点称作组件(可复用性)
(组件的特点以下还会谈到)可预測性
state属性包括定义组件所须要的一些数据,当数据发生变化时。将会调用render重现渲染
React 把组件看成是一个状态机(State Machines)
通过与用户的交互。实现不同状态,然后渲染 UI。让用户界面和数据保持一致单向数据流
数据从父节点传递到子节点。仅仅须要从父节点获取props渲染就可以
(往下看就理解了)
环境搭建
我选择使用webpack搭建好开发环境。当然其它工具也能够
这是我的webpack.config.js配置文件
module.exports = {entry: {index: './src/js/entry.js'},output: {path: './static/dist/',publicPath: 'http://localhost:8080/static/dist/',filename: '[name].js'},module: {loaders: [{test: /\.js$/,loader: 'babel',query: {presets: ['react', 'es']}},{test: /.less$/,loader: 'style!css!less'}]}}
这里的关键就是除了要安装babel-loader
和babel-core
还要安装babel-preset-es
和babel-preset-react
用于解析ES6语法和React的JSX语法
还有react
和react-dom
也是必须要下载的依赖
全部依赖模块在这里
"devDependencies": {"babel-core": "^6.22.1","babel-loader": "^6.2.10","babel-preset-es": "^6.22.0","babel-preset-react": "^6.22.0","css-loader": "^0.26.1","less": "^2.7.2","less-loader": "^2.2.3","react": "^15.4.2","react-dom": "^15.4.2","style-loader": "^0.13.1","webpack": "^1.14.0","webpack-dev-server": "^1.16.2"}
JSX
简单说一下React的JSX语法是个神马东西
可能一会儿大家会看大不明觉厉的代码
比方
return (<div>hehe<div>)
这就是JSX代码,它是React提供的语法糖
是React的重要组成部分,使用相似XML标记的方式来声明界面及关系
语法糖的意思我写ES6的时候也说了
就是计算机语言中加入的语法。对语言的功能没影响
方便我们开发者使用的,能够增强可读性
假设使用JS代码也能够,只是官方推荐使用JSX
这样结构层次关系都非常清晰
webpack会帮我们把他们转换成浏览器认识的js代码(loader的作用)
(假设好奇转换成了什么,能够去webpack输出文件查看,或者找jsx转js的工具)
JSX语法结构说的通俗一点就是HTML、JS混写
可能大家会有疑惑,说好的结构、样式、行为相分离的前端思想呢?!
React当中一个基本的设计理念是编写简单且easy理解的代码
但我们为了实现组件化确实不方便松耦合
大家也不要过分较真
这种语法结构是如何解析的呢?事实上并不奇妙
JSX的语法规则:
遇到 HTML 标签(以 < 开头),就使用 HTML 规则解析遇到代码块(以 { 开头),就使用 JavaScript 规则解析代码块中假设仅仅有一个数组变量,就展开这个数组的全部成员
不理解不要慌。看了以下就懂了
提前渗透一下
渲染到页面
最终写到语法正题了
在此之前我们必须要引用的两个对象
一个React核心对象和一个React-Dom对象
(这里就先不使用ES6的语法了)
var React = require('react');var ReactDom = require('react-dom');
ReactDom.render()是react最最基本的方法
所以我放到最開始来讲
它通过ReactDom将我们的组件渲染到页面
我在页面中加入一个节点
<div id="root"></div>
如今页面中什么也没有
只是立即就有了
ReactDom.render(<h1>Demo</h1>,document.getElementById('demo'));
第一个參数是要插入的组件(只是这里我们先插入一个DOM节点)
第二个參数就是要渲染的DOM节点
(React不建议直接加入到body标签document.body,不相信的话能够试一下会警告)
页面中出现了“Demo”
实际上react将我们的节点插入到了div节点的内部
组件化
React的一大特点就是组件化
React组件Component有以下特点
组合:简单组件可组合为复杂组件重用:组件独立,可被多个组件使用測试:组件独立,便于測试维护:UI和组件相关逻辑都封装在组件内部,便于维护
组件生成
React.createClass()就是用于将代码封装成组件的方法
它会生成一个React组件
var App = React.createClass({render: function(){return (<p>This is a component...</p>)}});
这种方法參数是一个对象
对象中有一个render返回一个组件
render是输出组件必须要写的(关于它以下还会再说)
先记住两点
组件名首字母一定大写。否则会报错输出的组件仅仅能有一个顶级标签。其它标签会失效
所以。各位,以下的写法都是不正确的
//错误的写法:变量名首字母没大写var app = React.createClass({render: function(){return <p>This is a component...</p>}})
//错误的写法:存在多个顶级标签var App = React.createClass({render: function(){return (<p>This is a component...</p><p>This is also a component...</p>)}});
组件中html语法两边加括号的目的
是为了
防止JavaScript自己主动分号机制产生问题
组件渲染
生成的组件要想渲染到页面
就使用我们刚刚提到的的ReactDom.render( )
ReactDom.render(<App></App>,document.getElementById('root'));
组件要写成标签的形式
这里我们就要写<App></App>
或者单标签形式<App/>
也能够
组件特性
为了加以区分。我把html标签的属性叫组件特性
var App = React.createClass({render: function(){return <p name="demo">This is a component...</p>;}});ReactDom.render(<App></App>,document.getElementById('root'));
还要注意两个特例
class要写成classNamefor要写成htmlFor
由于他们是JavaScript的保留字
假设想要为组件加入内联样式,能够这样写
var App = React.createClass({render: function(){var styles = {color: '#fff',backgroundColor: '#000'}return <p className="demo" style={styles}>This is a component...</p>; // <--}});ReactDom.render(<App></App>,document.getElementById('root'));
声明了一个styles对象
可是将它加入到属性时。要使用 { }
由于JSX语法中。html中使用js就必须使用大括号
关于这一点以下就不再赘述了
组件属性
this.props
组件的属性相同能够像html一样加入<App name="payen"></App>
而且这个组件属性内部能够通过this.props对象获取
var App = React.createClass({render: function(){return <p>name:{this.props.name} age:{this.props.age}</p>;}});ReactDom.render(<App name="payen" age="20"></App>,document.getElementById('root'));
了解了这个,我们能够做一个小练习
如今有一组数据,利用它组成一个有序列表组件
var data = ['Mr.A','Mr.B','Mr.C'];
能够将这个数组成为组件属性
然后利用this.props.data获取数据
最后使用ES5数组的map方法就大功告成了
var List = React.createClass({render: function(){return (<ol>{this.props.data.map(function(item, index){return <li key={1000 + index}>{item}</li>;})}</ol>) }});ReactDom.render(<List data={data}></List>,document.getElementById('root'));
还要注意<li key={1000 + index}>{item}</li>
key值假设不写的话,尽管能够正常渲染
但会警告我们数组或迭代器的每一项都应该有一个独一无二的key值
这里我就使用了1000加上索引的形式加入了key值
this.props.children
通常组件的属性与this.props对象中的属性是一一相应的
但有一个例外,它是this.props.children
它表示我们组件的全部子节点
什么意思呢?接着我们上面的样例
我们在List组件中加入一些子节点
改动ReactDom.render( )方法的參数
ReactDom.render(<List data={data}><span>Mr.D</span><span>Mr.E</span></List>,document.getElementById('root'));
我们发现页面中并没有什么变化。但浏览器也没有报错
这时我们须要使用this.props.children
var data = ['Mr.A','Mr.B','Mr.C'];var List = React.createClass({render: function(){console.log(this.props.children);return (<ol>{this.props.data.map(function(item, index){return <li key={1000 + index}>{item}</li>;})}{this.props.children}</ol>) }});ReactDom.render(<List data={data}><span>Mr.D</span><span>Mr.E</span></List>,document.getElementById('root'));
如此页面中就显示出了子节点
这个this.props.children非常奇怪,它有三种类型值
没有子节点,值为undefined有一个子节点。值为object对象有多个子节点,值为array数组
(能够在控制台上输出验证)
所以我们处理它要特别小心
好在我们能够使用React给我们提供的方法
利用React.Children.map( )我们就能够放心遍历处理子节点
var data = ['Mr.A','Mr.B','Mr.C'];var List = React.createClass({render: function(){return (<ol>{this.props.data.map(function(item, index){return <li key={1000 + index}>{item}</li>;})}{React.Children.map(this.props.children,function(child){return <li>{child}</li>})}</ol>) }});ReactDom.render(<List data={data}><span>Mr.D</span><span>Mr.E</span></List>,document.getElementById('root'));
组件验证
组件的属性能够接受不论什么值。数字、字符串、函数、对象什么都能够
但有时候,我们拿到一个组件,想要验证參数是否符合我们的要求(这事实上非常重要。不要藐视)
这时就须要使用组件的propTypes( )方法和React.PropTypes配合验证了
var data = ['Mr.A','Mr.B','Mr.C'];var App = React.createClass({propTypes: {data: React.PropTypes.array},render: function(){return (<div>{this.props.data}</div>)}});ReactDom.render(<App data={data}></App>,document.getElementById('root'));
这里我期望的data属性值为array数组类型
没有不论什么问题,由于我们传入的就是数组
可是假设改成期望字符串类型data: React.PropTypes.string
浏览器就会发出警告
具体见React中文官网:Prop 验证
组件嵌套
还记得React单向数据流的特点么
也就是说我们应该把数据传递给父节点
父节点通过this.prop将数据传递给子节点
子节点再通过自己的this.prop处理收到的数据
var data = ['Mr.A','Mr.B','Mr.C'];var List = React.createClass({render: function(){return (<ol>{this.props.data.map(function(item, index){return <li key={1000 + index}>{item}</li>; })}</ol>) }});var App = React.createClass({render: function(){return (<div><List data={this.props.data}></List></div>)}});ReactDom.render(<App data={data}></App>,document.getElementById('root'));
所呈现的DOM结构
生命周期
生命周期不难理解
组件的一生无非就是产生、更新、销毁
在组件的每个生命周期内,都会按顺序触发一些组件方法
比方我们刚刚的render方法就会在产生和更新的阶段都会触发
具体触发的回调函数API以及作用给大家整理在以下
(关于它们在整个生命周期的触发次数大家应该都能想明确就不写了)
(不经常使用的我在后面的标注了*号)
组件实例化Mouting【组件生成时触发】
getDefaultProps( )
作用于组件类,返回对象用于设置默认的this.props(引用值会在实例中共享)e,g.return {name: 'payen'}
相当于初始化了组件属性this.props.name = 'payen'
getInitialState( )
作用于组件的实例,返回对象作为this.state的初始值e,g.return {show: false}
相当于初始化了组件状态this.state.show = false
componentWillMount( )
首次渲染前调用。可做一些业务初始化操作。也能够通过this.setState()设置组件状态e,g.this.setState({show: false})
render( )
必选方法,用于创建虚拟DOM,有特殊规则(再啰嗦一遍)
仅仅能通过this.props和this.state訪问数据能够返回null、false或不论什么React组件仅仅能出现一个顶级组件(不能返回数组)不能改变组件的状态不能改动DOM的输出componentDidMount( )(server端不会调用)
真实DOM被渲染后调用,可通过this.getDOMNode()訪问到真实的DOM元素
此时可使用其它类库来操作该DOM组件存在期Updateing【组件更新时触发】(state,props变化触发)
componentWillReceiveProps( )*
组件接收新props时调用,并将其作为參数nextProps使用,此时能够更改组件props及stateshouldComponentUpdate( )*
组件是否应当渲染新props或state
返回false表示跳过兴许生命周期方法(通常不须要使用以避免出现bug)
在出现应用瓶颈时,可通过该方法进行适当的优化。
在首次渲染期间或者调用了forceUpdate方法后,该方法不会被调用componentWillUpdate( )
接收到新props或state后,进行渲染前调用,此时不同意更新props或staterender( )
不再赘述componentDidUpdate( )*
完毕渲染新的props或state后调用。此时能够訪问到新的DOM元素组件销毁期Unmounting【组件销毁时触发】
componentWillUnmount()*
组件移除前调用,可用于做一些清理工作
在componentDidMount中加入的全部任务都须要在该方法中撤销(e.g.定时器、事件监听器等等)
附上一张我盗的图,帮助大家理解(手动滑稽)
关于这些API更具体的信息
建议大家能够去React中文官网查看:Component Specs and Lifecycle
组件状态
上面提到了this.state,和我们之前介绍的this.props一样重要
只是this.props通常不会变。但this.state会变
就如其字面意思,表示组件的状态
这个属性是仅仅读的
所以设置状态我们须要使用this.setState( )
可使用this.setState( )的方法:
componentWillMount、componentDidMount、componentWillReceiveProps
组件交互
在此之前我们须要了解的就是React的事件系统
JavaScript原始行间绑定事件都是普遍小写<button onclick="clickHandle()"></button>
但我们在React中要使用驼峰写法<button onClick="clickHandle()"></button>
React的事件处理器会传入虚拟事件对象的实例(一个对浏览器本地事件的跨浏览器封装)
它有和浏览器本地事件相同的属性和方法。包括 stopPropagation() 和 preventDefault(),
可是没有浏览器兼容问题
具体支持事件见中文官网:事件系统-支持的事件
如今我们要来实现这样一个简单的功能
点击按钮,出现弹框
单击弹框。弹框消失
先来实现结构与样式
var App = React.createClass({render: function(){return (<div><button>点击</button><PopUp></PopUp></div>)}});var PopUp = React.createClass({render: function(){var styles = {position: 'absolute',left: '40px',top: '40px',width: '100px',height: '100px',backgroundColor: '#f40'}return (<div className="popup" style={styles}></div>)}})ReactDom.render(<App/>,document.getElementById('root'));
首先我们先来实现第一个功能:点击按钮出现弹框
问题是改如何实现
我们的React是单向数据流
父级向子级传递数据
最好的办法就是在父级设置组件状态this.state
将状态通过组件属性this.props传递给子级
这样点击事件要做的就是改变父级状态
子级状态也会随之改变
var App = React.createClass({getInitialState: function(){return {open: false}},buttonHandler: function(){this.setState({open: true});},render: function(){return (<div><button onClick={this.buttonHandler}>点击</button><PopUp open={this.state.open}></PopUp></div>)}});var PopUp = React.createClass({render: function(){var styles = {position: 'absolute',left: '40px',top: '40px',width: '100px',height: '100px',backgroundColor: '#f40'}if(this.props.open){styles.display = 'block';}else{styles.display = 'none';}return (<div className="popup" style={styles}></div>)}})ReactDom.render(<App/>,document.getElementById('root'));
第一个功能实现了,再来看第二个
点击弹窗让其消失
相同子级的显示与否掌控在父级手里
要向让子级消失,就必须要改变父级的组件状态this.state
所以我们必须要把事件函数绑定在父级
再利用组件属性this.props将其传递给子级
完整代码例如以下
var App = React.createClass({getInitialState: function(){return {open: false}},buttonHandler: function(){this.setState({open: true});},popupHandler: function(){this.setState({open: false});},render: function(){return (<div><button onClick={this.buttonHandler}>点击</button><PopUp open={this.state.open} handler={this.popupHandler}></PopUp></div>)}});var PopUp = React.createClass({render: function(){var styles = {position: 'absolute',left: '40px',top: '40px',width: '100px',height: '100px',backgroundColor: '#f40'}if(this.props.open){styles.display = 'block';}else{styles.display = 'none';}return (<div className="popup" style={styles} onClick={this.props.handler}></div>)}})ReactDom.render(<App/>,document.getElementById('root'));
用一句话来总结一下,那就是数据都交给父级来管理
获取真实DOM节点
我们已经知道了
创建的组件都是虚拟DOM节点
仅仅有当它渲染到了页面。才会成为真正的DOM节点
可是有些时候,我们须要获取到真正的DOM节点
这时须要先设置标签ref属性,再利用组件的this.refs对象获取
还是通过一个小样例来解释
如今要实现这样一个功能
在输入栏中输入字符并在外部实时输出
我们要获取的真实DOM节点就是input中的输入字符串
步骤也非常easy,完整代码例如以下
var Input = React.createClass({getInitialState: function(){return {val: ''}},changeHandler: function(){this.setState({val: this.refs.node.value});},render: function(){return (<div><input type="text" ref="node" onChange={this.changeHandler}/><p>{this.state.val}</p></div>)}});ReactDom.render(<Input/>,document.getElementById('root'));
我为input标签设置了ref值为node
能够把它理解为为这个节点起了个小名“node”
那么this.refs.node
就能够引用这个真实的节点<input/>
通过绑定一个change事件
我们的输入每次改变都会改变组件的状态state
state改变。value就会渲染到页面
获取真实DOM节点另一个不经常使用的方法
比方在我们的样例中能够把input标签改成这样
<input type="text" ref={function(dom){this._node = dom}.bind(this)} onChange={this.changeHandler}/>
向ref属性中加入一个匿名函数
这个函数的參数就是真实DOM节点
我们能够把它保存下来,比方做为组件的_node属性
不要忘了改变this的指向
事件触发函数就能够通过this._node获取真正的DOM节点
changeHandler: function(){this.setState({val: this._node.value});}
还要注意的一点是
这个真·DOM节点的获取
必须要等到虚拟DOM插入文档以后。才干使用属性this.refs.[ref-name]
否则会报错的
==主页传送门==