作者:小米墨客
原文地址:http://my.oschina.net/feiyangxiaomi/blog/644418
React是一個(gè)JavaScript庫(kù)文件,使用它的目的在于能夠解決構(gòu)建大的應(yīng)用和數(shù)據(jù)的實(shí)時(shí)變更稿存。該設(shè)計(jì)使用JSX允許你在構(gòu)建標(biāo)簽結(jié)構(gòu)時(shí)充分利用JavaScript的強(qiáng)大能力笨篷,而不必在笨拙的模板語(yǔ)言上浪費(fèi)時(shí)間。
1 生命周期
在組件的整個(gè)生命周期中瓣履,隨著該組件的props或者state發(fā)生改變率翅,它的DOM表現(xiàn)也將有相應(yīng)的變化,一個(gè)組件就是一個(gè)狀態(tài)機(jī):對(duì)于特定的輸入袖迎,它總會(huì)返回一致的輸出冕臭。 React為每個(gè)組件提供了生命周期鉤子函數(shù)去響應(yīng)不同的時(shí)刻腺晾,組件的生命周期分為三個(gè)部分:(1)實(shí)例化;(2)存在期辜贵;(3)銷毀&清理期丘喻。
具體周期如下圖所示:1.1 實(shí)例化
創(chuàng)建在代碼加載過(guò)程中至關(guān)重要,重要之處體現(xiàn)什么地方呢念颈,這里粗略的簡(jiǎn)述幾點(diǎn):
- 實(shí)例化是首次加載js展示給用戶最直觀的內(nèi)容泉粉,效率的高低直接決定體驗(yàn)的好壞;
- 實(shí)例化過(guò)程是對(duì)數(shù)據(jù)進(jìn)行說(shuō)明和描述的過(guò)程榴芳。
- 實(shí)例化過(guò)程完成了虛擬DOM和真實(shí)DOM的生成嗡靡。
下面看下示例來(lái)展示當(dāng)前流程:
var React = require("react");
var ReactDOM = require("react-dom");
var List = React.createClass({
//1.創(chuàng)建階段
getDefaultProps:function() {
console.log("getDefaultProps");
return {};
},
//2.實(shí)例化階段
getInitialState:function() {
console.log("getInitialState");
return {};
},
//render之前調(diào)用,業(yè)務(wù)邏輯都應(yīng)該放在這里窟感,如對(duì)state的操作等
componentWillMount:function() {
console.log("componentWillMount");
},
//渲染并返回一個(gè)虛擬DOM
render:function() {
console.log("render");
return(
<div> hello <strong> {this.props.name} </strong></div>
);
},
//該方法發(fā)生在render方法之后讨彼。在該方法中,ReactJS會(huì)使用render生成返回的虛擬DOM對(duì)象來(lái)創(chuàng)建真實(shí)的DOM結(jié)構(gòu)
componentDidMount:function() {
console.log("componentDidMount");
},
});
ReactDOM.render(<List name="ReactJS">children</List>, document.body);
輸出結(jié)果為:
getDefaultProps
getInitialState
componentWillMount
render
componentDidMount
上面經(jīng)歷的實(shí)例化過(guò)程可細(xì)分成兩個(gè)階段:創(chuàng)建階段和實(shí)例化階段柿祈。
1.1.1創(chuàng)建階段
該階段主要發(fā)生在創(chuàng)建組件類的時(shí)候哈误,即調(diào)用React.createClass
的時(shí)候。這個(gè)階段只會(huì)觸發(fā)一個(gè)getDefaultProps
方法躏嚎,該方法返回一個(gè)對(duì)象蜜自,并且緩存下來(lái)。然后與父組件指定的props
對(duì)象合并卢佣,最后賦值給this.props
作為該組件的默認(rèn)屬性重荠。對(duì)于那些沒(méi)有被父輩組件指定的props
屬性的新建實(shí)例來(lái)說(shuō),這個(gè)方法返回的對(duì)象可用于為實(shí)例設(shè)置默認(rèn)的props
值虚茶。
props
屬性又是什么呢戈鲁,它是一個(gè)對(duì)象,是組件用來(lái)接收外面?zhèn)鱽?lái)的參數(shù)的嘹叫,組件內(nèi)部是不允許修改自己的props
屬性的婆殿,只能通過(guò)父組件來(lái)修改。在getDefaultProps
方法中罩扇,是可以設(shè)定props
默認(rèn)值的婆芦。
1.1.2實(shí)例化階段
該階段主要發(fā)生在實(shí)例化組件類的時(shí)候,也就是該組件類被調(diào)用的時(shí)候:
ReactDOM.render(<NewView name="ReactJS">children</NewView>, document.body);
調(diào)用順序在demo結(jié)果中頁(yè):
-
getInitialState 初始化組件的
state
的值暮蹂,其返回值會(huì)賦值給組件的this.state
屬性寞缝。對(duì)于組件的每個(gè)實(shí)例來(lái)說(shuō),這個(gè)方法的調(diào)用次數(shù)有且只有一次仰泻。與getDefaultProps
方法不同的是,每次實(shí)例創(chuàng)建時(shí)該方法都會(huì)被調(diào)用一次滩届。 -
componentWillMount 此方法會(huì)在完成首次渲染之前被調(diào)用集侯。這也是在
render
方法調(diào)用前可以修改組件state的最后一次機(jī)會(huì)被啼。 -
render 生成頁(yè)面需要的虛擬DOM結(jié)構(gòu),用來(lái)表示組件的輸出棠枉。
render
方法需要滿足:
(1)只能通過(guò)this.props
和this.state
訪問(wèn)數(shù)據(jù)浓体;
(2)可以返回null
、false
或者任何React組件辈讶;
(3)只能出現(xiàn)一個(gè)頂級(jí)組件命浴;
(4)必需純凈,意味著不能改變組件的狀態(tài)或者修改DOM的輸出贱除。 -
componentDidMount 該方法發(fā)生在
render
方法成功調(diào)用并且真實(shí)的DOM已經(jīng)被渲染之后生闲,在該函數(shù)內(nèi)部可以通過(guò)this.getDOMNode()
來(lái)獲取當(dāng)前組件的節(jié)點(diǎn)。然后就可以像Web開(kāi)發(fā)中的那樣操作里面的DOM元素了月幌。
上面提到了兩個(gè)比較生分的術(shù)語(yǔ)——state和虛擬DOM:
-
state:是組建的屬性碍讯,主要用來(lái)存儲(chǔ)組件自身需要的數(shù)據(jù)。它是可以改變的扯躺,它的每次改變都會(huì)引起組件的更新捉兴,這也是ReactJS中的關(guān)鍵點(diǎn)之一。每次數(shù)據(jù)的更新都是通過(guò)修改
state
屬性的值录语,然后ReactJS內(nèi)部會(huì)監(jiān)聽(tīng)state
屬性的變化倍啥,一旦發(fā)生變化,就會(huì)主動(dòng)出發(fā)組件的render
方法來(lái)更新DOM結(jié)構(gòu)澎埠。 - 虛擬DOM:它是ReactJS中提出的一個(gè)概念逗栽,是將真實(shí)的DOM結(jié)構(gòu)映射成一個(gè)JSON數(shù)據(jù)結(jié)構(gòu),在有數(shù)據(jù)更改的時(shí)候更新真實(shí)的DOM失暂,不需修改的時(shí)候不更新真實(shí)的DOM彼宠。
1.2 存在期
由1.1可知,此時(shí)組件已經(jīng)渲染好并且用戶可以與它進(jìn)行交互弟塞,通常是通過(guò)一次鼠標(biāo)點(diǎn)擊凭峡、手指點(diǎn)按或者鍵盤(pán)事件來(lái)觸發(fā)一個(gè)事件處理器。隨著用戶改變了組件或者整個(gè)應(yīng)用的state决记,便會(huì)有新的state流入組件結(jié)構(gòu)樹(shù)摧冀。代碼如下所示:
var React = require("react");
var ReactDOM = require("react-dom");
var NewView = React.createClass({
//1.創(chuàng)建階段
getDefaultProps:function() {
console.log("getDefaultProps");
return {};
},
//2.實(shí)例化階段
getInitialState:function() {
console.log("getInitialState");
return {
num:1
};
},
//render之前調(diào)用,業(yè)務(wù)邏輯都應(yīng)該放在這里系宫,如對(duì)state的操作等
componentWillMount:function() {
console.log("componentWillMount");
},
//渲染并返回一個(gè)虛擬DOM
render:function() {
console.log("render");
return(
<div> hello <strong> {this.props.name} </strong>
<button onClick={this.handleAddNumber}> hello <strong> {this.state.num} </strong></button>
</div>
);
},
//該方法發(fā)生在render方法之后索昂。在該方法中,ReactJS會(huì)使用render生成返回的虛擬DOM對(duì)象來(lái)創(chuàng)建真實(shí)的DOM結(jié)構(gòu)
componentDidMount:function() {
console.log("componentDidMount");
},
//3.更新階段
componentWillReceiveProps:function() {
console.log("componentWillReceiveProps");
},
//是否需要更新
shouldComponentUpdate:function() {
console.log("shouldComponentUpdate");
return true;
},
//將要更新
componentWillUpdate:function() {
console.log("componentWillUpdate");
},
//更新完畢
componentDidUpdate:function() {
console.log("componentDidUpdate");
},
//4.銷毀階段
componentWillUnmount:function() {
console.log("componentWillUnmount");
},
// 處理點(diǎn)擊事件
handleAddNumber:function() {
console.log("add num");
this.setState({num:this.state.num+1});
this.setProps({name:"newName"});
}
});
ReactDOM.render(<NewView name="ReactJS"></NewView>, document.body);
該段代碼的目的是扩借,經(jīng)歷第一階段實(shí)例化階段椒惨,然后提供button
按鈕,改變state
狀態(tài)潮罪,也是調(diào)用代碼中的handleAddNumber:function()
方法康谆,實(shí)現(xiàn)第二階段存在期的更新领斥,該階段在state
狀態(tài)f
發(fā)生改變,組件逐漸受到影響沃暗。
輸出結(jié)果為(不包含):
add num
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
render
componentDidUpdate
- componentWillReceiveProps 在任意時(shí)刻月洛,組件的props都可以通過(guò)父輩組件來(lái)更改。當(dāng)組件接收到新的props(這里不同于state)時(shí)孽锥,會(huì)觸發(fā)該函數(shù)嚼黔,我們同時(shí)也獲得更改props對(duì)象及更新state的機(jī)會(huì)。
- shouldComponentUpdate 該方法用來(lái)攔截新的props和state惜辑,然后開(kāi)發(fā)者可以根據(jù)自己設(shè)定邏輯唬涧,做出要不要更新render的決定,讓它更快韵丑。
-
componentWillUpdate 與
componentWillMount
方法類似爵卒,組件上會(huì)接收到新的props或者state渲染之前,調(diào)用該方法撵彻。但是不可以在該方法中更新state和props钓株。 - render 生成頁(yè)面需要的虛擬DOM結(jié)構(gòu),并返回該結(jié)構(gòu)陌僵。
-
componentDidUpdate 與
componentDidMount
類似轴合,更新已經(jīng)渲染好的DOM。
1.3 生命周期之銷毀&清理
每當(dāng)react使用完一個(gè)組件碗短,這個(gè)組件就必須從DOM中卸載隨后被銷毀受葛。此時(shí),僅有的一個(gè)鉤子函數(shù)會(huì)做出響應(yīng)偎谁,完成所有的清理與銷毀工作总滩,這很必要。
componentWillUnmount
最后巡雨,隨著一個(gè)組件從它的層級(jí)結(jié)構(gòu)中移除闰渔,這個(gè)組件的生命也就走到了盡頭。該方法會(huì)在組件被移出之前調(diào)被調(diào)用铐望。在componentDidMount
方法中添加的所有任務(wù)都需要在該方法中撤銷冈涧,比如說(shuō)創(chuàng)建的定時(shí)器或者添加的事件監(jiān)聽(tīng)等。
1.4 反模式:把計(jì)算后的值賦給state
在getInitialState
方法中正蛙,嘗試通過(guò)this.props
來(lái)創(chuàng)建state
的做法是一種反模式督弓。
getDefaultProps:function() {
console.log("getDefaultProps");
return {
date:new Date()
};
},
getInitialState:function() {
console.log("getInitialState");
return {
num:this.props.date.getDay()
};
},
在getInitialState
中使用props
的值,同時(shí)可能存在props
的值沒(méi)有初始化完的狀態(tài)乒验。導(dǎo)致計(jì)算后的值永遠(yuǎn)不會(huì)與派生出他的props
值同步愚隧。
getDefaultProps:function() {
console.log("getDefaultProps");
return {
date:new Date()
};
},
//渲染并返回一個(gè)虛擬DOM
render:function() {
console.log("render");
var day = this.props.date.getMonth;
return(
<div> hello <strong> Day:{day}</strong>
</div>
);
}
2 數(shù)據(jù)流與事件操作
在React中,數(shù)據(jù)流向是單向的——從父節(jié)點(diǎn)傳遞到子節(jié)點(diǎn)徊件,因而組件是簡(jiǎn)單且易于把握的奸攻,他們只需從父節(jié)點(diǎn)獲取props渲染即可蒜危。如果頂層組件的某個(gè)prop改變了虱痕,React會(huì)遞歸地向下遍歷整棵組建樹(shù)睹耐,重新渲染所有使用這個(gè)屬性的組件。 React組件內(nèi)部還具有自己的狀態(tài)部翘,這些狀態(tài)只能在組件內(nèi)修改硝训。React組件本身很簡(jiǎn)單,你可以把他們看成是一個(gè)函數(shù)新思,他接受props和state作為參數(shù)窖梁,返回一個(gè)虛擬的DOM表現(xiàn)。
2.1 Props
props是properties的縮寫(xiě)夹囚,你可以使用它把任意類型的數(shù)據(jù)傳遞給組件纵刘。我們先創(chuàng)建一個(gè)父組件Parent
,它內(nèi)部調(diào)用的是一個(gè)叫child
的子組件荸哟,并將接收到的外部參數(shù)name
傳遞給子組件child
假哎,代碼如下所示:
var React = require("react");
var ReactDOM = require("react-dom");
var Child = React.createClass({
getDefaultProps:function() {
return {};
},
getInitialState:function() {
return {};
},
//渲染并返回一個(gè)虛擬DOM
render:function() {
return(
<button> hello {this.props.name}</button>
);
}
});
var Parent = React.createClass({
render:function() {
return(
<div onClick={this.onClick}>
<Child name={this.props.name} id="child"> </Child>
</div>
);
},
onClick:function() {
console.log(ReactDOM.findDOMNode(document.body));
}
});
ReactDOM.render(<Parent name="123"></Parent>, document.body);
我們?cè)?code>Parent上設(shè)置name="123"
,這個(gè)name
經(jīng)由Parent
傳遞給Child
的props
中鞍历,非常的方便舵抹。父組件可以傳遞任何值給到子組件。
2.2 PropTypes
通過(guò)在組件中定義一個(gè)配置對(duì)象劣砍,React提供了一種驗(yàn)證props的方式:
var Child = React.createClass({
propTypes: {
viewName:React.propTypes.shape({
id:React.propTypes.number.isRequired
}).isRequired,
onClick:React.propTypes.func
},
......
組件初始化時(shí)惧蛹,如果傳遞的屬性和propTypes
不匹配,則會(huì)打印一個(gè)console.warn
日志刑枝,如果是可選的配置香嗓,可以去掉.required
。 在應(yīng)用使用中装畅,propTypes
并不是強(qiáng)制性的靠娱,但這提供了一種極好的方式來(lái)描述組件的API。
2.3 State
每一個(gè)React組件都可以擁有自己的state洁灵,state與props的區(qū)別在于前者只存在與組件的內(nèi)部饱岸。并不是組件之間所共享的。state可以用來(lái)確定一個(gè)元素的視圖狀態(tài)徽千。
var Parent = React.createClass({
getInitialState:function() {
return {
number:1
};
},
render:function() {
return(
<div onClick={this.onClick}>
<button > hello {this.state.number} </button>
</div>
);
},
onClick:function() {
this.setState({
number:this.state.number+1
});
}
});
ReactDOM.render(<Parent></Parent>, document.body);
如上代碼苫费,可以通過(guò)點(diǎn)擊事件對(duì)state
進(jìn)行修改,調(diào)用setState
的時(shí)候双抽,會(huì)調(diào)用存在期的周期百框。也可以使用上面出現(xiàn)的getInitialState
方法提供一組默認(rèn)值,只要setState
被調(diào)用牍汹,render
就會(huì)被調(diào)用铐维。如果render
函數(shù)返回有變化柬泽,虛擬DOM就會(huì)更新,真實(shí)的DOM也會(huì)被更新嫁蛇,最終用戶會(huì)在瀏覽器中看到變化锨并。
注意:不要直接修改
this.state
,永遠(yuǎn)記得要通過(guò)this.setState
方法修改睬棚。
3 事件
對(duì)于用戶而言第煮,展示只占整體設(shè)計(jì)因素的一半。另一半則是響應(yīng)用戶輸入抑党,即通過(guò)JavaScript處理用戶產(chǎn)生的事件包警。 React通過(guò)將時(shí)間處理器綁定到組件上來(lái)處理事件。在事件被觸發(fā)的同時(shí)底靠,更新組件的內(nèi)部狀態(tài)害晦。組件內(nèi)部狀態(tài)的更新會(huì)觸發(fā)組件重繪。因此暑中,如果視圖層想要渲染出時(shí)間觸發(fā)后的結(jié)果壹瘟,它所需要做的就是在渲染函數(shù)中讀取組件的內(nèi)部狀態(tài)。
3.1 綁定事件處理器
React處理的事件本質(zhì)上和原生的JavaScript時(shí)間一樣:MouseEvents事件用于點(diǎn)擊處理器痒芝,Change事件用于表單元素變化俐筋,等等。所有的事件在命名上和原生的JavaScript規(guī)范一致且會(huì)在相同的場(chǎng)景下被觸發(fā)严衬。 React綁定事件處理器的語(yǔ)法和HTML語(yǔ)法類似澄者。比如一個(gè)按鈕,功能是添加请琳,寫(xiě)法如下:
<button className="btn-add" onClick={this.handleAddClicked}>Add</button>
用戶點(diǎn)擊這個(gè)按鈕時(shí)粱挡,組件的handleAddClicked
方法會(huì)被調(diào)用。這個(gè)方法中會(huì)包含處理Add的行為邏輯俄精。
這里我們可以懷疑onClick
哪里來(lái)的呢询筏,處理onClick
還支持什么事件,這里里出了MouseEvents事件:
onClick onContextMenu onDoubleClick onDrag onDragEnd onDragEnter onDragExit
onDragLeave onDragOver onDragStart onDrop onMouseDown onMouseEnter onMouseLeave
onMouseMove onMouseOut onMouseOver onMouseUp
參考: https://facebook.github.io/react/docs/events.html
3.2 事件對(duì)象
很多事件處理器只要觸發(fā)就會(huì)完成竖慧,但是有時(shí)候也會(huì)需要關(guān)于用戶輸入的跟多信息嫌套,然后有時(shí)候我們需要在輸入的時(shí)候一直在監(jiān)聽(tīng)輸入的內(nèi)容,及時(shí)的提醒給用戶輸入錯(cuò)誤的提示圾旨,提高產(chǎn)品的體驗(yàn)踱讨。
var React = require('react');
var ReactDOM = require("react-dom");
var InputListener = React.createClass({
handleInput:function(event) {
console.log(event.target.value);
},
render:function() {
return(
<div className="form-group">
<div className="input-item">
<textarea rows="3" onChange={this.handleInput}/>
</div>
</div>
);
}
});
ReactDOM.render(<InputListener className="input">input</InputListener>, document.body);
通常會(huì)有一個(gè)事件對(duì)象傳入到React的時(shí)間處理器函數(shù)中,類似原生的JavaScript事件監(jiān)聽(tīng)器的寫(xiě)法砍的。這里的handleInput
方法會(huì)接受一個(gè)事件的對(duì)象痹筛,并通過(guò)存取event.target.value
互相傳遞事件對(duì)象的內(nèi)容。在事件處理器中,使用event.target.value
獲取表單中的input
值是一中常規(guī)的方法帚稠。
編譯腳本參考:**第5.3小節(jié):打包程序 **