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