State 和生命週期
這個章節會介紹在 React component 中 state 以及生命週期的概念。
在這個章節中,我們將會學習如何封裝 Clock
component 讓它可以真正的被重複使用。它將會設定本身的 timer 並且每秒更新一次。
我們可以像這樣封裝 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
設定 timer 並在每秒更新 UI 應該是 Clock
實作的細節的事實。
理想情況下,我們想要撰寫一次 Clock
並且它會自己更新:
ReactDOM.render(
<Clock />, document.getElementById('root')
);
如果要實現這個理想情況,我們需要加入「state」到 Clock
component。
State 類似於 prop,但它是私有且由 component 完全控制的。
component 被定義為 class 有一些額外的特性。Local state 就是 class 其中的一個特性。
改為以下方式:建立一個Clock.js
加入生命週期方法到 Class
在具有許多 component 的應用程式中,當 component 被 destroy 時,釋放所佔用的資源是非常重要的。
每當 Clock
render 到 DOM 的時候,我們想要設定一個 timer。在 React 中稱為「mount」。
每當產生的 Clock
DOM 被移除時,我們想要清除 timer。在 React 中稱為「unmount」。
每當 component 在 mount 或是 unmount 的時候,我們可以在 component class 上宣告一些特別的方法來執行一些程式碼:
這些方法被稱為「生命週期方法」。
componentDidMount()
方法會在 component 被 render 到 DOM 之後才會執行。這是設定 timer 的好地方:
注意我們是如何正確的在 this
(this.timerID
) 儲存 timer ID。
雖然 this.props
是由 React 本身設定的,而且 this.state
具有特殊的意義,如果你需要儲存一些不相關於資料流的內容(像是 timer ID),你可以自由的手動加入。
componentWillUnmount()
生命週期方法內移除 timer:最後,我們將會實作一個 tick()
的方法,Clock
component 將會在每秒執行它。
它將會使用 this.setState()
來安排 component local state 的更新:
讓我們快速的回顧一下發生了哪些事情,以及呼叫這些方法的順序:
- 當
<Clock />
被傳入到ReactDOM.render()
時,React 會呼叫Clock
component 的constructor。由於Clock
需要顯示目前的時間,它使用包含目前時間的 object 初始化this.state
。我們會在之後更新這個 state。 - React 接著呼叫
Clock
component 的render()
方法。這就是 React 如何了解應該要在螢幕上顯示什麼內容。React 接著更新 DOM 來符合Clock
的 render 輸出。 - 每當
Clock
輸出被插入到 DOM 時,React 會呼叫componentDidMount()
生命週期方法。在Clock
component 生命週期方法內,會要求瀏覽器設定 timer 每秒去呼叫 component 的tick()
方法。 - 瀏覽器每秒呼叫
tick()
方法。其中,Clock
component 透過包含目前時間的 object 呼叫setState()
來調度 UI 更新。感謝setState()
,React 現在知道 state 有所改變,並且再一次呼叫render()
方法來了解哪些內容該呈現在螢幕上。這時候,在render()
方法內的this.state.date
將會有所不同,因此 render 輸出將會是更新的時間。React 相應地更新 DOM。 - 如果
Clock
component 從 DOM 被移除了,React 會呼叫componentWillUnmount()
生命週期方法,所以 timer 會被停止。
正確的使用 State
有三件關於 setState()
的事情你應該要知道。
請不要直接修改 State
例如,這將不會重新 render component:
// 錯誤
this.state.comment = 'Hello';
相反的,使用 setState()
:
// 正確
this.setState({comment: 'Hello'});
你唯一可以指定 this.state
值的地方是在 constructor。
State 的更新可能是非同步的
React 可以將多個 setState()
呼叫批次處理為單一的更新,以提高效能。
因為 this.props
和 this.state
可能是非同步的被更新,你不應該依賴它們的值來計算新的 state。
例如,這個程式碼可能無法更新 counter:
// 錯誤
this.setState({
counter: this.state.counter + this.props.increment,
});
要修正這個問題,使用第二種形式的 setState()
,它接受一個 function 而不是一個 object。Function 將接收先前的 state 作為第一個參數,並且將更新的 props 作為第二個參數:
// 正確
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
在上面我們使用 arrow function,但它也可以適用於正常的 function:
// 正確
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
State 的更新將會被 Merge
當你呼叫 setState()
時,React 會 merge 你提供的 object 到目前的 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 });
});
}
這個 merge 是 shallow 的,所以 this.setState({comments})
保持 this.state.posts
的完整,但它完全取代了 this.state.comments
。
向下資料流
Parent 和 child component 不會知道某個 component 是 stateful 或 stateless 的 component,而且它們不在意它是透過 function 或是 class 被定義的。
這就是 state 通常被稱為 local state 或被封裝的原因。除了擁有和可以設定它之外的任何 component 都不能訪問它。
Component 可以選擇將它的 state 做為 props 往下傳遞到它的 child component:
<FormattedDate date={this.state.date} />
FormattedDate
component 會在它的 props 接收到 date
,但他不知道它是從 Clock
的 state 傳遞過來的,從 Clock
的 props 或者是透過手動輸入:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
這通常被稱作為「上至下」或「單向」的資料流。任何 state 總是由某個特定的 component 所擁有,任何從 state 得到的資料或 UI,state 只能影響在 tree「以下」的 component。
如果你想像一個 component tree 是一個 props 的瀑布,每個 component 的 state 像是一個額外的水流源頭,它在任意的某個地方而且往下流。
為了表示所有 component 真的都是被獨立的,我們可以建立一個 App
component 來 render 三個 <Clock>
:
function App() {
return (
<div>
<Clock /> <Clock /> <Clock /> </div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
每個 Clock
設定它本身的 timer 並獨立的更新。
在 React 應用程式中,不論 component 是 stateful 或 stateless 都被視為是實作 component 的細節,它可能隨著時間而改變。你可以在 stateful component 內使用 stateless component,反之亦然。
0 Comments:
張貼留言