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>