State和生命周期

想一下上一節(jié)中那個滴答計時的例子莺禁。
迄今為止,我們只學(xué)到一種更新UI的方法狞换。
我們通過調(diào)用ReactDOM.render()來改變已經(jīng)渲染的輸出:

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);

在CodePen上試一試
在本節(jié)里谭网,我們將學(xué)會做一個真正可重用且有封裝良好的Clock組件痒芝。
我們可以從封裝時鐘的外觀開始:

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);

在CodePen上試一試
不過,上面的代碼漏了一個關(guān)鍵的需求:Clock應(yīng)該自己去設(shè)置計時器静秆,并且每秒更新UI粮揉。
理想情況下,我們想要只寫一次诡宗,并且讓Clock去更新自己:

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

為了實現(xiàn)這個功能滔蝉,我們需要為Clock組件添加state
State和props類似塔沃,但他是私有的蝠引,并完全由組件控制阳谍。
正如我們之前提到的,使用類定義的組件有一些額外的特性螃概。本地的state就是這樣一個特性:只能通過類來開啟矫夯。

將函數(shù)轉(zhuǎn)換成類

你可以在五個步驟內(nèi)將一個像Clock一樣的功能化組件轉(zhuǎn)為一個類:

  1. 創(chuàng)建一個擴(kuò)展自React.Component的同名ES6類
  2. 添加一個名為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>
    );
  }
}

在CodePen上試一試
Clock現(xiàn)在就是通過類來定義的递沪,而非函數(shù)嘍。
現(xiàn)在我們就可以添加諸如本地狀態(tài)和生命周期鉤子等額外的特性了综液。

向類中添加本地狀態(tài)

我們將date從props中移動到狀態(tài)中:

  1. render()方法中的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>
    );
  }
}
  1. 添加一個類的構(gòu)造函數(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òu)造函數(shù)的:

 constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

組件類應(yīng)該始終調(diào)用父類的構(gòu)造函數(shù),并傳入props谬莹。

  1. 將propdate<Clock />元素中移除:
ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

稍后我們將計時器代碼加回組件內(nèi)檩奠。
現(xiàn)在結(jié)果看起來:

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')
);

在CodePen上試一試
下面我們會讓Clock設(shè)置它自己的計時器,每秒自己去進(jìn)行更新附帽。

在類中添加生命周期方法

在有很多組件的應(yīng)用中埠戳,當(dāng)組件被銷毀時,組件可以釋放自己管理的資源是非常的重要蕉扮。
我們希望Clock在第一次被渲染到DOM時設(shè)置一個計時器整胃。這在React中被稱作"掛載"。
同樣的慢显,我們也希望在生成Clock被移除的DOM時爪模,清除掉這個計時器.這在React中被稱為"取消掛載"。
我們可以在組件類里聲明一些特殊的方法荚藻,在組件掛載和取消掛載的時候執(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()在組件被渲染到DOM后被執(zhí)行。在這里設(shè)置計時器非常合適:

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    );
  }

注意我們?nèi)绾螌⒂嫊r器的ID保存到this上的应狱。
this.props由React自己來設(shè)置共郭,this.state有特殊的意義,除此之外疾呻,如果你需要存一些不用于顯示的東西除嘹,可以自由地添加這些字段到類上面。
不再render()中使用的東西岸蜗,就不應(yīng)該將它們加入state尉咕。
我們將在生命周期鉤子componentWillUnmount()中拆除這個計時器:

  componentWillUnmount() {
    clearInterval(this.timerID);
  }

最終,我們來實現(xiàn)每秒運行的這個tick()方法璃岳。
它將使用this.setState()來定時更新組件的本地狀態(tài):

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')
);

在CodePen上試一試

現(xiàn)在時鐘每秒滴答一次年缎。
讓我們快速的回顧下發(fā)生了什么悔捶,還有這些方法調(diào)用的順序:

  1. 當(dāng)<Clock />傳給ReactDOM.render()時,React調(diào)用Clock組件的構(gòu)造函數(shù)单芜。因為Clock需要顯示當(dāng)前的時間蜕该,他使用一個包含當(dāng)前時間的對象來初始化this.state。我們稍后更新這個state洲鸠。
  2. 接著堂淡,React調(diào)用Clock組件的render()方法。React由此得知那些東西應(yīng)該顯示在屏幕上扒腕。然后React更新DOM來使之匹配Clock的渲染輸出绢淀。
  3. 當(dāng)Clock的輸出被插入DOM中,React調(diào)用componentDidMount()生命周期鉤子袜匿。在這個鉤子中更啄,Clock組件將請求瀏覽器設(shè)置一個計時器每秒調(diào)用tick()稚疹。
  4. 瀏覽器每秒調(diào)用一次tick()方法居灯。在這個方法中,Clock組件通過調(diào)用setState()(傳入一個包含當(dāng)前時間的對象)内狗,來調(diào)度UI的更新怪嫌。就是因為調(diào)用了setState(),React才得知狀態(tài)發(fā)生了變化柳沙,然后去再次調(diào)用render()方法岩灭,從而知道哪些東西應(yīng)該放在屏幕上。這次赂鲤,render()方法中的this.state.date將會不同噪径,所以渲染結(jié)果將包含更新后的時間。React也會相應(yīng)的更新DOM数初。
  5. 如果Clock組件從DOM中刪除找爱,React將會調(diào)用componentWillUnmount()生命周期鉤子,這樣計時器就停止了泡孩。

正確地使用State

關(guān)于setState()车摄,你需要知道三件事情。

不要直接修改State

比如仑鸥,這個組件就不會重新渲染:

// Wrong
this.state.comment = 'Hello';

setState()來代替:

// Correct
this.setState({comment: 'Hello'});

唯一一個你可以給this.state賦值的地方就是構(gòu)造函數(shù)吮播。

狀態(tài)更新可能是異步的

React為了性能,可能將多個setState()放在一起進(jìn)行更新眼俊。
由于this.propsthis.state可能不同時更新意狠,你不該依賴這些值來計算下一個狀態(tài)。
比如疮胖,下面更新計數(shù)器的代碼可能會失效:

// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});

使用setState()的第二種形式(參數(shù)是一個函數(shù)环戈,而不是一個對象)可以修復(fù)這種情況誊役。這個函數(shù)將上個狀態(tài)作為第一個參數(shù),這次更新時的props作為第二個參數(shù):

// Correct
this.setState((prevState, props) => ({
  counter: prevState.counter + props.increment
}));

上面我們用到了箭頭函數(shù)谷市,但用普通的函數(shù)也可以蛔垢。

// Correct
this.setState(function(prevState, props) {
  return {
    counter: prevState.counter + props.increment
  };
});

合并狀態(tài)更新

當(dāng)你調(diào)用setState(),React將你提供的對象合并到當(dāng)前狀態(tài)迫悠。
舉例來說鹏漆,你的狀態(tài)可能包含幾個獨立的變量:

constructor(props) {
  super(props);
  this.state = {
    posts: [],
    comments: []
  };
}

你可以多次調(diào)用setState()來獨立地更新他們:

  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ù)流

一個組件的父子都不會知道該組件是有狀態(tài)的還是無狀態(tài)的,而且他們不會去關(guān)心它是以函數(shù)還是類的形式定義的鞠抑。
這就是為什么狀態(tài)被稱作本地或封裝的饭聚。只有擁有、設(shè)置它的組件才可以訪問它搁拙。
一個組件可以選擇將它的狀態(tài)作為props向下傳給它的子組件:

<h2>It is {this.state.date.toLocaleTimeString()}.</h2>

這對自定義組件同樣有效:

<FormattedDate date={this.state.date} />

FormattedDate組件從它的props中接收date秒梳,而他并不知道這個數(shù)據(jù)來源到底是Clock的狀態(tài)彭雾,還是Clock的props函喉,亦或手動輸入的:

function FormattedDate(props) {
  return <h2>It is {props.date.toLocaleTimeString()}.</h2>;
}

在CodePen上試一試
這通常被稱為“自上而下”或“單向”數(shù)據(jù)流。任何狀態(tài)始終由某個特定組件擁有么夫,從該狀態(tài)導(dǎo)出的數(shù)據(jù)或UI只能影響樹狀結(jié)構(gòu)中他下面的組件盐茎。
如果你將一個組件樹想象成一個props的瀑布兴垦,那么每個組件的狀態(tài)就像一個額外的水源,它可以在任意一點加入字柠,但也是向下流動探越。
為了表明組件間真的都是獨立的,我們可以創(chuàng)建一個渲染三個<Clock>App組件:

function App() {
  return (
    <div>
      <Clock />
      <Clock />
      <Clock />
    </div>
  );
}

ReactDOM.render(
  <App />,
  document.getElementById('root')
);

在CodePen上試一試
每個Clock設(shè)置自己的計時器窑业,并且獨立更新钦幔。
在React應(yīng)用中,組件是否有狀態(tài)数冬,是作為組件的實現(xiàn)細(xì)節(jié)來考慮的节槐,可能隨著時間會發(fā)生變化。你可以在有狀態(tài)的組件中使用無狀態(tài)的組件拐纱,反之亦然铜异。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市秸架,隨后出現(xiàn)的幾起案子揍庄,更是在濱河造成了極大的恐慌,老刑警劉巖东抹,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚂子,死亡現(xiàn)場離奇詭異沃测,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)食茎,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進(jìn)店門蒂破,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人别渔,你說我怎么就攤上這事附迷。” “怎么了哎媚?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵喇伯,是天一觀的道長。 經(jīng)常有香客問我拨与,道長稻据,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任买喧,我火速辦了婚禮捻悯,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岗喉。我一直安慰自己秋度,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布钱床。 她就那樣靜靜地躺著,像睡著了一般埠居。 火紅的嫁衣襯著肌膚如雪查牌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天滥壕,我揣著相機(jī)與錄音纸颜,去河邊找鬼。 笑死绎橘,一個胖子當(dāng)著我的面吹牛胁孙,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播称鳞,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼涮较,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了冈止?” 一聲冷哼從身側(cè)響起狂票,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎熙暴,沒想到半個月后闺属,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慌盯,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年掂器,在試婚紗的時候發(fā)現(xiàn)自己被綠了亚皂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡国瓮,死狀恐怖孕讳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巍膘,我是刑警寧澤厂财,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站峡懈,受9級特大地震影響璃饱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜肪康,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一荚恶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧磷支,春花似錦谒撼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至善榛,卻和暖如春辩蛋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背移盆。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工悼院, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咒循。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓据途,卻偏偏與公主長得像,于是被迫代替她去往敵國和親叙甸。 傳聞我的和親對象是個殘疾皇子颖医,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

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