在React這股目前最熱前端框架之風(fēng)刮來(lái)之前怨绣,一直在Cocos2d-html5游戲和半路出家的Android應(yīng)用的糾結(jié)杜窄,在項(xiàng)目一開(kāi)始階段栽渴,為了圖省事(O(∩_∩)O~主要還是自我的懶惰)互妓,喜歡把各種場(chǎng)景和界面雜糅一起芥备,對(duì)UI封裝和組件的顆炼ⅲ化做的一塌糊涂。幸運(yùn)在迷茫時(shí)候遇到了React萌壳,它的虛擬DOM(Virtual DOM)亦镶、組件化的開(kāi)發(fā)思路、以及在跨移動(dòng)端解決方案(ReactNative)這些深深的吸引了我袱瓮,下面來(lái)跟我一起領(lǐng)略ReactJS的風(fēng)采吧~~
<b>一缤骨、React簡(jiǎn)介</b>
React 起源于 Facebook 的內(nèi)部項(xiàng)目,因?yàn)樵摴緦?duì)市場(chǎng)上所有 JavaScript MVC 框架尺借,都不滿(mǎn)意绊起,就決定自己寫(xiě)一套,用來(lái)架設(shè) Instagram 的網(wǎng)站燎斩。做出來(lái)以后虱歪,發(fā)現(xiàn)這套東西很好用,就在2013年5月開(kāi)源了栅表。由于 React 的設(shè)計(jì)思想極其獨(dú)特笋鄙,屬于革命性創(chuàng)新,性能出眾怪瓶,代碼邏輯卻非常簡(jiǎn)單局装。所以,越來(lái)越多的人開(kāi)始關(guān)注和使用,認(rèn)為它可能是將來(lái) Web 開(kāi)發(fā)的主流工具铐尚。
ReactJS官網(wǎng)地址:http://facebook.github.io/react/
Github地址:https://github.com/facebook/react
<b>二、React的幾個(gè)誤區(qū)</b>
1哆姻、僅僅一個(gè)處理VIEW層的工具
React不是一個(gè)完整的MVC框架宣增,最多可以認(rèn)為是MVC中的V(View)層,甚至React并不非常認(rèn)可MVC開(kāi)發(fā)模式矛缨;
因此:沒(méi)有辦法單單依靠React來(lái)完成一個(gè)完整的動(dòng)態(tài)應(yīng)用
2爹脾、React不是一個(gè)新的模板語(yǔ)言
React不是一個(gè)新的模板語(yǔ)言,JSX只是一個(gè)表象箕昭,沒(méi)有JSX的React也能工作灵妨。
<b>三、對(duì)ReactJS的認(rèn)識(shí)及ReactJS的優(yōu)點(diǎn)</b>
<b>1落竹、ReactJS的背景和原理</b>
在Web開(kāi)發(fā)中泌霍,我們總需要將變化的數(shù)據(jù)實(shí)時(shí)反應(yīng)到UI上,這時(shí)就需要對(duì)DOM進(jìn)行操作述召。而復(fù)雜或頻繁的DOM操作通常是性能瓶頸產(chǎn)生的原因(如何進(jìn)行高性能的復(fù)雜DOM操作通常是衡量一個(gè)前端開(kāi)發(fā)人員技能的重要指標(biāo))朱转。React為此引入了虛擬DOM(Virtual DOM)的機(jī)制:在瀏覽器端用Javascript實(shí)現(xiàn)了一套DOM API』基于React進(jìn)行開(kāi)發(fā)時(shí)所有的DOM構(gòu)造都是通過(guò)虛擬DOM進(jìn)行藤为,每當(dāng)數(shù)據(jù)變化時(shí),React都會(huì)重新構(gòu)建整個(gè)DOM樹(shù)夺刑,然后React將當(dāng)前整個(gè)DOM樹(shù)和上一次的DOM樹(shù)進(jìn)行對(duì)比缅疟,得到DOM結(jié)構(gòu)的區(qū)別,然后僅僅將需要變化的部分進(jìn)行實(shí)際的瀏覽器DOM更新遍愿。而且React能夠批處理虛擬DOM的刷新存淫,在一個(gè)事件循環(huán)(Event Loop)內(nèi)的兩次數(shù)據(jù)變化會(huì)被合并,例如你連續(xù)的先將節(jié)點(diǎn)內(nèi)容從A變成B错览,然后又從B變成A纫雁,React會(huì)認(rèn)為UI不發(fā)生任何變化,而如果通過(guò)手動(dòng)控制倾哺,這種邏輯通常是極其復(fù)雜的轧邪。盡管每一次都需要構(gòu)造完整的虛擬DOM樹(shù),但是因?yàn)樘摂MDOM是內(nèi)存數(shù)據(jù)羞海,性能是極高的忌愚,而對(duì)實(shí)際DOM進(jìn)行操作的僅僅是Diff部分,因而能達(dá)到提高性能的目的却邓。這樣硕糊,在保證性能的同時(shí),開(kāi)發(fā)者將不再需要關(guān)注某個(gè)數(shù)據(jù)的變化如何更新到一個(gè)或多個(gè)具體的DOM元素,而只需要關(guān)心在任意一個(gè)數(shù)據(jù)狀態(tài)下简十,整個(gè)界面是如何Render的檬某。
? React拋棄HTML,引入了虛擬DOM的概念螟蝙,必要時(shí)候?qū)⑺麄冧秩镜秸嬲腄OM上
如果你像在90年代那樣寫(xiě)過(guò)服務(wù)器端Render的純Web頁(yè)面那么應(yīng)該知道恢恼,服務(wù)器端所要做的就是根據(jù)數(shù)據(jù)Render出HTML送到瀏覽器端。如果這時(shí)因?yàn)橛脩?hù)的一個(gè)點(diǎn)擊需要改變某個(gè)狀態(tài)文字胰默,那么也是通過(guò)刷新整個(gè)頁(yè)面來(lái)完成的场斑。服務(wù)器端并不需要知道是哪一小段HTML發(fā)生了變化,而只需要根據(jù)數(shù)據(jù)刷新整個(gè)頁(yè)面牵署。換句話(huà)說(shuō)漏隐,任何UI的變化都是通過(guò)整體刷新來(lái)完成的。而React將這種開(kāi)發(fā)模式以高性能的方式帶到了前端奴迅,每做一點(diǎn)界面的更新青责,你都可以認(rèn)為刷新了整個(gè)頁(yè)面。至于如何進(jìn)行局部更新以保證性能半沽,則是React框架要完成的事情爽柒。
? ** 在React中,應(yīng)用程序在虛擬DOM上操作者填,這讓React有了優(yōu)化的機(jī)會(huì)浩村。簡(jiǎn)單說(shuō),React在每次需要渲染時(shí)占哟,會(huì)先比較當(dāng)前DOM內(nèi)容和待渲染內(nèi)容的差異心墅,然后再?zèng)Q定如何最優(yōu)地更新DOM。 **
上圖分析:基于React進(jìn)行開(kāi)發(fā)時(shí)所有的DOM構(gòu)造都是通過(guò)虛擬DOM進(jìn)行榨乎,每當(dāng)數(shù)據(jù)變化時(shí)怎燥,React會(huì)產(chǎn)生以下情況:
1)、觸發(fā)相應(yīng)組件render方法
2)蜜暑、重新構(gòu)建新的虛擬DOM樹(shù)
3)铐姚、將當(dāng)前新的虛擬DOM樹(shù)和上一次的舊樹(shù)進(jìn)行對(duì)比
4)、得到DOM結(jié)構(gòu)的區(qū)別肛捍,計(jì)算出最小變化集隐绵,進(jìn)行實(shí)際的瀏覽器DOM更新(批量更新)。
除了性能的考慮外拙毫,React引入虛擬DOM更重要的意義是提供了一種一致的開(kāi)發(fā)方式來(lái)開(kāi)發(fā)服務(wù)端應(yīng)用依许、Web應(yīng)用和手機(jī)端應(yīng)用
因?yàn)橛辛颂摂MDOM這一層,所以通過(guò)配備不同的渲染器缀蹄,就可以將虛擬DOM的內(nèi)容 渲染到不同的平臺(tái)峭跳,而應(yīng)用開(kāi)發(fā)者膘婶,使用JavaScript就可以通吃各個(gè)平臺(tái)了。
2蛀醉、組件化
虛擬DOM(virtual-dom)不僅帶來(lái)了簡(jiǎn)單的UI開(kāi)發(fā)邏輯悬襟,同時(shí)也帶來(lái)了組件化開(kāi)發(fā)的思想,所謂組件拯刁,即封裝起來(lái)的具有獨(dú)立功能的UI部件古胆。就像生活中孩子喜歡用積木搭房子一樣,每一個(gè)積木就是React中的組件,積木與積木之間可以互相嵌套坞淮,最終拼在一起組成了房子狸演。
例1:
對(duì)于評(píng)論界面而言,整個(gè)UI是一個(gè)通過(guò)小組件構(gòu)成的大組件论熙,每個(gè)組件只關(guān)心自己部分的邏輯,彼此獨(dú)立。
例2:
對(duì)于文章界面來(lái)說(shuō)朗儒,整個(gè)UI包括了頭部Header、內(nèi)容區(qū)域Content以及底部的評(píng)論區(qū)域CommentList
四参淹、ReactJS正式起航
1醉锄、React.render
React.render 是React 的最基本方法,用于將模板轉(zhuǎn)為 HTML 語(yǔ)言浙值,并插入指定的 DOM 節(jié)點(diǎn)恳不。
React.render( <MessageBox/> , document.getElementById('app'), function(){
console.log("渲染完成!");
});
2开呐、創(chuàng)建組件React.createClass
React 允許將代碼封裝成組件(component)烟勋,然后像插入普通 HTML 標(biāo)簽一樣,在網(wǎng)頁(yè)中插入這個(gè)組件筐付。React.createClass 方法就用于生成一個(gè)組件類(lèi)
var MessageBox = React.createClass({
render:function (){
var submessage = [];
for(var i=0; i<10; i++){
submessage.push( <Submessage key={i }/> );
}
return (
<div>
<h1>Hello World!</h1>
{submessage}
</div>
)
}
});
var Submessage = React.createClass({
render: function (){
return (
<div>
<h3>寫(xiě)代碼很有意思</h3>
<Footer/>
</div>
)
}
});
var Footer = React.createClass({
render: function (){
return (
<small>因?yàn)槲覀冊(cè)儆么a創(chuàng)造</small>
)
}
});
React.render(<MessageBox/>, document.getElementById('app'), function (){
console.log("渲染完成卵惦!");
});
上面代碼中,變量MessageBox和Submessage就是一個(gè)組件類(lèi)瓦戚。模板插入時(shí)沮尿,會(huì)自動(dòng)生成MessageBox、Submessage的一個(gè)實(shí)例(下文的"組件"都指組件類(lèi)的實(shí)例)较解。
注意:
1)所有組件類(lèi)都必須有自己的render方法畜疾,用于輸出組件。
2)組件類(lèi)的第一個(gè)字母必須大寫(xiě)哨坪,否則會(huì)報(bào)錯(cuò)庸疾,比如MessageBox不能寫(xiě)成messageBox。
3)組件類(lèi)只能包含一個(gè)頂層標(biāo)簽当编,否則也會(huì)報(bào)錯(cuò)届慈。
組件的用法與原生的 HTML 標(biāo)簽完全一致徒溪,可以任意加入屬性,比如金顿,就是MessageBox組件加入一個(gè)name屬性臊泌,值為John。組件的屬性可以在組件類(lèi)的this.props對(duì)象上獲取揍拆,比如name屬性就可以通過(guò)this.props.name讀取渠概。
添加組件屬性,有一個(gè)地方需要注意嫂拴,就是class屬性需要寫(xiě)成className播揪,for屬性需要寫(xiě)成htmlFor,這是因?yàn)閏lass和for是 JavaScript 的保留字筒狠。
3猪狈、this.props
react中主要有下面三種方法來(lái)傳遞屬性
1)直接在組件中使用key/value的形式來(lái)指定屬性:
var title = "你好世界(來(lái)自頂層的props)";
var messageBox = React.render(<MessageBox title={title}/>, document.getElementById('app'), function (){
console.log("渲染完成!");
});
可以看到辩恼,在自定義的MessageBox組件中雇庙,我們指定了一個(gè)titile的屬性,在組件中灶伊,獲取屬性的方法如下代碼:
var MessageBox = React.createClass({
getInitialState: function(){
return {
isVisble: true,
subMessages:[
'我會(huì)代碼搬磚',
'以及花式搬磚',
'不說(shuō)了疆前,工頭叫我回去搬磚了,聘萨,竹椒,,'
]
}
},
render:function (){
return (
<div>
<h1>{this.props.title}</h1>
<Submessage messages={this.state.subMessages}/>
</div>
)
}
});
2)使用延展屬性為組件指定屬性
<script type="text/jsx">
var ShowInfo = React.createClass({
render: function() {
return (
<div>Hello, {this.props.name}, 年齡為:{this.props.age}</div>
);
}
});
var props = {
name: 'Jack',
age: '34'
};
React.render(
<ShowInfo {...props}/>,
document.getElementById('container')
);
</script>
如果一個(gè)在組件上需要傳遞的屬性比較多匈挖,例如Person對(duì)象的屬性:name碾牌、age、height儡循、weight等等屬性特征要一步步傳遞下去舶吗,實(shí)在是太麻煩,延展屬性這個(gè)ES6的特性就大放異彩了~~~~~
為了在ShowInfo組件中定義多個(gè)屬性择膝,我們首先定義了一個(gè)props對(duì)象誓琼,里面包含兩個(gè)鍵值對(duì),然后在標(biāo)簽中肴捉,用{...props}的方式為組件傳遞了這兩個(gè)屬性腹侣,這就是JSX中的延展屬性,"..."成為延展操作符齿穗,這種方式可以很方便地為組件指定多個(gè)屬性傲隶。
3)通過(guò)調(diào)用組件的setProps函數(shù)為組件指定屬性
React.render()函數(shù)執(zhí)行后,會(huì)返回代表組件的一個(gè)對(duì)象窃页,通過(guò)調(diào)用這個(gè)對(duì)象的setProps函數(shù)跺株,可以為其指定屬性复濒,如下代碼:
<script type="text/jsx">
var HelloWorld = React.createClass({
render: function() {
return (
<div>Hello, {this.props.name}</div>
);
}
});
var instance = React.render(
<HelloWorld />,
document.getElementById('container')
);
instance.setProps({name: 'Jack'});
</script>
react官方認(rèn)為,props應(yīng)該是只讀的乒省,不應(yīng)該被修改巧颈。因?yàn)?React 不能幫你檢查屬性類(lèi)型(propTypes)。這樣即使你的 屬性類(lèi)型有錯(cuò)誤也不能得到清晰的錯(cuò)誤提示袖扛。
Props 應(yīng)該被當(dāng)作禁止修改的砸泛。修改 props 對(duì)象可能會(huì)導(dǎo)致預(yù)料之外的結(jié)果,所以最好不要去修改 props 對(duì)象蛆封。
<b>this.props.children:</b>
this.props對(duì)象的屬性與組件的屬性一一對(duì)應(yīng)唇礁,但是有一個(gè)例外,就是this.props.children屬性惨篱。它表示組件的所有子節(jié)點(diǎn)
<script type="text/jsx">
var MessageBox = React.createClass({
getInitialState: function(){
return {
isVisble: true
}
},
render:function (){
return (
<div>
<h1>{this.props.title}</h1>
<Submessage>
<p>第一名</p>
<p>第二名</p>
<p>第三名</p>
<p>第四名</p>
</Submessage>
</div>
)
}
});
var Submessage = React.createClass({
getDefalutProps:function (){
return {
message: ['默認(rèn)的子消息']
}
},
render: function (){
var msgs = [];
this.props.children.forEach(function (node, index){
msgs.push(
node
);
});
return (
<div>{msgs}</div>
)
}
});
var title = "this.props.children";
var messageBox = React.render(<MessageBox title={title}/>, document.getElementById('app'), function (){
console.log("渲染完成垒迂!");
});
</script>
上面代碼的NoteList組件有兩個(gè)span子節(jié)點(diǎn),它們都可以通過(guò)this.props.children讀取妒蛇。
這里需要注意,this.props.children的值有三種可能:
1)如果當(dāng)前組件沒(méi)有子節(jié)點(diǎn)楷拳,它就是undefined;
2)如果有一個(gè)子節(jié)點(diǎn)绣夺,數(shù)據(jù)類(lèi)型是object;
3)如果有多個(gè)子節(jié)點(diǎn)欢揖,數(shù)據(jù)類(lèi)型就是array陶耍。
所以,處理this.props.children的時(shí)候要小心她混。
4烈钞、this.state
組件免不了要與用戶(hù)互動(dòng),React 的一大創(chuàng)新坤按,就是將組件看成是一個(gè)狀態(tài)機(jī)毯欣,一開(kāi)始有一個(gè)初始狀態(tài),然后用戶(hù)互動(dòng)臭脓,導(dǎo)致?tīng)顟B(tài)變化酗钞,從而觸發(fā)重新渲染 UI。
<script type="text/jsx">
var ClickApp = React.createClass({
getInitialState:function (){
return {
clickCount: 0
}
},
handleClick:function(){
this.setState({
clickCount: this.state.clickCount+1
})
},
render:function(){
return (
<div>
<h2>點(diǎn)擊下面按鈕</h2>
<button onClick={this.handleClick}>點(diǎn)擊我</button>
<p>你一共點(diǎn)擊了:{this.state.clickCount}</p>
</div>
)
}
})
React.render(
<ClickApp/>, document.getElementById('app')
);
</script>
上面代碼是一個(gè)ClickApp組件来累,它的getInitialState方法用于定義初始狀態(tài)砚作,也就是一個(gè)對(duì)象,這個(gè)對(duì)象可以通過(guò)this.state屬性讀取嘹锁。當(dāng)用戶(hù)點(diǎn)擊組件葫录,導(dǎo)致?tīng)顟B(tài)變化,this.setState方法就修改狀態(tài)值领猾,每次修改以后米同,自動(dòng)調(diào)用this.render方法骇扇,再次渲染組件。
由于this.props和this.state都用于描述組件的特性窍霞,可能會(huì)產(chǎn)生混淆匠题。一個(gè)簡(jiǎn)單的區(qū)分方法是,this.props表示那些一旦定義但金,就不再改變的特性韭山,而this.state是會(huì)隨著用戶(hù)互動(dòng)而產(chǎn)生變化的特性 。
5冷溃、組件的生命周期
簡(jiǎn)單地說(shuō)钱磅,React Component通過(guò)其定義的幾個(gè)函數(shù)來(lái)控制組件在生命周期的各個(gè)階段的動(dòng)作。
在ES6中似枕,一個(gè)React組件是用一個(gè)class來(lái)表示的(具體可以參考官方文檔)
// 定義一個(gè)TodoList的React組件盖淡,通過(guò)繼承React.Component來(lái)實(shí)現(xiàn)
class TodoList extends React.Component {
...
}
這幾個(gè)生命周期相關(guān)的函數(shù)有:
constructor(props, context) //構(gòu)造函數(shù),在創(chuàng)建組件的時(shí)候調(diào)用一次凿歼。
void componentWillMount() // 在組件掛載之前調(diào)用一次褪迟。
//如果在這個(gè)函數(shù)里面調(diào)用setState,本次的render函數(shù)可以看到更新后的state答憔,并且只渲染一次味赃。
void componentDidMount() // 在組件掛載之后調(diào)用一次。這個(gè)時(shí)候虐拓,子主鍵也都掛載好了心俗,可以在這里使用refs。
// props是父組件傳遞給子組件的蓉驹。父組件發(fā)生render的時(shí)候子組件就會(huì)調(diào)用componentWillReceiveProps
//(不管props有沒(méi)有更新城榛,也不管父子組件之間有沒(méi)有數(shù)據(jù)交換)
void componentWillReceiveProps(nextProps)
// 組件掛載之后,每次調(diào)用setState后都會(huì)調(diào)用shouldComponentUpdate判斷是否需要重新渲染組件态兴。
// 默認(rèn)返回true狠持,需要重新render。在比較復(fù)雜的應(yīng)用里瞻润,有一些數(shù)據(jù)的改變并不影響界面展示工坊,
// 可以在這里做判斷,優(yōu)化渲染效率敢订。
bool shouldComponentUpdate(nextProps, nextState)
// shouldComponentUpdate返回true或者調(diào)用forceUpdate之后componentWillUpdate會(huì)被調(diào)用王污。
void componentWillUpdate(nextProps, nextState)
// 除了首次render之后調(diào)用componentDidMount,其它render結(jié)束之后都是調(diào)用componentDidUpdate楚午。
void componentDidUpdate()
componentWillMount昭齐、componentDidMount和componentWillUpdate、componentDidUpdate可以對(duì)應(yīng)起來(lái)矾柜。區(qū)別在于阱驾,前者只有在掛載的時(shí)候會(huì)被調(diào)用就谜;而后者在以后的每次更新渲染之后都會(huì)被調(diào)用。
// render是一個(gè)React組件所必不可少的核心函數(shù)(上面的其它函數(shù)都不是必須的)里覆。記住丧荐,不要在render里面修改state。
ReactElement render()
// 組件被卸載的時(shí)候調(diào)用喧枷。一般在componentDidMount里面注冊(cè)的事件需要在這里刪除虹统。
void componentWillUnmount()
** 更新方式: **
在react中,觸發(fā)render的有4條路徑隧甚。
以下假設(shè)shouldComponentUpdate都是按照默認(rèn)返回true的方式车荔。
1)首次渲染Initial Render
2)調(diào)用this.setState (并不是一次setState會(huì)觸發(fā)一次render,React可能會(huì)合并操作戚扳,再一次性進(jìn)行render)
3)父組件發(fā)生更新(一般就是props發(fā)生改變忧便,但是就算props沒(méi)有改變或者父子組件之間沒(méi)有數(shù)據(jù)交換也會(huì)觸發(fā)render)
4)調(diào)用this.forceUpdate
下面是我對(duì)React組件四條更新路徑地總結(jié):
注意,如果在shouldComponentUpdate里面返回false可以提前退出更新路徑帽借。
一個(gè)React組件生命周期的測(cè)試?yán)?/strong>
var MessageBox = React.createClass({
getInitialState: function(){
console.log('getInitialState')
return {
count: 0
}
},
getDefaultProps:function(){
console.log('getDefaultProps')
},
componentWillMount:function(){
},
componentDidMount:function(){
},
componentWillUnmount:function(){
},
//更新時(shí)才執(zhí)行珠增,頁(yè)面加載時(shí)不會(huì)執(zhí)行
shouldComponentUpdate:function (nextProp, nextState){
console.log('shouldComponentUpdate');
if(nextState.count > 5){
return false;
}
return true; //必需返回 true|false
},
componentWillUpdate:function (nextProp, nextState){
console.log('componentWillUpdate');
},
componentDidUpdate: function(){
console.log('componentDidUpdate');
},
killSelf:function(){
React.unmountComponentAtNode(document.getElementById('app'));
},
doUpdate:function(){
this.setState({
count: this.state.count + 1
});
},
render:function (){
console.log('渲染。砍艾。切平。');
return (
<div>
<h1>計(jì)數(shù):{this.state.count}</h1>
<button onClick={this.killSelf}>卸載掉這個(gè)組件</button>
<br/><br/>
<button onClick={this.doUpdate}>手動(dòng)更新一下組件(包括子組件)</button>
<Submessage count={this.state.count}/>
</div>
)
}
});
var Submessage = React.createClass({
componentWillReceiveProps:function (nextProp){
console.log('子組件將要獲得prop');
},
shouldComponentUpdate:function (nextProp, nextState){
if(nextProp.count > 3){
return false;
}
return true;
},
render: function (){
return (
<h3>當(dāng)前計(jì)數(shù)是:{this.props.count}</h3>
)
}
});
var messageBox = React.render(<MessageBox/>, document.getElementById('app'), function (){
console.log("渲染完成!");
});
運(yùn)行后的效果為:
五辐董、ES6八大特性
更多關(guān)于ES6的特性,請(qǐng)看下一個(gè)ES6分享
六禀综、Webpack的配置
更多關(guān)于Webpack的配置詳情简烘,請(qǐng)看下一個(gè)Webpack配置詳解