思考一下之前章節(jié)中的時(shí)鐘案例
到目前為止瓜浸,我們只學(xué)習(xí)了一種更新UI的方式缨历。
我們調(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);
在這一章節(jié)中痛侍,我們將學(xué)習(xí)如何把Clock 這個(gè)組件真正地進(jìn)行封裝和復(fù)用惜颇。它將會(huì)啟動(dòng)屬于自己的定時(shí)器并且每秒鐘它將會(huì)對(duì)自己進(jìn)行更新。
我們首先開始封裝Clock 在頁面中呈現(xiàn)的樣子:
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);
但是壶运,它缺少很關(guān)鍵的一點(diǎn):事實(shí)上 Clock啟動(dòng)一個(gè)定時(shí)器并且在每秒鐘更新UI應(yīng)該是Clock組件的內(nèi)部實(shí)現(xiàn)。
最理想的情況是我們只這樣寫一次浪秘,然后讓Clock組件對(duì)自己進(jìn)行更新:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
為了可以像這樣實(shí)現(xiàn)蒋情,我們需要給Clock組件添加狀態(tài)(state)。
state和props相似耸携,但是它是私有的棵癣,并且完全被組件控制。
我們?cè)谥疤岬竭^夺衍,如果一個(gè)組件以類的方式定義的話狈谊,會(huì)有一些額外的特性:局部state變量就是這樣一個(gè)特性:這個(gè)特性僅為類式定義組件提供。
把函數(shù)轉(zhuǎn)變?yōu)轭?/h3>
你可以把一個(gè)函數(shù)式組件通過以下五個(gè)步驟來轉(zhuǎn)化為類式組件:
1.以相同的名字創(chuàng)建一個(gè)ES6類沟沙,這個(gè)類繼承React.Component
2.在里面添加一個(gè)被稱為render()的方法
3.把函數(shù)體中的內(nèi)容移到render()方法中
4.把render()中的props用this.props替換
5.刪掉原來的函數(shù)
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
Clock組件現(xiàn)在就是一個(gè)類式定義的組件了河劝,而不再是函數(shù)式定義的組件。
這將使得我們可以使用例如局部(state)和生命周期(Lifecycle)鉤子
在類里添加state局部變量
我們將會(huì)用三步把date從props中移動(dòng)到state中:
1.在render()方法中用this.state.date來替換this.props.date:
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
2.添加一個(gè)類構(gòu)造器(class constructor)來標(biāo)識(shí)初始化的this.state:
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傳入到這個(gè)構(gòu)造器的(constructor):
constructor(props) {
super(props);
this.state = {date: new Date()};
}
類式定義的組件應(yīng)該總是調(diào)用構(gòu)造器函數(shù)矛紫,并且傳入props
3.從<Clock /> 元素中移除date屬性:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
我們將會(huì)在稍后給Clock組件自身添加定時(shí)器部分的代碼
現(xiàn)在它看起來應(yīng)該是這樣的:
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')
);
接下來赎瞎,我們將使得這個(gè)Clock啟動(dòng)自己的定時(shí)器,并且每秒鐘更新自己一次颊咬。
為類添加生命周期函數(shù)
在一個(gè)擁有許多組件的應(yīng)用中务甥,當(dāng)一個(gè)組件被銷毀時(shí),釋放掉它所占用的資源是十分重要的喳篇。
我們想要為Clock啟動(dòng)一個(gè)定時(shí)器敞临,無論何時(shí)它被第一次渲染進(jìn)DOM中。在React中杭隙,這個(gè)過程被稱為掛載(mounting).
我們同時(shí)也想清除一個(gè)定時(shí)器哟绊,無論何時(shí)Clock元素從DOM中移除。在React中,這個(gè)過程被稱為卸載(unmounting).
我們可以在組件類中聲明一些特殊的方法票髓,在組件掛載(mounting)和卸載(unmounting)的時(shí)候執(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>
);
}
}
這些方法被稱為生命周期鉤子(lifecycle hooks)
這個(gè)componentDidMount()生命周期鉤子在組件輸出已經(jīng)被渲染進(jìn)DOM后執(zhí)行攀涵,在這里是開啟一個(gè)定時(shí)器的好地方:
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
注意,我們?cè)趖his上保存了timer ID
this.props是React自己設(shè)置的洽沟,而this.state有著一個(gè)特殊的含義以故,如果你想存儲(chǔ)一些西并且這些東西不會(huì)為了視覺上的展示而使用,你可以隨意的在類中手動(dòng)的添加域(fileds)
如果這些東西你不需要在render()方法中使用裆操,那么請(qǐng)不要把它放在state中
我們將會(huì)在componentWillUnmount()生命周期鉤子中清除定時(shí)器:
componentWillUnmount() {
clearInterval(this.timerID);
}
最后怒详,我們將實(shí)現(xiàn)一個(gè)被稱為tick()的函數(shù),來讓Clock組件每秒鐘運(yùn)行一次
它將會(huì)使用this.setState() 方法來按照計(jì)劃更新Clock組件中的局部變量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í)鐘每秒鐘滴答走一次啦W偾@ニ浮!
讓我們一起快速的總結(jié)一下發(fā)生了什么缎岗,這些方法是按照什么樣的順序來調(diào)用的:
1.當(dāng)<Clock />元素被傳入到 ReactDOM.render()方法中静尼,React將會(huì)調(diào)用Clock組件中的構(gòu)造器函數(shù)(constructor)。因?yàn)闀r(shí)鐘需要展示當(dāng)前的時(shí)間传泊,它使用一個(gè)包含當(dāng)前時(shí)間的對(duì)象來初始化this.state鼠渺。我們將會(huì)在稍后更新這個(gè)state。
2.React然后調(diào)用了Clock組件的render() 方法眷细,通過這樣使得React知道了在頁面上應(yīng)該顯示什么拦盹。然后React更新DOM來使之與Clock組件的渲染輸出相匹配。
3.當(dāng)Clock 的渲染輸出被插入的DOM中后溪椎,React調(diào)用componentDidMount()生命周期鉤子普舆。在這里面,Clock組件要求瀏覽器開啟一個(gè)定時(shí)器池磁,來每秒鐘調(diào)用一次組件中的tick()方法奔害。
4.每秒鐘瀏覽器調(diào)用一次tick()方法。在它里面地熄,Clock組件按照計(jì)劃更新UI华临,通過調(diào)用setState() 方法,向其中傳入一個(gè)包含當(dāng)前時(shí)間的對(duì)象端考。多虧了setState() 方法的調(diào)用雅潭,React知道了state已經(jīng)發(fā)生了改變,然后再一次調(diào)用render()方法去了解應(yīng)該在頁面上展示什么却特。這時(shí)扶供,在render()方法中的this.state.date發(fā)生了改變,因此渲染輸出將會(huì)包含更新的時(shí)間裂明。React據(jù)此更新DOM椿浓。
5.如果Clock組件從DOM中移除,React會(huì)調(diào)用componentWillUnmount()這個(gè)生命周期鉤子來清除定時(shí)器。
正確地使用state
關(guān)于setState()方法你有三點(diǎn)需要知道:
不要直接對(duì)state進(jìn)行修改
例如扳碍,這樣子并不會(huì)讓組件重新渲染:
// Wrong
this.state.comment = 'Hello';
取而代之的提岔,應(yīng)該使用setState()方法:
// Correct
this.setState({comment: 'Hello'});
唯一的你可以指定this.state的地方就是在構(gòu)造器函數(shù)中(constructor)中
state更新可能是異步的
React可能處于性能的考慮來對(duì)多個(gè)setState()方法調(diào)用,通過批操作只進(jìn)行一整次更新(React may batch multiple setState() calls into a single update for performance.)
由于this.props和this.state的更新可能是異步的笋敞,你不應(yīng)該依賴它們的值為了下一次state計(jì)算(you should not rely on their values for calculating the next state.)
例如碱蒙,這段代碼可能無法更新這個(gè)counter:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
為了修復(fù)它,我們將使用第二種形式的setState()方法夯巷,它將接收一個(gè)函數(shù)而不是一個(gè)對(duì)象赛惩。這個(gè)函數(shù)將會(huì)接收previous state 作為第一個(gè)參數(shù),
更新申請(qǐng)的時(shí)候的props作為第二個(gè)參數(shù)(the props at the time the update is applied as the second argument):
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
在上面我們使用了箭頭函數(shù)趁餐,我們同樣可以使用常規(guī)的函數(shù):
// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
state更新被融合
當(dāng)你調(diào)用setState()方法喷兼,React把你提供的對(duì)象融合進(jìn)當(dāng)前的state對(duì)象。
例如澎怒,你的state可能包含幾個(gè)單獨(dú)的變量:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
你可以單獨(dú)的更新它們褒搔,通過分別調(diào)用setState()方法:
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
這個(gè)合并是一種淺層的合并阶牍,因此this.setState({comments})使得this.state.posts 完好無損喷面,但是會(huì)把this.state.comments完全覆蓋。
數(shù)據(jù)向下流動(dòng)(The Data Flows Down)
父組件和子組件都不知道一個(gè)確切的組件是有狀態(tài)的(stateful)還是無狀態(tài)的(stateless)走孽,它們不應(yīng)該在意它是用類式定義的還是函數(shù)式定義的惧辈。
這也是state被稱為局部的或者封裝的原因,它除了被擁有它的組件訪問以外磕瓷,不可以被任何其它的組件訪問盒齿。
一個(gè)組件可能會(huì)選擇以props的形式向下傳遞state給它的子組件:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
它同樣適用于自定義組件:
<FormattedDate date={this.state.date} />
這個(gè)FormattedDate 組件將會(huì)在它的props中接收到date,但是并不知道它是否來自Clock的state困食,Clock的props边翁,或者是手動(dòng)輸入的:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
這通常被稱作自頂向下(top-down)或者 單向(unidirectional)的數(shù)據(jù)流動(dòng)。任何state都被指定的組件所擁有硕盹,并且任何源自這個(gè)state的數(shù)據(jù)或者UI僅僅可以影響在組件樹中位于它們下方的組件(Any state is always owned by some specific component, and any data or UI derived from that state can only affect components “below” them in the tree.)符匾。
如果你把組件樹想象成props的瀑布流,每個(gè)組件的state在某一點(diǎn)隨意加入的水源瘩例,但是它也是向下流動(dòng)的啊胶。
為了展示所有的組件都是獨(dú)立的,我們可以創(chuàng)建一個(gè)App組件垛贤,它渲染了三個(gè)<Clock/>:
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
每一個(gè)Clock啟動(dòng)它自己的定時(shí)器焰坪,并且獨(dú)立的進(jìn)行更新。
在React應(yīng)用中聘惦,無論一個(gè)組件是有狀態(tài)的還是無狀態(tài)的某饰,都被當(dāng)作是組件的細(xì)節(jié)實(shí)現(xiàn)。它們可能隨時(shí)間而改變,你可以在有狀態(tài)的組件黔漂;里面使用無狀態(tài)的組件碧浊,反之亦然。