React最初来自Facebook内部的广告系统项目,项目实施过程中前端开发遇到了巨大挑战,代码变得越来越臃肿且混乱不堪,难以维护。他们又对市场的现有的前端mvc框架都不满意。于是他们决定抛开很多所谓的“最佳实践”,重新思考前端界面的构建方式,就决定自己写一套框架来解决这些问题,然后就有了React。
后来发现React框架在开发中很好用,于是FB投入了更多的人力去开发这套框架,最后在2013年5月宣布开源。
由于 React 的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能引领未来用户界面开发的主流框架。
React 这么火,那么它到底有什么牛逼的地方?
特点及优势
1.虚拟dom (开发时候不需要在页面中写任何dom元素)
2.jsx语法(写页面时候使用javascript xml格式的语法)
3.组件化开发(react最核心的思想是将页面中任何一个区域或者元素都看成是一个组件 Component)
4.单向数据流(组件和后端之间的数据是单向的,从后端流动到react组件中)
5.组件生命周期(任何一个组件在dom中都具有一个完整的声明周期,组件初始化的时候开始,组件被移除的时候消失,从而保证性能的优越)
虚拟DOM
虚拟DOM则是在DOM的基础上建立了一个抽象层,我们对数据和状态所做的任何改动,都会被自动且高效的同步到虚拟DOM,最后再批量同步到DOM中。
虚拟DOM会使得App只关心数据和组件的执行结果,中间产生的DOM操作不需要App干预,而且通过虚拟DOM来生成DOM,会有一项非常可观收益——-性能。
React会在内存中维护一个虚拟DOM树,当我们对这个树进行读或写的时候,实际上是对虚拟DOM进行的。当数据变化时,然后React会自动更新虚拟DOM,然后拿新的虚拟DOM和旧的虚拟DOM进行对比,找到有变更的部分,得出一个Patch,然后将这个Patch放到一个队列里,最终批量更新这些Patch到DOM中。
缺陷——首次渲染大量DOM时因为多了一层虚拟DOM的计算,会比innerHTML插入方式慢,所以使用时尽量不要一次性渲染大量DOM。
JSX语法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 核心 -->
<script src="build/react.min.js"></script>
<!-- 渲染dom -->
<script src="build/react-dom.min.js"></script>
<!-- 把jsx、es6转换成js、es5 -->
<script src="build/browser.min.js"></script>
</head>
<body>
<div id="root"></div>
<!-- bebel是工具,把es6转换成es5 -->
<script type="text/babel">
ReactDOM.render(<h1>React</h1>,document.getElementById("root"))
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 核心 -->
<script src="build/react.min.js"></script>
<!-- 渲染dom -->
<script src="build/react-dom.min.js"></script>
<!-- 把jsx、es6转换成js、es5 -->
<script src="build/browser.min.js"></script>
</head>
<body>
<div id="root"></div>
<!-- bebel是工具,把es6转换成es5 -->
<script type="text/babel">
var arr = ["张三","李四","王五"];
ReactDOM.render(<div>{
arr.map(function(ele,index){
return <p>{ele}</p>
})
}</div>,document.getElementById("root"))
</script>
</body>
</html>
JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析。
组件
模块化中的模块一般指的是为了实现某些功能的代码片段,模块化主要实现了代码分工,分模块完成,强调的是功能;
组件是组成页面的部件,内部封装了组件的结构、样式、行为,更多的考虑代码的复用性,页面结构的实现
在组件化开发中,一个模块可能需要多个组件(比如评价的模块,可能需要列表的组件、按钮的组件、用户信息组件…),但是有些模块也可以跟组件没有关系,只是实现一些功能。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 核心 -->
<script src="build/react.min.js"></script>
<!-- 渲染dom -->
<script src="build/react-dom.min.js"></script>
<!-- 把jsx、es6转换成js、es5 -->
<script src="build/browser.min.js"></script>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
.header{
position: fixed;left: 0;top: 0;height: 44px;border-bottom: 1px solid #ccc; text-align: center;width: 100%;line-height: 44px;
}
.footer{
position: fixed;left: 0;bottom: 0;height: 44px;border-top: 1px solid #ccc; width: 100%;line-height: 44px;
list-style: none;
}
.footer li{
float: left;width: 25%;text-align: center;
}
.content{
padding-top: 45px;padding-bottom: 45px;
}
</style>
</head>
<body>
<div id="root"></div>
<!-- bebel是工具,把es6转换成es5 -->
<!-- 组件都是用面向对象构建的,组件经常会被复用,用面向对象写可以做到共用,组件名称要大写 -->
<!-- 不能用class,class在js中是保留字符 -->
<script type="text/babel">
var Header = React.createClass({
render:function(){
return <div className="header">网站头部</div>
}
})
var Footer = React.createClass({
render:function(){
return <ul className="footer">
<li>首页</li>
<li>列表</li>
<li>购物车</li>
<li>我的</li>
</ul>
}
})
var Content = React.createClass({
render:function(){
return <div className="content">内容</div>
}
})
var IndexPage = React.createClass({
render:function(){
return <div className="page">
<Header/>
<Footer/>
<Content/>
</div>
}
})
ReactDOM.render(<IndexPage/>,document.getElementById("root"))
</script>
<!-- 组件里面render的内容,就是当组件被调用显示的内容 -->
</body>
</html>
单项数据流
props(properties 特性)是在调用时候被调用者设置的,只设置一次,一般没有额外变化,可以把任意类型的数据传递给组件,尽可能的吧props当做数据源,不要在组件内部设置props。
组件传参:
1.this.props.children
2.this.props.xxx
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 核心 -->
<script src="build/react.min.js"></script>
<!-- 渲染dom -->
<script src="build/react-dom.min.js"></script>
<!-- 把jsx、es6转换成js、es5 -->
<script src="build/browser.min.js"></script>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
.header{
position: fixed;left: 0;top: 0;height: 44px;border-bottom: 1px solid #ccc; text-align: center;width: 100%;line-height: 44px;
}
.footer{
position: fixed;left: 0;bottom: 0;height: 44px;border-top: 1px solid #ccc; width: 100%;line-height: 44px;
list-style: none;
}
.footer li{
float: left;width: 25%;text-align: center;
}
.content{
padding-top: 45px;padding-bottom: 45px;
}
</style>
</head>
<body>
<div id="root"></div>
<!-- bebel是工具,把es6转换成es5 -->
<!-- 组件都是用面向对象构建的,组件经常会被复用,用面向对象写可以做到共用,组件名称要大写 -->
<!-- 不能用class,class在js中是保留字符 -->
<script type="text/babel">
var Header = React.createClass({
render:function(){
return <div className="header">{this.props.title}</div>
}
})
var Footer = React.createClass({
render:function(){
return <ul className="footer">
<li>首页</li>
<li>列表</li>
<li>购物车</li>
<li>我的</li>
</ul>
}
})
var Content = React.createClass({
render:function(){
return <div className="content">{this.props.children}</div>
}
})
var List = React.createClass({
render:function(){
return <ul>
<li>商品1</li>
<li>商品2</li>
<li>商品3</li>
</ul>
}
})
var IndexPage = React.createClass({
render:function(){
return <div className="page">
<Header title="首页" />
<Footer/>
<Content>
<List/>
</Content>
</div>
}
})
ReactDOM.render(<IndexPage/>,document.getElementById("root"))
</script>
<!-- 组件里面render的内容,就是当组件被调用显示的内容 -->
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 核心 -->
<script src="build/react.min.js"></script>
<!-- 渲染dom -->
<script src="build/react-dom.min.js"></script>
<!-- 把jsx、es6转换成js、es5 -->
<script src="build/browser.min.js"></script>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
.header{
position: fixed;left: 0;top: 0;height: 44px;border-bottom: 1px solid #ccc; text-align: center;width: 100%;line-height: 44px;
}
.footer{
position: fixed;left: 0;bottom: 0;height: 44px;border-top: 1px solid #ccc; width: 100%;line-height: 44px;
list-style: none;
}
.footer li{
float: left;width: 25%;text-align: center;
}
.content{
padding-top: 45px;padding-bottom: 45px;
}
</style>
</head>
<body>
<div id="root"></div>
<!-- bebel是工具,把es6转换成es5 -->
<!-- 组件都是用面向对象构建的,组件经常会被复用,用面向对象写可以做到共用,组件名称要大写 -->
<!-- 不能用class,class在js中是保留字符 -->
<script type="text/babel">
var Header = React.createClass({
render:function(){
return <div className="header">{this.props.title}</div>
}
})
var Footer = React.createClass({
render:function(){
return <ul className="footer">
<li>首页</li>
<li>列表</li>
<li>购物车</li>
<li>我的</li>
</ul>
}
})
var Content = React.createClass({
render:function(){
return <div className="content">{this.props.children}</div>
}
})
var List = React.createClass({
render:function(){
return <ul>
{this.props.listData.map((ele,index)=>{
return <li>商品{ele}</li>
})}
</ul>
}
})
var IndexPage = React.createClass({
render:function(){
return <div className="page">
<Header title="首页" />
<Footer/>
<Content>
<List listData={["1","2","3"]}/>
</Content>
</div>
}
})
ReactDOM.render(<IndexPage/>,document.getElementById("root"))
</script>
<!-- 组件里面render的内容,就是当组件被调用显示的内容 -->
</body>
</html>
组件的生命周期
随着该组件的props(数据)或者state(状态)发生改变,它的DOM表现也将有相应的变化,一个组件就是一个状态机:对于特定的输入,它总会返回一致的输出。 React为每个组件提供了生命周期钩子函数去响应不同的时刻,组件的生命周期分为三个部分:(1)实例化;(2)存在期;(3)销毁&清理期。
钩子函数类似有回调函数(callback),但他是在方法内容一开始调用的,而callback是在事件结束调用的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 核心 -->
<script src="build/react.min.js"></script>
<!-- 渲染dom -->
<script src="build/react-dom.min.js"></script>
<!-- 把jsx、es6转换成js、es5 -->
<script src="build/browser.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/babel">
//getInitialState给组件设置默认的状态
//setState是React方法来修改state,视图可以自动更新
var Switch = React.createClass({
getInitialState:function(){
return{
show:false
}
console.log("设置初始状态")
},
changeShow:function(){
this.setState({
show:!this.state.show
})
},
componentWillMount:function(){
console.log("即将添加")
},
render:function(){
return (
<div id="warp" ref="warp">
<button onClick={this.changeShow} ref="btn">切换</button>
<div style={{display:this.state.show?'block':'none'}}>虚拟DOM,组件化开发,生命周期,jsx语法,单项数据流</div>
</div>
)
},
componentDidMount:function(){
//获取真实dom,请求数据
//this.refs或取到的是设置了ref属性的dom集合
console.log("添加完成")
console.log(this.refs)
}
})
ReactDOM.render(<Switch/>,document.getElementById("root"))
</script>
</html>
实例化阶段(在组件调用之前的——创建阶段)
getDefaultProps方法发生在创建组件类的时候,即调用React.createClass的时候。这个阶段只会触发一个getDefaultProps方法,给this.props作为该组件的默认属性。
props属性是一个对象,是组件用来接收外面传来的参数的组件内部是不允许修改自己的props属性的,只能通过父组件来修改。在getDefaultProps方法中,是可以设定props默认值的。
getInitialState 初始化组件的state的值,其返回值会赋值给组件的this.state属性。对于组件的每个实例来说,这个方法的调用次数有且只有一次。
componentWillMount 此方法会在完成首次渲染之前被调用。这也是在render方法调用前可以修改组件state的最后一次机会。
render 生成页面需要的虚拟DOM结构,用来表示组件的输出。render方法需要满足:(1)只能通过this.props和this.state访问数据;(2)可以返回null、false或者任何React组件;(3)只能出现一个顶级组件;(4)必需纯净,意味着不能改变组件的状态或者修改DOM的输出。
componentDidMount 该方法发生在render方法成功调用并且真实的DOM已经被渲染之后,在该函数内部可以通过this.getDOMNode()来获取当前组件的节点。然后就可以像Web开发中的那样操作里面的DOM元素了。
存在期
用户改如果变了组件的state,或者要展示的数据发生改变,这时候需要重新渲染组件。
componentWillReceiveProps 在任意时刻,组件的props都可以通过父辈组件来更改。当组件接收到新的props(这里不同于state)时,会触发该函数,我们同时也获得更改props对象及更新state的机会。
shouldComponentUpdate 该方法用来拦截新的props和state,然后开发者可以根据自己设定逻辑,做出要不要更新render的决定,让它更快(当props 发生改变,或者调用了setState方法,调用了setState 方法哪怕state没有改变也会触发)
componentWillUpdate 与componentWillMount方法类似,组件上会接收到新的props或者state渲染之前,调用该方法。但是不可以在该方法中更新state和props。
render 生成页面需要的虚拟DOM结构,并返回该结构。
componentDidUpdate 与componentDidMount类似,更新已经渲染好的DOM。
demo
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 核心 -->
<script src="build/react.min.js"></script>
<!-- 渲染dom -->
<script src="build/react-dom.min.js"></script>
<!-- 把jsx、es6转换成js、es5 -->
<script src="build/browser.min.js"></script>
</head>
<body>
<div id="root"></div>
</body>
<script type="text/babel">
var ProductList = React.createClass({
getDefaultProps:function () {
//设置默认数据
return {
listData:[]
}
},
fnClick:function (index) {
alert(index)
},
render:function () {
return (
<ul>
{
this.props.listData.map(function (ele,index) {
return <li key={index} onClick={()=>this.fnClick(index)}>{ele}</li>
}.bind(this))
}
</ul>
)
}
});
var IndexPage = React.createClass({
render:function () {
return (
<div>
<header>头部</header>
<ProductList listData={[1,2,3,4]}/>
<footer>底部</footer>
</div>
)
}
});
ReactDOM.render(<IndexPage/>,document.getElementById("root"));
</script>
</html>