在上一篇文章:React.js的基礎知識及一些demo(一)中牺弄,我們介紹了React.js的元素板惑、JSX語法邓夕、組件和屬性等相關基礎語法及一些簡單demo掰茶。這篇文章我們繼續(xù)往下了解React的語法萤厅。
狀態(tài)和生命周期
在上一篇文章更新已渲染的元素一節(jié)中橄抹,有一個時鐘的例子。
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
在時鐘例子中祈坠,我們通過調(diào) ReactDOM.render() 方法來更新渲染的輸出害碾,這是一種更新UI的方式。
接下來我們將時鐘功能封裝成一個組件
function Clock(props) {
return (
<div>
<h1>Hello,world!</h1>
<h2>It is {props.date.toLocaleTimeString()}</h2>
</div>
);
}
function tick() {
ReactDOM.render(
<Clock date={new Date()}/>,
document.getElementById('root')
);
}
setInterval(tick,1000);
然而赦拘,它沒有滿足一個關鍵的要求:Clock 設置定時器并每秒更新 UI 慌随,事實上應該是 Clock 自身實現(xiàn)的一部分。
理想情況下躺同,我們應該只引用一個 Clock , 然后讓它自動計時并更新:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
要實現(xiàn)這點阁猜,我們需要添加 state 到 Clock 組件。state 和 props 類似蹋艺,但是它是私有的剃袍,并且由組件本身完全控制。
在上一篇文章中提到捎谨,組件有兩種定義方式:類組件和函數(shù)組件民效。用類定義的組件有一些額外的特性憔维。 這個”類專有的特性”, 指的就是局部狀態(tài)畏邢。
如何將函數(shù)式組件轉換為類組件
在上一小節(jié)中业扒,我們定義的Clock組件屬于函數(shù)式組件,我們以Clock為例舒萎,介紹函數(shù)組件轉換為類組件程储。
- 創(chuàng)建一個繼承自
React.Component
類的 ES6 class 同名類。 - 添加一個名為
render()
的空方法臂寝。 - 把原函數(shù)中的所有內(nèi)容移至
render()
中章鲤。 - 在
render()
方法中使用this.props
替代props
。 - 刪除保留的空函數(shù)聲明咆贬。
class Clock extends React.Component{
render(){
return (
<div>
<h1>Hello,world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}</h2>
</div>
);
}
}
Clock 現(xiàn)在被定為類組件败徊,而不是函數(shù)式組件。類允許我們在其中添加本地狀態(tài)(state)和生命周期鉤子掏缎。
在類組件中添加本地狀態(tài)(state)
我們現(xiàn)在通過以下3步, 把date從屬性(props) 改為 狀態(tài)(state):
1.替換 render() 方法中的 this.props.date 為 this.state.date集嵌;
2.添加一個 類構造函數(shù)(class constructor) 初始化 this.state
;
3.移除 <Clock /> 元素中的 date 屬性御毅;
結果如下所示:
class Clock extends React.Component{
constructor(props){
super(props);//調(diào)用父類的constructor(props)
this.state = {date:new Date()};
}
render(){
return (
<div>
<h1>Hello,world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
這里:super關鍵字代表父類的實例(即父類的this對象)根欧。
注意我們?nèi)绾螌?props 傳遞給基礎構造函數(shù),類組件應始終使用 props 調(diào)用基礎構造函數(shù)端蛆。
接下來凤粗,我們將使 Clock 設置自己的計時器,并每秒更新一次今豆。
在類中添加生命周期方法
在一個具有許多組件的應用程序中嫌拣,在組件被銷毀時釋放所占用的資源是非常重要的。
當 Clock
第一次渲染到DOM時呆躲,我們要設置一個定時器 异逐。 這在 React 中稱為 “掛載(mounting)” 。
當 Clock
產(chǎn)生的 DOM 被銷毀時插掂,我們也想清除該計時器灰瞻。 這在 React 中稱為 “卸載(unmounting)” 。
當組件掛載和卸載時辅甥,我們可以在組件類上聲明特殊的方法來運行一些代碼酝润,這些方法稱為 “生命周期鉤子”。
1.componentDidMount() 鉤子在組件輸出被渲染到 DOM 之后運行璃弄。這是設置時鐘的合適位置要销;
- 注意我們把計時器ID直接存在 this 中。
- this.props 由 React 本身設定, 而 this.state 具有特殊的含義夏块,但如果需要存儲一些不用于視覺輸出的內(nèi)容疏咐,則可以手動向類中添加額外的字段纤掸。
- 如果在 render() 方法中沒有被引用, 它不應該出現(xiàn)在 state 中。
2.我們在componentWillUnmount()生命周期鉤子中取消這個計時器浑塞;
componentDidMount(){
this.timerID = setInterval(() => this.tick(),1000);
}
componentWillUnmount(){
clearInterval(this.timerID);
}
最后茁肠,我們將會實現(xiàn)每秒運行的 tick() 方法。它將使用 this.setState() 來周期性地更新組件本地狀態(tài)缩举。
最后完整代碼如下所示:
class Clock extends React.Component{
constructor(props){
super(props);
this.state = {date:new Date()};
}
componentDidMount(){
this.timerID = setInterval(() => this.tick(),1000);
}
componentWillUnmount(){
clearInterval(this.timerID);
}
tick(){
this.setState({
date:new Date()
});
}
render(){
return (
<div>
<h1>Hello,world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
關于這個例子的總結
我們來快速回顧一下該過程,以及調(diào)用方法的順序:
1.當 <Clock /> 被傳入 ReactDOM.render() 時, React 會調(diào)用 Clock組件的構造函數(shù)匹颤。 因為 Clock 要顯示的是當前時間仅孩,所以它將使用包含當前時間的對象來初始化 this.state 。我們稍后會更新此狀態(tài)印蓖。
2.然后 React 調(diào)用了 Clock 組件的 render() 方法辽慕。 React 從該方法返回內(nèi)容中得到要顯示在屏幕上的內(nèi)容。然后赦肃,React 更新 DOM 以匹配 Clock 的渲染輸出溅蛉。
3.當 Clock 輸出被插入到 DOM 中時,React 調(diào)用 componentDidMount() 生命周期鉤子他宛。在該方法中船侧,Clock 組件請求瀏覽器設置一個定時器來一次調(diào)用 tick()。
4.瀏覽器會每隔一秒調(diào)用一次 tick()方法厅各。在該方法中镜撩, Clock 組件通過 setState() 方法并傳遞一個包含當前時間的對象來安排一個 UI 的更新。通過 setState(), React 得知了組件 state(狀態(tài))的變化, 隨即再次調(diào)用 render() 方法队塘,獲取了當前應該顯示的內(nèi)容袁梗。 這次,render() 方法中的 this.state.date 的值已經(jīng)發(fā)生了改變憔古, 從而遮怜,其輸出的內(nèi)容也隨之改變锯梁。React 于是據(jù)此對 DOM 進行更新。
5.如果通過其他操作將 Clock 組件從 DOM 中移除了, React 會調(diào)用 componentWillUnmount() 生命周期鉤子, 所以計時器也會被停止。
使用 State(狀態(tài))的一些注意點
關于 setState() 有三件事是你應該知道的壁却。
1.不要直接修改 state(狀態(tài))
例如,這樣將不會重新渲染一個組件:
// 錯誤
this.state.comment = 'Hello';
應該使用 setState() 代替:
// 正確
this.setState({comment: 'Hello'});
唯一可以分配 this.state 的地方是構造函數(shù)盐肃。
2.state(狀態(tài)) 更新可能是異步的
React 為了優(yōu)化性能砸王,有可能會將多個 setState() 調(diào)用合并為一次更新榔昔。
因為 this.props 和 this.state 可能是異步更新的嘹朗,你不能依賴他們的值計算下一個state(狀態(tài))。
例如, 以下代碼可能導致 counter(計數(shù)器)更新失敱拱:
// 錯誤
this.setState({
counter: this.state.counter + this.props.increment,
});
要解決這個問題许蓖,應該使用另一種 setState() 的形式自阱,它接受一個函數(shù)而不是一個對象。這個函數(shù)將接收前一個狀態(tài)作為第一個參數(shù)叫确,應用更新時的 props 作為第二個參數(shù):
// 正確
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
3.state(狀態(tài))更新會被合并
當你調(diào)用 setState(), React 將合并你提供的對象到當前的狀態(tài)中。
例如,你的狀態(tài)可能包含幾個獨立的變量:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
然后通過調(diào)用獨立的 setState() 調(diào)用分別更新它們:
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
注意:合并是淺合并主卫,所以 this.setState({comments}) 不會改變 this.state.posts 的值,但會完全替換this.state.comments 的值。
數(shù)據(jù)向下流動
無論作為父組件還是子組件,它都無法獲悉一個組件是否有狀態(tài)备典,同時也不需要關心另一個組件是定義為函數(shù)組件還是類組件荤崇。
這就是 state(狀態(tài)) 經(jīng)常被稱為 本地狀態(tài) 或 封裝狀態(tài)的原因。 它不能被擁有并設置它的組件 以外的任何組件訪問雳攘。
一個組件可以選擇將 state(狀態(tài)) 向下傳遞喧兄,作為其子組件的 props(屬性):
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
<FormattedDate date={this.state.date} />
FormattedDate 組件通過 props(屬性) 接收了 date 的值恭理,但它仍然不能獲知該值是來自于 Clock的 state(狀態(tài)) 拯辙,還是 Clock 的 props(屬性),或者是直接手動創(chuàng)建的:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
這通常稱為一個“從上到下”颜价,或者“單向”的數(shù)據(jù)流涯保。任何 state(狀態(tài)) 始終由某個特定組件所有,并且從該 state(狀態(tài)) 導出的任何數(shù)據(jù) 或 UI 只能影響樹中 “下方” 的組件周伦。
如果把組件樹想像為 props(屬性) 的瀑布夕春,所有組件的 state(狀態(tài)) 就如同一個額外的水源匯入主流,且只能隨著主流的方向向下流動专挪。
要證明所有組件都是完全獨立的及志, 我們可以創(chuàng)建一個 App 組件,并在其中渲染 3 個 <Clocks>:
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
每個 Clock 都設置它自己的計時器并獨立更新寨腔。
在 React 應用中速侈,一個組件是否是有狀態(tài)或者無狀態(tài)的,被認為是組件的一個實現(xiàn)細節(jié)迫卢,隨著時間推移可能發(fā)生改變锌畸。你可以在有狀態(tài)的組件中使用無狀態(tài)組件,反之亦然靖避。
參考: