這篇介紹 React 組件中狀態和聲明週期的概念。詳情能夠查看API參考 。javascript
思考前一部分中時鐘的例子。渲染元素中,咱們僅學習了一種更新 UI 的方式。調用 ReactDOM.render() 改變渲染後的輸出。html
function tick() { const element = ( <div> <h1>Hello, world!</h1> <h2>It is {new Date().toLocaleTimeString()}.</h2> </div> ); ReactDOM.render( element, document.getElementById('root') ); }
在線嘗試 java
這部分,咱們學習如何編寫真正可複用的封裝 Clock 組件。 它會設置本身的計時器每秒更新本身。react
咱們從封裝 時鐘的外層開始:segmentfault
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 的細節實現。瀏覽器
理想狀況下咱們寫一次讓 Clock 更新自身:異步
ReactDOM.render( <Clock />, document.getElementById('root') );
實現這一需求咱們須要給 Clock 組件添加 "state".函數
State 很相似 props, 不一樣的是它徹底私有由組件控制。post
轉化一個相似 Clock 的函數組件爲類組件須要五步:
class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.props.date.toLocaleTimeString()}.</h2> </div> ); } }
[在線嘗試]()
函數組件定義的 Clock 如今由類組件定義。
render() 方法會在每次更新時調用,可是隻要咱們渲染 <Clock /> 到一樣的 DOM 節點,就會使用 Clock 類的單一實例。這讓咱們可使用如 local state 和 lifecycle 鉤子等額外的特性。
三步把 date 從 props 移動到 state:
class Clock extends React.Component { render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
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> ); } }
注意咱們傳遞 props 到基礎構造器:
construct(props) { super(props); this.state = {date: new Date()}; }
類組件老是經過 props 調用基礎構造器。
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 設置他本身的計時器每秒更新自身。
多個組件的應用中,當組件銷燬時釋放組件佔用的資源很是重要。
咱們想 [設置一個計時器]() 不管什麼時候 Clock 第一次被渲染到 DOM. React 中稱之爲 」mounting".
咱們也想 [清楚一個計時器]() 不管什麼時候 Clock 被DOM 移除。 React 中稱之爲 「unmounting".
當組件 mounts 和 unmounts 時咱們能夠在組件類聲明特殊的方法來運行。
class Clock extends React.Component { constructor(props) { super(props); this.state = {date: new Date()}; } componentDidMount() { } componentWillMount() { } render() { return ( <div> <h1>Hello, world!</h1> <h2>It is {this.state.date.toLocaleTimeString()}.</h2> </div> ); } }
這些方法稱爲 "lifecycle hooks(生命週期鉤子)".
componentDidMount() 鉤子在組件輸出渲染至DOM 後運行。這個位置很適合創建一個計時器:
componentDidMount() { this.timerID = setInterval(() => this.tick(), 1000); }
注意咱們如何正確的將 計時器 ID 保存到 this.
當 React 設置 this.props this.state 有了特別的含義,你能夠隨意爲類手動添加額外字段,若是你須要保存一些不參與數據流(好比 timerID)。
咱們在 componentWillUnmount() 生命週期鉤子函數中去掉 計時器。
componentWillUnmount() { clearInterval(this.timerID); }
最後咱們,實現一個稱爲 tick() 的方法實現 Clock 組件每秒運行。
這會用到 this.setState() 來調度更新組件的本地狀態:
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') );
如今時鐘按秒運行。
快速的回顧下發生了什麼還有方法調用順序:
關於setState() 的三個須知:
例如,這樣不會 從新渲染一個組件:
// Wrong this.state.comment = 'Hello';
應該使用 setState():
// Correct this.setState({comment: 'Hello'});
構造器是惟一可爲 this.state 賦值的地方。
React 爲了性能可能批量屢次 調用 setState() 一個單獨的更新。
由於 this.props 和 this.state 可能異步更新,不該該依賴它們的值來計算下一個狀態。
例如,以下代碼可能更新計數器失敗:
// Wrong this.setState({ counter: this.state.counter + this.props.increment, });
修復這個問題,使用 setState() 的第二種形式,接受函數而不是一個對象。這個函數接受以前的 state 做爲第一個參數, 當時間更新時 props 做爲第二個參數。
// Correct this.setState((prevState, props) => ({ counter: prevState.counter + props.increment }));
上面的例子中咱們使用了 [箭頭函數](),但常規函數也是能夠的。
// Correnct this.setState(function(prevState, props) { return { counter: prevState.counter + props.increment }; });
當調用 setState(), React會合並你提供給當前狀態的對象。
例如, 你的狀態可能包含多個獨立的變量:
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 向下做爲 props 給它的子組件:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
這對用戶自定義組件也一樣有效:
<FormattedDate date={this.state.date} />
FormattedDate 組件接受它 props 中的 date,並不知道他來自 Clock的 state,仍是來自 Clock 的 props, 或者是手動輸入:
function FormattedDate(props) { return <h2>It is {props.date.toLocaleTimeString()}.</h2>; }
這個一般稱爲 "top-down(自上而下)" 或者 "unidirectional(單向)" 數據流。任何 state 總數被特定的組件所擁有,任何經過 state 傳遞的數據或 UI 都只能影響樹形結構的下方組件。
你能夠家鄉組件樹是一個 props 瀑布,每一個組件的狀態就像一個額外的水源在隨機點加入它同時向下流。
爲展現全部組件真正獨立,咱們建立一個 App 組件 渲染三個 <Clock />:
function App() {
return (
<div> <Clock /> <Clock /> <Clock /> </div> );
}
ReactDOM.render() {
<App />,
document.getElementById('root')
[在線嘗試]() 每一個 **Clock** 設置他本身的計時器獨立更新他們。 React 應用中,不管一個組件是有狀態仍是無狀態都被當作一個組件可隨時間改變的實現細節。 你可在有狀態組件中使用無狀態組件,反之亦然。