考慮前面部分中的滴答時鐘示例(第三章)。
到目前爲止,咱們只學習了一種更新UI的方法。
咱們調用ReactDOM.render()
來改變渲染輸出:javascript
import React from 'react'; import ReactDOM from 'react-dom'; function tick() { const element = ( <div> <h1>Hell world</h1> <h2>It is {new Date().toLocaleTimeString()}</h2> </div> ); ReactDOM.render( element, document.getElementById('root') ); } setInterval(tick, 1000);
在本節中,咱們將學習如何使Clock
組件真正可重用和封裝。 它將設置本身的計時器並每秒更新一次。
咱們能夠從封裝時鐘的外觀開始:java
import React from 'react'; import ReactDOM from 'react-dom'; 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);
然而,它缺乏了一個關鍵要求:時鐘設置一個定時器和每秒更新UI的事實應該是時鐘的實現細節。
理想狀況下,咱們要寫這一次,並由時鐘自己來更新時間:react
ReactDOM.render( <Clock />, document.getElementById('root') );
要實現這一點,咱們須要添加「state」到時鐘組件。瀏覽器
state相似於props,但它是私有的,徹底由組件控制。dom
咱們以前提到,定義爲類組件具備一些附加功能。 內部state就是:一個只有類組件可用的功能。異步
您能夠經過五個步驟將功能組件(如Clock)轉換爲類組件 :函數
建立一個與擴展React.Component
相同名稱的ES6類。post
爲它添加一個單一的空方法render()
。性能
將函數的主體移動到render()
方法中。學習
在render()
主體中用this.props
替換props
。
刪除剩餘的空函數聲明。
class Clock extends React.Component { render() { return ( <div> <h1>hello world</h1> <h2>It is {this.props.date.toLocaleTimeString()}.</h2> </div> ) }; }
Clock
如今已經從新定義爲類組件而不是以前的功能組件了。
這使咱們可使用額外的功能,如內部state和生命週期鉤子。
咱們將分爲三個步驟把date
從props
移動到state
:
render()
方法中將this.props.date
替換爲this.state.date
:class Clock extends React.Component { render() { return ( <div> <h1>hello world</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
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.toLocalTimeString()}.</h2> </div> ); } }
注意咱們如何將props傳遞給基類的構造函數:
constructor(props) { super(props); this.state = {date: new Date()}; }
類組件應該老是用props調用基類構造函數。
<Clock />
元素中刪除date
prop:ReactDOM.render( <Clock />, document.getElementById('root') );
咱們稍後將定時器代碼添加回組件自己。
結果以下所示:
import React from 'react'; import ReactDOM from 'react-dom'; 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') );
接下來,咱們將使時鐘設置本身的定時器,並每秒更新一次。
在具備許多組件的應用程序中,釋放組件在銷燬時佔用的資源很是重要。
咱們想要在第一次將時鐘渲染到DOM時設置一個計時器。 這在React中稱爲「安裝(mounting)」
。
咱們還想清除定時器,當時鍾產生的DOM被刪除。 這在React中稱爲「卸載(unmounting)"
。
咱們能夠在組件類上聲明特殊方法,以便在組件裝入和卸載時運行一些代碼:
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> ); } }
這些方法稱爲「生命週期鉤子」
。componentDidMount()
子在組件輸出呈現到DOM以後運行。 這是設置計時器的好地方:
componentDidMount() { this.timerID = setInterval( () => this.tick(), 1000 ) }
注意咱們如何保存計時器ID就在這。
雖然this.props
是由React自己設置的,而且this.state
有一個特殊的含義,若是你須要存儲不用於視覺輸出的東西,你能夠手動地添加額外的字段到類中。
若是你不使用render()
中的東西,它不該該放置在state
中。
咱們將拆除componentWillUnmount()
生命週期鉤子中的計時器:
componentWillUnmount() { clearInterval(this.timerID); }
最後,咱們將實現每秒運行的tick()
方法。
它將使用this.setState()
來調度組件本地state的更新:
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') );
如今時鐘每秒鐘都在滴答地走,棒不棒。。。。
讓咱們快速回顧一下發生了什麼以及調用方法的順序:
1)當將<Clock />
傳遞給ReactDOM.render()時,React調用Clock組件的構造函數。因爲Clock須要顯示當前時間,它使用包括當前時間的對象初始化this.state
。咱們稍後將更新此state。
2)React而後調用Clock組件的render()
方法。這是React如何學習應該在屏幕上顯示什麼。 React而後更新DOM以匹配時鐘的渲染輸出。
3)當時鍾輸出插入到DOM中時,React調用componentDidMount()
生命週期鉤子。在其中,時鐘組件要求瀏覽器設置一個定時器,每秒調用tick()
一次。
4)每秒鐘瀏覽器調用tick()
方法。在其中,Clock組件經過調用setState()
和包含當前時間的對象來調度UI更新。因爲setState()
調用,React知道state已更改,並再次調用render()
方法來了解屏幕上應該顯示的內容。這個時候,render()
方法中的this.state.date
將會不一樣,所以渲染輸出將包括更新的時間。 React相應地更新DOM。
5)若是時鐘組件從DOM中被移除,React將調用componentWillUnmount()
生命週期鉤子,所以定時器中止。
關於setState()
你應該瞭解三件事情:
例如,這將不會從新渲染組件:
// 這是錯誤的 this.state.comment = 'hello';
應該使用setState()
代替:
// 這是正確的 this.setState({comment: 'hello'});
惟一能夠分配this.state
的地方是構造函數。
React能夠將多個setState()
用批處理爲單個更新以實現較高的性能。
由於this.props
和this.state
多是異步更新的,你不該該依賴它們的值來計算下一個state。
例如,此代碼可能沒法更新計數器:
// 這是錯誤的 this.setState({ counter: this.state.counter + this.props.increment, });
要解決它,應該使用回調函數而不是對象來調用setState()
。 回調函數將接收先前的state做爲第一個參數,並將應用更新時的props
做爲第二個參數:
// 這是正確的 this.setState((prevState, props) => ({ counter: prevState.counter + props.increment }));
咱們使用上面的箭頭函數,但它也能夠與常規函數一塊兒使用:
// 這一樣也是正確的,將剪頭函數改成普通函數 this.setState(function(prevState, props) { return { counter: prevState.counter + prps.increment } });
當調用setState()
時,React會將您提供的對象合併到當前state。
例如,您的state可能包含幾個獨立變量:
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
而已。
父組件和子組件都不能知道某個組件是有State的仍是無State的,而且它們不該該關心它是否爲功能組件或類組件。
這就是爲何State一般被設置爲局部變量或封裝到組件內部。 除了擁有和設置它的組件以外的其餘任何組件都不能訪問它。
組件能夠選擇將其state做爲props傳遞給其子組件:
<h2>Is is {this.state.date.toLocaleTimeString()}.</h2>
這也適用於用戶定義的組件:
<formattedDate date={this.state.data} />
FormattedDate
組件將在其props
中接收date
,而且不知道它是來自時鐘的state
,props
仍是手動輸入
:
function FormattedData(props) { return <h2>Is is {props.date.toLocaleTimeString()}.</h2>; }
這一般被稱爲「自頂向下」
或「單向」
數據流。 任何state老是由一些特定組件擁有,而且從該state派生的任何數據或UI只能影響樹中的「下面」組件。
若是你想象一個組件樹做爲props的瀑布流,每一個組件的state就像一個額外的水源,它能夠在任意點鏈接它,但也向下流。
爲了顯示全部組件都是真正隔離的,咱們能夠建立一個App組件來渲染三個<Clock>
:
function App() { return ( <div> <Clock /> <Clock /> <Clock /> </div> ); } ReactDOM.render( <App />, document.getElementById('root') );
每一個時鐘設置本身的定時器並獨立更新。在React應用程序中,組件是有狀態仍是無狀態被視爲可能隨時間更改的組件的實現細節。 您能夠在有狀態組件內使用無狀態組件,反之亦然。