通過調(diào)用 ReactDOM.render() 來修改我們想要渲染的元素:
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);
如何封裝真正可復用的 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);
然而,它忽略了一個關鍵的技術細節(jié):Clock 組件需要設置一個計時器,并且需要每秒更新 UI。
理想情況下,我們希望只編寫一次代碼,便可以讓 Clock 組件自我更新:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
我們需要在 Clock 組件中添加 “state” 來實現(xiàn)這個功能。
State 與 props 類似,但是 state 是私有的,并且完全受控于當前組件。
通過以下五步將 Clock 的函數(shù)組件轉成 class 組件:
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
現(xiàn)在 Clock 組件被定義為 class,而不是函數(shù)。
每次組件更新時 render 方法都會被調(diào)用,但只要在相同的 DOM 節(jié)點中渲染 <Clock /> ,就僅有一個 Clock 組件的 class 實例被創(chuàng)建使用。
這就使得我們可以使用如 state 或生命周期方法等很多其他特性。
我們通過以下三步將 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 傳遞到父類的構造函數(shù)中:
constructor(props) {
super(props);
this.state = {date: new Date()};
}
Class 組件應該始終使用 props 參數(shù)來調(diào)用父類的構造函數(shù)。
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 中被稱為“掛載(mount)”。
同時,當 DOM 中 Clock 組件被刪除的時候,應該清除計時器。
這在 React 中被稱為“卸載(unmount)”。
我們可以為 class 組件聲明一些特殊的方法,當組件掛載或卸載時就會去執(zhí)行這些方法:
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() 方法會在組件已經(jīng)被渲染到 DOM 中后運行,所以,最好在這里設置計時器:
componentDidMount() {
this.timerID = setInterval(() => this.tick(),1000);
}
接下來把計時器的 ID 保存在 this 之中(this.timerID)。
盡管 this.props 和 this.state 是 React 本身設置的,且都擁有特殊的含義,但是其實你可以向 class 中隨意添加不參與數(shù)據(jù)流(比如計時器 ID)的額外字段。
我們會在 componentWillUnmount() 生命周期方法中清除計時器:
componentWillUnmount() {
clearInterval(this.timerID);
}
最后,我們會實現(xiàn)一個叫 tick() 的方法,Clock 組件每秒都會調(diào)用它。
使用 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')
);
現(xiàn)在時鐘每秒都會刷新。
讓我們來快速概括一下發(fā)生了什么和這些方法的調(diào)用順序:
關于 setState() 你應該了解三件事:
例如,此代碼不會重新渲染組件:
// Wrong
this.state.comment = 'Hello';
而是應該使用 setState():
// Correct
this.setState({comment: 'Hello'});
構造函數(shù)是唯一可以給 this.state 賦值的地方:
出于性能考慮,React 可能會把多個 setState() 調(diào)用合并成一個調(diào)用。
因為 this.props 和 this.state 可能會異步更新,所以你不要依賴他們的值來更新下一個狀態(tài)。
例如,此代碼可能會無法更新計數(shù)器:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
要解決這個問題,可以讓 setState() 接收一個函數(shù)而不是一個對象。這個函數(shù)用上一個 state 作為第一個參數(shù),將此次更新被應用時的 props 做為第二個參數(shù):
// Correct
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
上面使用了箭頭函數(shù),不過使用普通的函數(shù)也同樣可以:
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});
當你調(diào)用 setState() 的時候,React 會把你提供的對象合并到當前的 state。
例如,你的 state 包含幾個獨立的變量:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
然后你可以分別調(diào)用 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。
不管是父組件或是子組件都無法知道某個組件是有狀態(tài)的還是無狀態(tài)的,并且它們也并不關心它是函數(shù)組件還是 class 組件。
這就是為什么稱 state 為局部的或是封裝的的原因。除了擁有并設置了它的組件,其他組件都無法訪問。
組件可以選擇把它的 state 作為 props 向下傳遞到它的子組件中:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
這對于自定義組件同樣適用:
<FormattedDate date={this.state.date} />
FormattedDate 組件會在其 props 中接收參數(shù) date,但是組件本身無法知道它是來自于 Clock 的 state,或是 Clock 的 props,還是手動輸入的:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
這通常會被叫做“自上而下”或是“單向”的數(shù)據(jù)流。任何的 state 總是所屬于特定的組件,而且從該 state 派生的任何數(shù)據(jù)或 UI 只能影響樹中“低于”它們的組件。
如果你把一個以組件構成的樹想象成一個 props 的數(shù)據(jù)瀑布的話,那么每一個組件的 state 就像是在任意一點上給瀑布增加額外的水源,但是它只能向下流動。
為了證明每個組件都是真正獨立的,我們可以創(chuàng)建一個渲染三個 Clock 的 App 組件:
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
每個 Clock 組件都會單獨設置它自己的計時器并且更新它。
在 React 應用中,組件是有狀態(tài)組件還是無狀態(tài)組件屬于組件實現(xiàn)的細節(jié),它可能會隨著時間的推移而改變。你可以在有狀態(tài)的組件中使用無狀態(tài)的組件,反之亦然。
更多建議: