第五節(jié)——狀態(tài)(State)和生命周期(Lifecycle)

思考一下之前章節(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)的組件碧浊,反之亦然。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘟仿,一起剝皮案震驚了整個(gè)濱河市箱锐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌劳较,老刑警劉巖驹止,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異观蜗,居然都是意外死亡臊恋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門墓捻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來抖仅,“玉大人,你說我怎么就攤上這事砖第〕仿” “怎么了?”我有些...
    開封第一講書人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵梧兼,是天一觀的道長(zhǎng)放吩。 經(jīng)常有香客問我,道長(zhǎng)羽杰,這世上最難降的妖魔是什么渡紫? 我笑而不...
    開封第一講書人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮考赛,結(jié)果婚禮上惕澎,老公的妹妹穿的比我還像新娘。我一直安慰自己颜骤,他們只是感情好唧喉,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著复哆,像睡著了一般欣喧。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上梯找,一...
    開封第一講書人閱讀 51,482評(píng)論 1 302
  • 那天唆阿,我揣著相機(jī)與錄音,去河邊找鬼锈锤。 笑死驯鳖,一個(gè)胖子當(dāng)著我的面吹牛闲询,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播浅辙,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼扭弧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了记舆?” 一聲冷哼從身側(cè)響起鸽捻,我...
    開封第一講書人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎泽腮,沒想到半個(gè)月后御蒲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡诊赊,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年厚满,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碧磅。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡碘箍,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鲸郊,到底是詐尸還是另有隱情丰榴,我是刑警寧澤,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布严望,位于F島的核電站多艇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏像吻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一复隆、第九天 我趴在偏房一處隱蔽的房頂上張望拨匆。 院中可真熱鬧,春花似錦挽拂、人聲如沸惭每。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽台腥。三九已至,卻和暖如春绒北,著一層夾襖步出監(jiān)牢的瞬間黎侈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工闷游, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留峻汉,地道東北人贴汪。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像休吠,于是被迫代替她去往敵國(guó)和親扳埂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • React版本:15.4.2**翻譯:xiyoki ** 考慮前一節(jié)中滴答作響的時(shí)鐘的例子瘤礁。到目前為止阳懂,我們只學(xué)習(xí)...
    前端xiyoki閱讀 394評(píng)論 0 0
  • 想一下上一節(jié)中那個(gè)滴答計(jì)時(shí)的例子。迄今為止柜思,我們只學(xué)到一種更新UI的方法希太。我們通過調(diào)用ReactDOM.rende...
    莫銘閱讀 515評(píng)論 0 0
  • 考慮之前的例子,我們只學(xué)會(huì)了一種方法去更新UI酝蜒,我們調(diào)用ReactDOM.render()去改變輸出渲染: 在這個(gè)...
    編碼的哲哲閱讀 580評(píng)論 0 0
  • 學(xué)習(xí)使用Clock組件誊辉,來重用和封裝。并設(shè)置定時(shí)器亡脑。封裝了一個(gè)定時(shí)器堕澄,如: Try it on CodePen. ...
    ZMJun閱讀 451評(píng)論 0 0
  • State 和生命周期 考慮前面章節(jié)中時(shí)鐘的例子。 到目前位置霉咨,我們僅學(xué)習(xí)了一種更新 UI 的方式蛙紫。 我們調(diào)用Re...
    soojade閱讀 1,212評(píng)論 0 1