考慮之前的例子褒翰,我們只學(xué)會(huì)了一種方法去更新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);
在這個(gè)章節(jié)险耀,我們將會(huì)學(xué)到如何將Clock這個(gè)自定義組件變的真正可復(fù)用而且封裝完備觉痛,讓它可以在內(nèi)部設(shè)置它自己的時(shí)間定時(shí)器并且按照這個(gè)定時(shí)器時(shí)時(shí)更新它的UI荠瘪。
我們可以先看一看clock長(zhǎng)什么樣子:
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)重要的需求:Clock的定時(shí)器必須在其內(nèi)部定義實(shí)現(xiàn)孕索。
我們想要的是只需要將Clock組件寫(xiě)一次它就可以自動(dòng)的更新它本身逛艰,像下面這樣:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
為了實(shí)現(xiàn)我們所想要的效果,我們需要給Clock組件添加一個(gè)state屬性搞旭。
state和props看起來(lái)像是一樣的散怖,但是state是組件私有的并且完全受到組件自身控制。
在之前的章節(jié)我們提到過(guò)肄渗,類(lèi)式的聲明component會(huì)使定義的組件有一些額外的特性镇眷,state就是一個(gè),它只在類(lèi)式聲明的組件中起作用翎嫡。
將函數(shù)式聲明轉(zhuǎn)化為類(lèi)式聲明
你可以通過(guò)以下五個(gè)步驟將函數(shù)式聲明轉(zhuǎn)化為類(lèi)式聲明:
- 創(chuàng)建一個(gè)ES6的class欠动,這個(gè)class繼承自React.Component。
- 為這個(gè)類(lèi)添加一個(gè)名為render的空函數(shù)惑申。
- 將函數(shù)式聲明內(nèi)部的代碼移到render函數(shù)中具伍。
- 將render函數(shù)中的props替換成this.props。
- 將函數(shù)式聲明刪除圈驼。
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.props.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
現(xiàn)在Clock組件是一個(gè)類(lèi)式定義的組件了人芽,這使得你可以使用組件額外的特性,比如:state绩脆,鉤子函數(shù)萤厅。
給這個(gè)類(lèi)式聲明的組件添加state
我們將props對(duì)象中的state轉(zhuǎn)化成state需要經(jīng)過(guò)三個(gè)步驟:
- 在render函數(shù)中將this.props.date用this.state.date代替:
class Clock extends React.Component {
render() {
return (
<div>
<h1>Hello, world!</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}
- 添加一個(gè)類(lèi)構(gòu)造器,在構(gòu)造器中指定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>
);
}
}
注意我們?nèi)绾螌rops傳遞給構(gòu)造器:
constructor(props) {
super(props);
this.state = {date: new Date()};
}
類(lèi)式聲明的組件必須調(diào)用super(props)這行代碼靴迫。
- 將Clock組件聲明中去掉date:
ReactDOM.render(
<Clock />,
document.getElementById('root')
);
待會(huì)我們會(huì)添加一個(gè)定時(shí)器的代碼給組件√栉叮現(xiàn)在我們所有完成的代碼看起來(lái)是下面這個(gè)樣子:
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)我們?yōu)镃lock組件設(shè)置自己的定時(shí)器并且依照這個(gè)定時(shí)器實(shí)時(shí)更新它自己。
為組件添加生命周期函數(shù)
在實(shí)際應(yīng)用中玉锌,對(duì)于組件來(lái)說(shuō)非常重要的一點(diǎn)是在它被銷(xiāo)毀的時(shí)候釋放自身的資源名挥。
我們想要為Clock組件設(shè)置一個(gè)定時(shí)器以便Clock組件在任何被聲明的時(shí)候可以立即執(zhí)行這個(gè)定時(shí)器,這段代碼被寫(xiě)在一個(gè)稱(chēng)之為裝載函數(shù)的內(nèi)部芬沉。
我們也想在這個(gè)組件被移除時(shí)銷(xiāo)毀這個(gè)定時(shí)器躺同,這段代碼被寫(xiě)在一個(gè)稱(chēng)之為卸載函數(shù)的內(nèi)部。
我們可以在class里聲明兩個(gè)不同的方法以實(shí)現(xiàn)組件的裝載和卸載:
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>
);
}
}
這些方法被稱(chēng)為生命循環(huán)鉤子函數(shù)丸逸。
componentDidMount()函數(shù)在組件被渲染到dom上時(shí)執(zhí)行蹋艺,這個(gè)我們?cè)O(shè)置定時(shí)器的絕佳地方:
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
注意看看我們是如何將定時(shí)器ID保存在this中的。
當(dāng)this.props被組件自己設(shè)置且this.state有一個(gè)特殊的含義時(shí)黄刚,你就可以手動(dòng)的為這個(gè)類(lèi)添加你需要儲(chǔ)存的東西捎谨,這些你添加的東西是不會(huì)被輸出到外部的。
你在render函數(shù)中用不到的東西不應(yīng)該出現(xiàn)在state中憔维。
我們將清除定時(shí)器的代碼寫(xiě)在componentWillUnmount函數(shù)中:
componentWillUnmount() {
clearInterval(this.timerID);
}
最后涛救,我們實(shí)現(xiàn)tick方法。tick方法用this.setState函數(shù)來(lái)實(shí)時(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)在tick函數(shù)每過(guò)一秒便被執(zhí)行一次业扒。讓我們快速整理一下上面的代碼:
- 首先我們將clock組件作為參數(shù)傳遞給ReactDOM.render()函數(shù)检吆,這時(shí)候react會(huì)調(diào)用clock組件中的構(gòu)造器,在構(gòu)造器中初始化了state程储。
- react這時(shí)會(huì)調(diào)用組件中的render方法蹭沛,這個(gè)方法的返回值即為react渲染到屏幕上的element。
- 當(dāng)clock的返回值被插入到dom中時(shí)章鲤,react會(huì)開(kāi)始執(zhí)行componentDidMount這個(gè)方法摊灭。在這個(gè)方法內(nèi)部,我們定義了一個(gè)tick的函數(shù)實(shí)時(shí)的更新時(shí)間败徊。
- 每一秒鐘clcok都會(huì)執(zhí)行tick這個(gè)函數(shù)帚呼,在tick函數(shù)內(nèi)部,我們每一秒都會(huì)用當(dāng)前時(shí)間去替換this.state.date的值皱蹦。
- 如果clock組件被移除dom時(shí)煤杀,react 將會(huì)調(diào)用componentWillUnmount函數(shù)去移除我們?cè)赾omponentDidMount中定義的定時(shí)器。
正確使用State
對(duì)于setState函數(shù)根欧,你必須知道以下三點(diǎn):
** 不要直接修改State **
比如怜珍,下面這行代碼不會(huì)改變component:
// Wrong
this.state.comment = 'Hello';
你需要用下面這行代碼代替:
// Correct
this.setState({comment: 'Hello'});
你唯一可以定義state的地方便是constructor。
** state可能是異步更新的 **
react可能會(huì)因?yàn)樾阅芏淮涡缘膱?zhí)行多個(gè)setState函數(shù)凤粗。正是由于this.props和this.state會(huì)異步更新酥泛,所以你不能直接用它們的值來(lái)進(jìn)行計(jì)算,比如下面的例子你可能得不到想要的結(jié)果:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
為了讓值確定嫌拣,我們需要用到setState函數(shù)的第二種傳參方式柔袁,傳遞一個(gè)函數(shù)進(jìn)去而不是對(duì)象。這個(gè)函數(shù)將以前的state作為它的第一個(gè)參數(shù)异逐,props作為第二個(gè)參數(shù):
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
上面是我們使用了尖頭函數(shù)捶索,它和普通的函數(shù)沒(méi)什么區(qū)別:
// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
State更新是合并性更新
當(dāng)你調(diào)用setState函數(shù)時(shí),react會(huì)合并你此次設(shè)置的state和原來(lái)的state灰瞻。比如腥例,你的state包含以下2個(gè)變量:
constructor(props) {
super(props);
this.state = {
posts: [],
comments: []
};
}
你可以單獨(dú)的更新它們:
componentDidMount() {
fetchPosts().then(response => {
this.setState({
posts: response.posts
});
});
fetchComments().then(response => {
this.setState({
comments: response.comments
});
});
}
數(shù)據(jù)流
不管是根組件或者子組件辅甥,它們都不知道一個(gè)組件是有狀態(tài)的還是無(wú)狀態(tài)的,也不知道這個(gè)組件使用類(lèi)式定義的函數(shù)函數(shù)式定義的燎竖。這就是為什么state被稱(chēng)為本地的或者被封裝的璃弄。一個(gè)組件可以選擇是否將其state作為props傳遞給子組件:
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
這一特性同樣也在自定義組件中工作正常:
<FormattedDate date={this.state.date} />
FormattedDate組件將date作為其props,但是它并不知道date是來(lái)自于Clock的state构回,Clock的props或者手動(dòng)輸入的值:
function FormattedDate(props) {
return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}
這一特性通常被稱(chēng)為數(shù)據(jù)流夏块。一些state通常被一些特定的component所有,所以這些組件之下的用到這些state的子組件會(huì)受到這些特殊組件的影響纤掸。為了展示所以的組件都是獨(dú)立的脐供,我們可以創(chuàng)建一個(gè)擁有3個(gè)Clock組件的應(yīng)用:
function App() {
return (
<div>
<Clock />
<Clock />
<Clock />
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
每一個(gè)clock組件都有它自己的定時(shí)器。