React State(狀態(tài)): React通過this.state來訪問state瞻佛,通過this.setState()方法來更新state

React State(狀態(tài))

React 把組件看成是一個(gè)狀態(tài)機(jī)(State Machines)匕垫。通過與用戶的交互,實(shí)現(xiàn)不同狀態(tài)除破,然后渲染 UI牧氮,讓用戶界面和數(shù)據(jù)保持一致。
React 里瑰枫,只需更新組件的 state踱葛,然后根據(jù)新的 state 重新渲染用戶界面(不要操作 DOM)。
以下實(shí)例中創(chuàng)建了 LikeButton 組件光坝,getInitialState 方法用于定義初始狀態(tài)尸诽,也就是一個(gè)對(duì)象,這個(gè)對(duì)象可以通過 this.state 屬性讀取盯另。當(dāng)用戶點(diǎn)擊組件性含,導(dǎo)致狀態(tài)變化,this.setState 方法就修改狀態(tài)值鸳惯,每次修改以后商蕴,自動(dòng)調(diào)用 this.render 方法,再次渲染組件芝发。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>菜鳥教程 React 實(shí)例</title>
    <script src="https://cdn.bootcss.com/react/15.4.2/react.min.js"></script>
    <script src="https://cdn.bootcss.com/react/15.4.2/react-dom.min.js"></script>
    <script src="https://cdn.bootcss.com/babel-standalone/6.22.1/babel.min.js"></script>
  </head>
  <body>
    <div id="example"></div>
    <script type="text/babel">
      var LikeButton = React.createClass({
        getInitialState: function() {
          return {liked: false};
        },
        handleClick: function(event) {
          this.setState({liked: !this.state.liked});
        },
        render: function() {
          var text = this.state.liked ? '喜歡' : '不喜歡';
          return (
            <p onClick={this.handleClick}>
              你<b>{text}</b>我绪商。點(diǎn)我切換狀態(tài)。
            </p>
          );
        }
      });

      ReactDOM.render(
        <LikeButton />,
        document.getElementById('example')
      );
    </script>
  </body>
</html>

我們都知道辅鲸,React通過this.state來訪問state格郁,通過this.setState()方法來更新state。當(dāng)this.setState()方法被調(diào)用的時(shí)候独悴,React會(huì)重新調(diào)用render方法來重新渲染UI

setState異步更新

setState方法通過一個(gè)隊(duì)列機(jī)制實(shí)現(xiàn)state更新例书,當(dāng)執(zhí)行setState的時(shí)候,會(huì)將需要更新的state合并之后放入狀態(tài)隊(duì)列绵患,而不會(huì)立即更新this.state(可以和瀏覽器的事件隊(duì)列類比)雾叭。如果我們不使用setState而是使用this.state.key來修改,將不會(huì)觸發(fā)組件的re-render落蝙。如果將this.state賦值給一個(gè)新的對(duì)象引用织狐,那么其他不在對(duì)象上的state將不會(huì)被放入狀態(tài)隊(duì)列中暂幼,當(dāng)下次調(diào)用setState并對(duì)狀態(tài)隊(duì)列進(jìn)行合并時(shí),直接造成了state丟失移迫。(這里特別感謝@Dcatfly的指正)

我們來看一下React文檔中對(duì)setState的說明

    void setState(
      function|object nextState,
      [function callback]
    )

The second (optional) parameter is a callback function that will be executed once setState is completed and the component is re-rendered.

翻譯一下旺嬉,第二個(gè)參數(shù)是一個(gè)回調(diào)函數(shù),在setState的異步操作結(jié)束并且組件已經(jīng)重新渲染的時(shí)候執(zhí)行厨埋。也就是說邪媳,我們可以通過這個(gè)回調(diào)來拿到更新的state的值。

React也正是利用狀態(tài)隊(duì)列機(jī)制實(shí)現(xiàn)了setState的異步更新荡陷,避免頻繁地重復(fù)更新state(pending的意思是未定的雨效,即將發(fā)生的)

   //將新的state合并到狀態(tài)更新隊(duì)列中
   var nextState =  this._processPendingState(nextProps, nextContext);
   //根據(jù)更新隊(duì)列和shouldComponent的狀態(tài)來判斷是否需要更新組件
   var shouldUpdate = 
      this._pendingForceUpdate ||
      !inst.shouldComponentUpdate ||
      inst.shouldComponentUpdate(nextProps, nextState, nextContext);

setState循環(huán)調(diào)用風(fēng)險(xiǎn)

當(dāng)調(diào)用setState時(shí),實(shí)際上會(huì)執(zhí)行enqueueSetState方法废赞,并對(duì)partialState以及_pending-StateQueue更新隊(duì)列進(jìn)行合并操作徽龟,最終通過enqueueUpdate執(zhí)行state更新

而performUpdateIfNecessary方法會(huì)獲取pendingElement, pendingStateQueue,_ pending-ForceUpdate唉地,并調(diào)用receiveComponent和updateComponent方法進(jìn)行組件更新

如果在shouldComponentUpdate或者componentWillUpdate方法中調(diào)用setState据悔,此時(shí)this._pending-StateQueue != null,就會(huì)造成循環(huán)調(diào)用耘沼,使得瀏覽器內(nèi)存占滿后崩潰

調(diào)用棧

既然setState最終是通過enqueueUpdate執(zhí)行state更新极颓,那么enqueueUpdate到底是如何更新state的呢? 首先看下面的問題

   import React, { Component } from 'react';
   class Example extends Component {
       constructor(){
           super();
           //在組件初始化可以直接操作this.state
           this.state = {
               val: 0
           }
       }
       componentDidMount(){
           this.setState({
              val: this.state.val + 1
           });
           //第一次輸出
           console.log(this.state.val);
           this.setState({
              val: this.state.val + 1
           });
           //第二次輸出
           console.log(this.state.val);
           setTimeout(()=>{
              this.setState({val: this.state.val + 1});
               //第三次輸出
               console.log(this.state.val);
               this.setState({
                  val: this.state.val + 1
               });
               //第四次輸出
               console.log(this.state.val);
           }, 0);  
       }
       render(){
           return null;
       }
   }

上述代碼中群嗤,4次console.log打印出來的val分別是: 0菠隆,0,2 狂秘,3

我們來看一個(gè)簡(jiǎn)化的setState的調(diào)用棧

   this.setState(newState) =>
   newState存入pending隊(duì)列 =>
   調(diào)用enqueueUpdate =>
   是否處于批量更新模式 =>
   是的話將組件保存到dirtyComponents
   不是的話遍歷dirtyComponents浸赫,調(diào)用updateComponent,更新pending state or props

enqueueUpdate的源碼如下(上面流程的第三步)(batching的意思是批量的)

   function enqueueUpdate(component){
       //injected注入的
       ensureInjected();
       //如果不處于批量更新模式
       if(!batchingStrategy.isBatchingUpdates){
           batchingStrategy.batchedUpdates(enqueueUpdate, component);
           return;
       }
       //如果處于批量更新模式
       dirtyComponents.push(component);
   }

如果isBatchingUpdates為true,則對(duì)所有隊(duì)列中的更新執(zhí)行batchedUpdates方法赃绊,否則只把當(dāng)前組件(即調(diào)用了setState的組件)放入dirtyComponents數(shù)組中,例子中4次setState調(diào)用的表現(xiàn)之所以不同羡榴,這里的邏輯判斷起了關(guān)鍵作用

事務(wù)

事務(wù)就是將需要執(zhí)行的方法使用wrapper封裝起來碧查,再通過事務(wù)提供的perform方法執(zhí)行,先執(zhí)行wrapper中的initialize方法校仑,執(zhí)行完perform之后忠售,在執(zhí)行所有的close方法,一組initialize及close方法稱為一個(gè)wrapper迄沫。

那么事務(wù)和setState方法的不同表現(xiàn)有什么關(guān)系稻扬,首先我們把4次setState簡(jiǎn)單歸類,前兩次屬于一類羊瘩,因?yàn)樗鼈冊(cè)谕徽{(diào)用棧中執(zhí)行泰佳,setTimeout中的兩次setState屬于另一類盼砍。

在setState調(diào)用之前,已經(jīng)處在batchedUpdates執(zhí)行的事務(wù)中了逝她。那么這次batchedUpdates方法是誰調(diào)用的呢浇坐,原來是ReactMount.js中的_renderNewRootComponent方法。也就是說黔宛,整個(gè)將React組件渲染到DOM中的過程就是處于一個(gè)大的事務(wù)中近刘。而在componentDidMount中調(diào)用setState時(shí),batchingStrategy的isBatchingUpdates已經(jīng)被設(shè)為了true臀晃,所以兩次setState的結(jié)果沒有立即生效觉渴。

再反觀setTimeout中的兩次setState,因?yàn)闆]有前置的batchedUpdates調(diào)用徽惋,所以導(dǎo)致了新的state馬上生效案淋。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市寂曹,隨后出現(xiàn)的幾起案子哎迄,更是在濱河造成了極大的恐慌,老刑警劉巖隆圆,帶你破解...
    沈念sama閱讀 211,561評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件漱挚,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡渺氧,警方通過查閱死者的電腦和手機(jī)旨涝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,218評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來侣背,“玉大人白华,你說我怎么就攤上這事》纺停” “怎么了弧腥?”我有些...
    開封第一講書人閱讀 157,162評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)潮太。 經(jīng)常有香客問我管搪,道長(zhǎng),這世上最難降的妖魔是什么铡买? 我笑而不...
    開封第一講書人閱讀 56,470評(píng)論 1 283
  • 正文 為了忘掉前任更鲁,我火速辦了婚禮,結(jié)果婚禮上奇钞,老公的妹妹穿的比我還像新娘澡为。我一直安慰自己,他們只是感情好景埃,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,550評(píng)論 6 385
  • 文/花漫 我一把揭開白布媒至。 她就那樣靜靜地躺著顶别,像睡著了一般。 火紅的嫁衣襯著肌膚如雪塘慕。 梳的紋絲不亂的頭發(fā)上筋夏,一...
    開封第一講書人閱讀 49,806評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音图呢,去河邊找鬼条篷。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蛤织,可吹牛的內(nèi)容都是我干的赴叹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,951評(píng)論 3 407
  • 文/蒼蘭香墨 我猛地睜開眼指蚜,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼乞巧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起摊鸡,我...
    開封第一講書人閱讀 37,712評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤绽媒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后免猾,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體是辕,經(jīng)...
    沈念sama閱讀 44,166評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,510評(píng)論 2 327
  • 正文 我和宋清朗相戀三年猎提,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了获三。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,643評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡锨苏,死狀恐怖疙教,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情伞租,我是刑警寧澤贞谓,帶...
    沈念sama閱讀 34,306評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站葵诈,受9級(jí)特大地震影響经宏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜驯击,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,930評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耐亏。 院中可真熱鬧徊都,春花似錦、人聲如沸广辰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,745評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至李根,卻和暖如春槽奕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背房轿。 一陣腳步聲響...
    開封第一講書人閱讀 31,983評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工粤攒, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人囱持。 一個(gè)月前我還...
    沈念sama閱讀 46,351評(píng)論 2 360
  • 正文 我出身青樓夯接,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親纷妆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盔几,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,509評(píng)論 2 348

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

  • 說在前面 關(guān)于 react 的總結(jié)過去半年就一直碎碎念著要搞起來,各(wo)種(tai)原(lan)因(le)掩幢。心...
    陳嘻嘻啊閱讀 6,850評(píng)論 7 41
  • 前面提過react中的state和props是react組件中的兩大部分逊拍,有很多人分不清state和props,這...
    DCbryant閱讀 17,972評(píng)論 0 11
  • 原教程內(nèi)容詳見精益 React 學(xué)習(xí)指南际邻,這只是我在學(xué)習(xí)過程中的一些閱讀筆記芯丧,個(gè)人覺得該教程講解深入淺出,比目前大...
    leonaxiong閱讀 2,813評(píng)論 1 18
  • 我曾很害怕的時(shí)候 會(huì)躲在云層的背后枯怖,或者藏在森林里 我曾赤身裸體的呼喊云朵和春天 像自由的云注整,或者像孤獨(dú)的花 夏已...
    馮玙哲閱讀 260評(píng)論 6 10
  • 寵寵寵文,大甜
    木木影視會(huì)員閱讀 601評(píng)論 0 0