React State(狀態)
React 把組件看成是一個狀態機(State Machines)。通過與用戶的交互,實現不同狀態,然後渲染 UI,讓用戶介面和數據保持一致。
React 裏,只需更新組件的 state,然後根據新的 state 重新渲染用戶介面(不要操作 DOM)。
以下實例創建一個名稱擴展為 React.Component 的 ES6 類,在 render() 方法中使用 this.state 來修改當前的時間。
添加一個類構造函數來初始化狀態 this.state,類組件應始終使用 props 調用基礎構造函數。
React 實例
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>現在是 {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('example')
);
接下來,我們將使Clock設置自己的計時器並每秒更新一次。
將生命週期方法添加到類中
在具有許多組件的應用程式中,在銷毀時釋放組件所佔用的資源非常重要。
每當 Clock 組件第一次加載到 DOM 中的時候,我們都想生成定時器,這在 React 中被稱為掛載。
同樣,每當 Clock 生成的這個 DOM 被移除的時候,我們也會想要清除定時器,這在 React 中被稱為卸載。
我們可以在組件類上聲明特殊的方法,當組件掛載或卸載時,來運行一些代碼:
React 實例
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>現在是 {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('example')
);
實例解析:
componentDidMount() 與 componentWillUnmount() 方法被稱作生命週期鉤子。
在組件輸出到 DOM 後會執行 componentDidMount() 鉤子,我們就可以在這個鉤子上設置一個定時器。
this.timerID 為定時器的 ID,我們可以在 componentWillUnmount() 鉤子中卸載定時器。
代碼執行順序:
-
當 <Clock />
被傳遞給 ReactDOM.render()
時,React 調用 Clock
組件的構造函數。 由於 Clock
需要顯示當前時間,所以使用包含當前時間的對象來初始化 this.state
。 我們稍後會更新此狀態。
-
React 然後調用 Clock
組件的 render()
方法。這是 React 瞭解螢幕上應該顯示什麼內容,然後 React 更新 DOM 以匹配 Clock
的渲染輸出。
-
當 Clock
的輸出插入到 DOM 中時,React 調用 componentDidMount()
生命週期鉤子。 在其中,Clock
組件要求流覽器設置一個定時器,每秒鐘調用一次 tick()
。
-
流覽器每秒鐘調用 tick()
方法。 在其中,Clock
組件通過使用包含當前時間的對象調用 setState()
來調度UI更新。 通過調用 setState()
,React 知道狀態已經改變,並再次調用 render()
方法來確定螢幕上應當顯示什麼。 這一次,render()
方法中的 this.state.date
將不同,所以渲染輸出將包含更新的時間,並相應地更新 DOM。
-
一旦 Clock
組件被從 DOM 中移除,React 會調用 componentWillUnmount()
這個鉤子函數,定時器也就會被清除。
數據自頂向下流動
父組件或子組件都不能知道某個組件是有狀態還是無狀態,並且它們不應該關心某組件是被定義為一個函數還是一個類。
這就是為什麼狀態通常被稱為局部或封裝。 除了擁有並設置它的組件外,其他組件不可訪問。
以下實例中 FormattedDate 組件將在其屬性中接收到 date 值,並且不知道它是來自 Clock 狀態、還是來自 Clock 的屬性、亦或手工輸入:
React 實例
function FormattedDate(props) {
return <h2>現在是 {props.date.toLocaleTimeString()}.</h2>;
}
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>
<FormattedDate date={this.state.date} />
</div>
);
}
}
ReactDOM.render(
<Clock />,
document.getElementById('example')
);
這通常被稱為自頂向下或單向數據流。 任何狀態始終由某些特定組件所有,並且從該狀態導出的任何數據或 UI 只能影響樹中下方的組件。
如果你想像一個組件樹作為屬性的瀑布,每個組件的狀態就像一個額外的水源,它連接在一個任意點,但也流下來。
為了表明所有組件都是真正隔離的,我們可以創建一個 App 組件,它渲染三個Clock:
React 實例
function FormattedDate(props) {
return <h2>現在是 {props.date.toLocaleTimeString()}.</h2>;
}
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>
<FormattedDate date={this.state.date} />
</div>
);
}
}
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(<App />, document.getElementById('example'));
以上實例中每個 Clock 組件都建立了自己的定時器並且獨立更新。
在 React 應用程式中,組件是有狀態還是無狀態被認為是可能隨時間而變化的組件的實現細節。
我們可以在有狀態組件中使用無狀態組件,也可以在無狀態組件中使用有狀態組件。