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