?
This document uses PHP Chinese website manual Release
考慮上一節(jié)中的滴答時鐘示例。
到目前為止,我們只學習了一種更新 UI 的方法。
我們調用ReactDOM.render()
改變呈現的輸出:
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);
在本節(jié)中,我們將學習如何使Clock
組件真正可重用和封裝。它會設置自己的計時器并每秒更新一次。
我們可以從封裝時鐘的外觀開始:
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 的事實應該是該實現的實現細節(jié)Clock
。
理想情況下,我們想寫一次,并有Clock
更新本身:
ReactDOM.render( <Clock />, document.getElementById('root'));
為了實現這個,我們需要給組件添加“狀態(tài)” Clock
。
狀態(tài)類似于道具,但是它是私人的并且完全由組件控制。
我們之前提到,定義為類的組件具有一些附加功能。本地狀態(tài)就是這樣:一個僅適用于類的功能。
您可以通過Clock
五個步驟將功能組件轉換為類:
創(chuàng)建一個擴展名為 ES6 的類,名稱相同React.Component
。
為它添加一個空的方法render()
。
將函數的主體移到render()
方法中。
更換props
用this.props
的render()
身體。
刪除剩余的空函數declaration.class Clock extends React.Component {render(){return(<div> <h1> Hello,world!</ h1> <h2> {this.props.date.toLocaleTimeString()} 。</ h2> </ div>); }} 。Clock
現在被定義為一個類而不是一個函數。這讓我們可以使用額外的功能,例如本地狀態(tài)和生命周期鉤子。將本地狀態(tài)添加到類我們將通過date
三個步驟將從道具移動到狀態(tài):
在該方法中替換this.props.date
為:this.state.daterender()
class Clock extends React.Component { render() { return (<div><h1>Hello, world!</h1><h2>It is {this.state.date.toLocaleTimeString()}.</h2></div>); } }
1. 添加一個類的構造函數,指定初始值 this.state
:class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}Note how we pass props
to the base constructor: constructor(props) {
super(props);
this.state = {date: new Date()};
}Class components should always call the base constructor with props
.
Remove the date
prop from the <Clock />
element:
ReactDOM.render( <Clock />, document.getElementById('root'));
稍后我們將計時器代碼添加回組件本身。
結果如下所示:
class Clock extends React.Component { constructor(props) { super(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'));
接下來,我們將Clock
設置自己的計時器并每秒更新一次。
在具有多個組件的應用程序中,釋放組件在銷毀時所占用的資源非常重要。
我們想要在第一次呈現給DOM 時設置一個計時器Clock
。這在React中被稱為“掛載”。
我們也想清除該定時器,只要Clock
刪除由該DOM生成的DOM 。這在React中被稱為“卸載”。
我們可以在組件類上聲明特殊的方法來在組件裝載和卸載時運行一些代碼:
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { } componentWillUnmount() { } render() { return (<div><h1>Hello, world!</h1><h2>It is {this.state.date.toLocaleTimeString()}.</h2></div>); } }
這些方法被稱為“生命周期掛鉤”。
在將組件輸出呈現給 DOM 后,componentDidMount()
在將組件輸出呈現給 DOM 后,該鉤子運行。這是設置計時器的好地方:
componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ); }
注意我們如何保存定時器 ID this
。
雖然this.props
由 React 自己設置并this.state
具有特殊含義,但如果您需要存儲某些不用于視覺輸出的內容,則可以自由向該類手動添加其他字段。
如果你不使用某些東西render()
,它不應該處于這種狀態(tài)。
我們將拆除componentWillUnmount()
生命周期鉤子中的計時器:
componentWillUnmount() { clearInterval(this.timerID); }
最后,我們將實現一個調用的方法tick()
,Clock
組件將運行每一秒。
它將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'));
現在時鐘每秒鐘都在滴答。
讓我們快速回顧發(fā)生了什么以及調用方法的順序:
當<Clock />
傳遞給ReactDOM.render()
React 時,React 調用Clock
組件的構造函數。由于Clock
需要顯示當前時間,因此它會this.state
使用包含當前時間的對象進行初始化。我們稍后將更新這個狀態(tài)。
React 然后調用Clock
組件的render()
方法。這就是 React 如何學習屏幕上應顯示的內容。React 然后更新 DOM 以匹配Clock
渲染輸出。
當Clock
輸出插入到 DOM 中時,React調用componentDidMount()
生命周期鉤子。在它里面,Clock
組件要求瀏覽器設置一個計時器,tick()
每秒調用一次該組件的方法。
瀏覽器每秒調用一次該tick()
方法。在它內部,Clock
組件通過調用setState()
包含當前時間的對象來調度 UI 更新。感謝setState()
電話,React 知道狀態(tài)已經改變,并render()
再次調用方法來了解屏幕上應顯示的內容。這一次,this.state.date
該render()
方法將會不同,因此渲染輸出將包含更新的時間。React 會相應地更新 DOM。
如果Clock
組件從 DOM 中移除,React 將調用componentWillUnmount()
生命周期鉤子,以便定時器停止。
有三件事你應該知道setState()
。
例如,這不會重新渲染組件:
// Wrongthis.state.comment = 'Hello';
相反,使用setState()
:
// Correctthis.setState({comment: 'Hello'});
唯一可以分配的地方this.state
是構造函數。
React 可能會將多個setState()
調用分批到單個更新中進行性能調優(yōu)。
因為this.props
和this.state
可異步更新,你不應該依賴于它們的值來計算下一個狀態(tài)。
例如,此代碼可能無法更新計數器:
// Wrongthis.setState({ counter: this.state.counter + this.props.increment,});
要修復它,請使用setState()
接受函數而不是對象的第二種形式。該函數將接收前一個狀態(tài)作為第一個參數,并將更新應用時的道具作為第二個參數:
// Correctthis.setState((prevState, props) => ({ counter: prevState.counter + props.increment}));
我們使用了上面的箭頭函數,但它也適用于常規(guī)函數:
// Correctthis.setState(function(prevState, props) { return { counter: prevState.counter + props.increment };});
當你打電話時setState()
,React 將你提供的對象合并到當前狀態(tài)。
例如,您的狀態(tài)可能包含多個獨立變量:
constructor(props) { super(props); this.state = { posts: [], comments: [] }; }
然后,您可以使用單獨的setState()
調用獨立更新它們:
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
。
父母和孩子的組件都不知道某個組件是有狀態(tài)的還是無狀態(tài)的,它們不應該關心它是被定義為一個函數還是一個類。
這就是為什么狀態(tài)通常被稱為本地或封裝。除了擁有和設置它的組件之外,其他任何組件都無法訪問它。
組件可以選擇將其狀態(tài)作為道具傳遞給其子組件:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
這也適用于用戶定義的組件:
<FormattedDate date={this.state.date} />
FormattedDate
組件會收到date
它的道具,并不知道它是來自Clock
國家,Clock
道具還是手工輸入:
function FormattedDate(props) { return <h2>It is {props.date.toLocaleTimeString()}.</h2>;}
在 CodePen 上試用它。
這通常稱為“自頂向下”或“單向”數據流。任何狀態(tài)總是由某個特定組件擁有,并且從該狀態(tài)派生的任何數據或 UI 只能影響樹中“在其下”的組件。
如果將組件樹想象成道具的瀑布,則每個組件的狀態(tài)就像是一個額外的水源,它可以在任意點加入它,但也會流下來。
為了顯示所有組件都是真正隔離的,我們可以創(chuàng)建一個App
呈現三個<Clock>
的組件:
function App() { return ( <div> <Clock /> <Clock /> <Clock /> </div> );}ReactDOM.render( <App />, document.getElementById('root'));
在 CodePen 上試用它。
每個Clock
設置自己的計時器并獨立更新。
在 React 應用程序中,無論組件是有狀態(tài)的還是無狀態(tài)的,都被視為可能隨時間而改變的組件的實現細節(jié)。您可以在有狀態(tài)組件內使用無狀態(tài)組件,反之亦然。