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