新聞中心
通過調(diào)用 ReactDOM.render() 來修改我們想要渲染的元素:

function tick() {
const element = (
Hello, world!
It is {new Date().toLocaleTimeString()}.
);
ReactDOM.render(element,document.getElementById('root'));
}
setInterval(tick, 1000);在 CodePen 上嘗試
如何封裝真正可復(fù)用的 Clock 組件。將設(shè)置自己的計(jì)時(shí)器并每秒更新一次。
我們可以從封裝時(shí)鐘的外觀開始:
function Clock(props) {
return (
Hello, world!
It is {props.date.toLocaleTimeString()}.
);
}
function tick() {
ReactDOM.render(
,
document.getElementById('root')
);
}
setInterval(tick, 1000);在 CodePen 上嘗試
然而,它忽略了一個(gè)關(guān)鍵的技術(shù)細(xì)節(jié):Clock 組件需要設(shè)置一個(gè)計(jì)時(shí)器,并且需要每秒更新 UI。
理想情況下,我們希望只編寫一次代碼,便可以讓 Clock 組件自我更新:
ReactDOM.render(
,
document.getElementById('root')
);我們需要在 Clock 組件中添加 “state” 來實(shí)現(xiàn)這個(gè)功能。
State 與 props 類似,但是 state 是私有的,并且完全受控于當(dāng)前組件。
將函數(shù)組件轉(zhuǎn)換成 class 組件
通過以下五步將 Clock 的函數(shù)組件轉(zhuǎn)成 class 組件:
- 創(chuàng)建一個(gè)同名的 ES6 class,并且繼承于 React.Component。
- 添加一個(gè)空的 render() 方法。
- 將函數(shù)體移動(dòng)到 render() 方法之中。
- 在 render() 方法中使用 this.props 替換 props。
- 刪除剩余的空函數(shù)聲明。
class Clock extends React.Component {
render() {
return (
Hello, world!
It is {this.props.date.toLocaleTimeString()}.
);
}
}在 CodePen 上嘗試
現(xiàn)在 Clock 組件被定義為 class,而不是函數(shù)。
每次組件更新時(shí) render 方法都會(huì)被調(diào)用,但只要在相同的 DOM 節(jié)點(diǎn)中渲染
這就使得我們可以使用如 state 或生命周期方法等很多其他特性。
向 class 組件中添加局部的 state
我們通過以下三步將 date 從 props 移動(dòng)到 state 中:
- 把 render() 方法中的 this.props.date 替換成 this.state.date :
class Clock extends React.Component {
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}- 添加一個(gè) class 構(gòu)造函數(shù),然后在該函數(shù)中為 this.state 賦初值:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}通過以下方式將 props 傳遞到父類的構(gòu)造函數(shù)中:
constructor(props) {
super(props);
this.state = {date: new Date()};
}Class 組件應(yīng)該始終使用 props 參數(shù)來調(diào)用父類的構(gòu)造函數(shù)。
- 移除
元素中的 date 屬性:
ReactDOM.render(
,
document.getElementById('root')
);我們之后會(huì)將計(jì)時(shí)器相關(guān)的代碼添加到組件中。
代碼如下:
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}
render() {
return (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
ReactDOM.render(
, document.getElementById('root')
);在 CodePen 上嘗試
接下來,我們會(huì)設(shè)置 Clock 的計(jì)時(shí)器并每秒更新它。
將生命周期方法添加到 Class 中
在具有許多組件的應(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 (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}這些方法叫做“生命周期方法”。
componentDidMount() 方法會(huì)在組件已經(jīng)被渲染到 DOM 中后運(yùn)行,所以,最好在這里設(shè)置計(jì)時(shí)器:
componentDidMount() {
this.timerID = setInterval(() => this.tick(),1000);
}接下來把計(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() 來時(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 (
Hello, world!
It is {this.state.date.toLocaleTimeString()}.
);
}
}
ReactDOM.render(
,
document.getElementById('root')
);在 CodePen 上嘗試
現(xiàn)在時(shí)鐘每秒都會(huì)刷新。
讓我們來快速概括一下發(fā)生了什么和這些方法的調(diào)用順序:
- 當(dāng)
被傳給 ReactDOM.render()的時(shí)候,React 會(huì)調(diào)用 Clock 組件的構(gòu)造函數(shù)。因?yàn)?nbsp;Clock 需要顯示當(dāng)前的時(shí)間,所以它會(huì)用一個(gè)包含當(dāng)前時(shí)間的對(duì)象來初始化 this.state。我們會(huì)在之后更新 state。 - 之后 React 會(huì)調(diào)用組件的 render() 方法。這就是 React 確定該在頁面上展示什么的方式。然后 React 更新 DOM 來匹配 Clock 渲染的輸出。
- 當(dāng) Clock 的輸出被插入到 DOM 中后,React 就會(huì)調(diào)用 ComponentDidMount() 生命周期方法。在這個(gè)方法中,Clock 組件向?yàn)g覽器請(qǐng)求設(shè)置一個(gè)計(jì)時(shí)器來每秒調(diào)用一次組件的 tick() 方法。
- 瀏覽器每秒都會(huì)調(diào)用一次 tick() 方法。 在這方法之中,Clock 組件會(huì)通過調(diào)用 setState() 來計(jì)劃進(jìn)行一次 UI 更新。得益于 setState() 的調(diào)用,React 能夠知道 state 已經(jīng)改變了,然后會(huì)重新調(diào)用 render() 方法來確定頁面上該顯示什么。這一次,render() 方法中的 this.state.date 就不一樣了,如此以來就會(huì)渲染輸出更新過的時(shí)間。React 也會(huì)相應(yīng)的更新 DOM。
- 一旦 Clock 組件從 DOM 中被移除,React 就會(huì)調(diào)用 componentWillUnmount() 生命周期方法,這樣計(jì)時(shí)器就停止了。
正確地使用 State
關(guān)于 setState() 你應(yīng)該了解三件事:
不要直接修改 State
例如,此代碼不會(huì)重新渲染組件:
// Wrong
this.state.comment = 'Hello';而是應(yīng)該使用 setState():
// Correct
this.setState({comment: 'Hello'});構(gòu)造函數(shù)是唯一可以給 this.state 賦值的地方:
State 的更新可能是異步的
出于性能考慮,React 可能會(huì)把多個(gè) setState() 調(diào)用合并成一個(gè)調(diào)用。
因?yàn)?nbsp;this.props 和 this.state 可能會(huì)異步更新,所以你不要依賴他們的值來更新下一個(gè)狀態(tài)。
例如,此代碼可能會(huì)無法更新計(jì)數(shù)器:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});要解決這個(gè)問題,可以讓 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ù),不過使用普通的函數(shù)也同樣可以:
// Correct
this.setState(function(state, props) {
return {
counter: state.counter + props.increment
};
});State 的更新會(huì)被合并
當(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() 來單獨(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。
數(shù)據(jù)是向下流動(dòng)的
不管是父組件或是子組件都無法知道某個(gè)組件是有狀態(tài)的還是無狀態(tài)的,并且它們也并不關(guān)心它是函數(shù)組件還是 class 組件。
這就是為什么稱 state 為局部的或是封裝的的原因。除了擁有并設(shè)置了它的組件,其他組件都無法訪問。
組件可以選擇把它的 state 作為 props 向下傳遞到它的子組件中:
It is {this.state.date.toLocaleTimeString()}.
這對(duì)于自定義組件同樣適用:
FormattedDate 組件會(huì)在其 props 中接收參數(shù) date,但是組件本身無法知道它是來自于 Clock 的 state,或是 Clock 的 props,還是手動(dòng)輸入的:
function FormattedDate(props) {
return It is {props.date.toLocaleTimeString()}.
;
}在 CodePen 上嘗試
這通常會(huì)被叫做“自上而下”或是“單向”的數(shù)據(jù)流。任何的 state 總是所屬于特定的組件,而且從該 state 派生的任何數(shù)據(jù)或 UI 只能影響樹中“低于”它們的組件。
如果你把一個(gè)以組件構(gòu)成的樹想象成一個(gè) props 的數(shù)據(jù)瀑布的話,那么每一個(gè)組件的 state 就像是在任意一點(diǎn)上給瀑布增加額外的水源,但是它只能向下流動(dòng)。
為了證明每個(gè)組件都是真正獨(dú)立的,我們可以創(chuàng)建一個(gè)渲染三個(gè) Clock 的 App 組件:
function App() {
return (
);
}
ReactDOM.render(
,
document.getElementById('root')
);在 CodePen 上嘗試
每個(gè) Clock 組件都會(huì)單獨(dú)設(shè)置它自己的計(jì)時(shí)器并且更新它。
在 React 應(yīng)用中,組件是有狀態(tài)組件還是無狀態(tài)組件屬于組件實(shí)現(xiàn)的細(xì)節(jié),它可能會(huì)隨著時(shí)間的推移而改變。你可以在有狀態(tài)的組件中使用無狀態(tài)的組件,反之亦然。
當(dāng)前名稱:創(chuàng)新互聯(lián)React教程:ReactState(狀態(tài))&生命周期
URL分享:http://fisionsoft.com.cn/article/ccojgph.html


咨詢
建站咨詢
